diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml index 1b56228555..67b493b3cf 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.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-18-experimental-agent-teams-packages.md -2026-08-18-experimental-agent-teams-packages.md: cec73be48a5cde25fae570587d2ecd17a4a87d01 -2026-08-18-experimental-agent-teams-packages.zh.md: 71b702daa8694fddec6e2a0972704f19955cb509 +2026-08-18-experimental-agent-teams-packages.md: cfaf7b23d03a1adecf8acc18a110717a62aa9ed1 +2026-08-18-experimental-agent-teams-packages.zh.md: 4491f4f638b7c5db26416aa88f2a6453c2a72ff6 diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md index cec73be48a..cfaf7b23d0 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md +++ b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md @@ -12,9 +12,9 @@ An experimental directory without a current package previously imposed placement ## Decision -`packages/experimental/team` and `packages/experimental/tool-team` are private workspace packages. Their npm names remain `@deepseek-ai/dsh-team` and `@deepseek-ai/dsh-tool-team`, so promotion changes paths and release metadata without renaming imports. +`packages/experimental/agent-team` and `packages/experimental/tool-agent-team` are private workspace packages. The [experimental package naming decision](2026-08-19-experimental-package-name-prefix.md) owns their npm names and promotion rename; this note owns their placement, release exclusion, and dependency isolation. -The dsh release family and local baseline publisher exclude every manifest below `packages/experimental/`. Workspace constraints require each experimental package to set `private: true` and omit `publishConfig`. The same top-level check rejects `dependencies`, `optionalDependencies`, and `peerDependencies` from release packages, release apps, or the Python runtime to an experimental package. Experimental packages may depend on release packages and each other; tests may use them through `devDependencies`, and examples may load them explicitly. +The dsh pack and publish set and the local baseline publisher exclude every manifest below `packages/experimental/`. `release:dsh` still advances their manifest versions with the shared dsh version without creating release tags. Workspace constraints require each experimental package to set `private: true` and omit `publishConfig`. The same top-level check rejects `dependencies`, `optionalDependencies`, and `peerDependencies` from release packages, release apps, or the Python runtime to an experimental package. Experimental packages may depend on release packages and each other; tests may use them through `devDependencies`, and examples may load them explicitly. The generic caller-reserved continuable child identity and selective direct-child drain remain in the stable Subagent service. They own Subagent identity and Activation lifecycle without importing or naming Agent Teams; the experimental Team service consumes them in the permitted direction. @@ -28,10 +28,8 @@ Experimental status changes publication and compatibility expectations only. The **Move the Subagent prerequisites into the experimental directory.** Child identity allocation and Activation teardown belong to the Subagent owner and contain no Team-specific contract. Moving or duplicating them would invert the dependency or split one lifecycle across packages. -**Publish experimental packages under different npm names.** Experimental suffixes would make promotion rename every import and configuration row without strengthening release exclusion; private manifests and release-family filtering enforce the actual requirement. - ## Consequences Agent Teams can use the full repository graph and quality checks without entering official tarballs or becoming a supported runtime dependency. A release package cannot expose Team until the Team packages are promoted, so CLI and Web experiments use explicit example or experimental compositions instead of the shipped base bundles. -The product-role grouping is less direct while the packages incubate, and promotion creates path churn. Keeping npm names stable limits that churn to repository paths, metadata, documentation, and composition. +The product-role grouping is less direct while the packages incubate. Promotion creates path and npm-name churn as specified by the experimental package naming decision. diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md index 71b702daa8..4491f4f638 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md @@ -12,9 +12,9 @@ Agent Teams 的服务与工具约定仍在变化,但它需要使用真实 Sess ## 决策 -`packages/experimental/team` 与 `packages/experimental/tool-team` 是私有 workspace 包。其 npm 名仍为 `@deepseek-ai/dsh-team` 与 `@deepseek-ai/dsh-tool-team`,因此 promotion 只需修改路径与发布元数据,无需重命名 import。 +`packages/experimental/agent-team` 与 `packages/experimental/tool-agent-team` 是私有 workspace 包。[实验性包命名决策](2026-08-19-experimental-package-name-prefix.md)负责其 npm 名和 promotion 重命名;本记录负责其目录归属、发布排除与依赖隔离。 -dsh 发布系列与本地 baseline 发布器均排除 `packages/experimental/` 下的所有 manifest。workspace 约束要求每个实验性包设置 `private: true` 并省略 `publishConfig`。同一个顶层检查会拒绝发布包、发布 app 或 Python runtime 通过 `dependencies`、`optionalDependencies` 或 `peerDependencies` 依赖实验性包。实验性包可以依赖发布包和其他实验性包;测试可以通过 `devDependencies` 使用它们,示例可以显式加载它们。 +dsh pack 与 publish 集合以及本地 baseline 发布器均排除 `packages/experimental/` 下的所有 manifest。`release:dsh` 仍会让这些 manifest 跟随 dsh 共享版本递增,但不会创建发布 tag。workspace 约束要求每个实验性包设置 `private: true` 并省略 `publishConfig`。同一个顶层检查会拒绝发布包、发布 app 或 Python runtime 通过 `dependencies`、`optionalDependencies` 或 `peerDependencies` 依赖实验性包。实验性包可以依赖发布包和其他实验性包;测试可以通过 `devDependencies` 使用它们,示例可以显式加载它们。 通用的调用方预留 continuable child 身份和精确 direct-child drain 仍属于稳定 Subagent 服务。它们负责 Subagent 身份与 Activation 生命周期,不 import 或命名 Agent Teams;实验性 Team 服务沿允许的方向消费这些能力。 @@ -28,10 +28,8 @@ dsh 发布系列与本地 baseline 发布器均排除 `packages/experimental/` **把 Subagent 前置能力移入 experimental 目录。** child 身份分配与 Activation teardown 属于 Subagent owner,且不包含 Team 专用约定。移动或复制这些能力会反转依赖方向,或把同一个生命周期拆到多个包中。 -**使用不同的 npm 名发布实验性包。** experimental 后缀会让 promotion 重命名所有 import 与配置项,却不能加强发布排除;私有 manifest 与发布系列过滤已经强制执行实际要求。 - ## 后果 Agent Teams 可以使用完整仓库依赖图与质量检查,而不进入正式 tarball,也不会成为受支持的运行时依赖。在 Team 包 promotion 前,发布包不能暴露 Team,因此 CLI 和 Web 实验使用显式示例或实验性组合,而不是已发布的基础组合包。 -孵化期间的产品职责分组不够直接,promotion 也会产生路径改动。保持 npm 名稳定后,改动范围仅包括仓库路径、元数据、文档和组合。 +孵化期间的产品职责分组不够直接。promotion 会按照实验性包命名决策产生路径和 npm 名改动。 diff --git a/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.i18n.yaml new file mode 100644 index 0000000000..e25d832371 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.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-19-experimental-package-name-prefix.md +2026-08-19-experimental-package-name-prefix.md: 3420fa20a0dd9519af5684142fc8f3e70f2ed779 +2026-08-19-experimental-package-name-prefix.zh.md: b7e7d073157eab272baed4ffb75f987a685f53d7 diff --git a/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.md b/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.md new file mode 100644 index 0000000000..3420fa20a0 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.md @@ -0,0 +1,29 @@ +# Agent Note: Mark experimental packages in npm names + +Status: implemented + +English | [中文](2026-08-19-experimental-package-name-prefix.zh.md) + +## Problem + +Directory placement, private manifests, and release-family filtering keep experimental packages out of releases, but an npm specifier or Cordis configuration row does not expose that status. A stable-looking package name can be copied into another composition without the reader seeing that its complete public contract remains experimental. + +## Decision + +Every package directly below `packages/experimental/` uses the `@deepseek-ai/dsh-experimental-*` npm prefix. The workspace constraints gate discovers those manifests and rejects a missing prefix alongside the existing `private: true` and omitted-`publishConfig` requirements. + +Agent Teams uses `@deepseek-ai/dsh-experimental-agent-team` from `packages/experimental/agent-team` and `@deepseek-ai/dsh-experimental-tool-agent-team` from `packages/experimental/tool-agent-team`. Package imports, Cordis configuration rows, generated catalogs, and repository metadata use those names without compatibility aliases. + +Promotion moves a package to its product-role group, removes `experimental-` from its npm name, and updates every repository reference atomically. The pre-release compatibility policy permits that rename without an alias package. + +## Alternatives considered + +**Keep stable-looking npm names while using only directory and release metadata for experimental status.** This minimizes promotion churn, but import specifiers and configuration rows hide the package status and cannot carry the repository-only placement rule into review. + +**Use an experimental suffix.** A prefix groups every experimental package under one searchable npm namespace and makes the status visible before the product role; a suffix would scatter that marker after role-specific names. + +## Consequences + +Experimental imports and configuration rows identify their support status without consulting repository layout. The top-level constraints command and its focused unit test prevent a newly added experimental package from omitting the prefix. + +Promotion deliberately renames imports, configuration, generated references, and metadata. No compatibility package preserves the experimental name. diff --git a/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.zh.md b/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.zh.md new file mode 100644 index 0000000000..b7e7d07315 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-19-experimental-package-name-prefix.zh.md @@ -0,0 +1,29 @@ +# Agent Note: 在 npm 名中标记实验性包 + +Status: implemented + +[English](2026-08-19-experimental-package-name-prefix.md) | 中文 + +## 问题 + +目录归属、私有 manifest 与发布系列过滤可以阻止实验性包进入发布,但 npm specifier 或 Cordis 配置项无法体现该状态。外观稳定的包名可能被复制到其他组合中,而读者看不出其完整公开约定仍处于实验阶段。 + +## 决策 + +`packages/experimental/` 直属的每个包都使用 `@deepseek-ai/dsh-experimental-*` npm 前缀。workspace constraints 门禁会发现这些 manifest,并在现有 `private: true` 与省略 `publishConfig` 要求之外拒绝缺少该前缀的包。 + +Agent Teams 使用位于 `packages/experimental/agent-team` 的 `@deepseek-ai/dsh-experimental-agent-team`,以及位于 `packages/experimental/tool-agent-team` 的 `@deepseek-ai/dsh-experimental-tool-agent-team`。包 import、Cordis 配置项、生成目录和仓库元数据直接使用这些名称,不提供兼容别名。 + +promotion 会把包移至其产品职责组、从 npm 名中移除 `experimental-`,并原子更新全部仓库引用。预发布兼容策略允许该重命名不提供别名包。 + +## 曾考虑的替代方案 + +**保留外观稳定的 npm 名,仅通过目录和发布元数据表达实验状态。** 这种方式可以减少 promotion 改动,但 import specifier 与配置项会隐藏包状态,也无法在评审中携带仅存在于仓库内的目录规则。 + +**使用 experimental 后缀。** 前缀会把所有实验性包归入一个可搜索的 npm 命名空间,并在产品职责之前显示状态;后缀会把该标记分散在各个职责名称之后。 + +## 后果 + +实验性 import 与配置项无需查阅仓库布局即可表明其支持状态。顶层 constraints 命令及其聚焦单元测试会阻止新实验性包遗漏该前缀。 + +promotion 会明确重命名 import、配置、生成引用与元数据。不会有兼容包保留实验名称。 diff --git a/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.i18n.yaml new file mode 100644 index 0000000000..5917e80616 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.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-19-session-projection-state-and-client-views.md +2026-08-19-session-projection-state-and-client-views.md: 14da0525b2cc838ff496d5902dd66ae6ab456af4 +2026-08-19-session-projection-state-and-client-views.zh.md: edd2edaf0bc897bb2084325a5768e549637ed720 diff --git a/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.md b/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.md new file mode 100644 index 0000000000..14da0525b2 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.md @@ -0,0 +1,28 @@ +# Agent Note: Separate session projection state from client views + +Status: implemented + +English | [中文](2026-08-19-session-projection-state-and-client-views.zh.md) + +## Problem + +The projection registry persisted each unit's internal fold state without a runtime schema, while `SessionProjectionMap` described the client value returned by `view`. This left restored state unvalidated and made the same type table appear to describe two values that may differ. Host consumers also needed the current folded state without serializing every registered client view or exposing internal-only state through the client protocol. + +## Decision + +`SessionProjectionStateMap` is the merge-extensible table for host fold states. Every `ProjectionDefinition` key belongs to this table and supplies a `stateSchema`; cached rows are validated before they seed a fold. `SessionProjectionMap` retains its existing meaning and name as the sole table of client-visible whole values, preserving existing client data structures such as `title: string | null`. + +A unit whose key also appears in `SessionProjectionMap` supplies `wire.viewSchema` and `wire.view`. Every unit's state is checkpointed — client-visible and host-only alike; the `persist` opt-in is gone, so no unit can silently skip the durable cache. Snapshot APIs return only `SessionProjectionMap`, so internal states cannot enter API payloads. Host code reads one current state through `stateOf(session, key)`; the returned reference is borrowed and must not be mutated. + +## Consequences + +Projection state and client values are independently typed and validated without introducing a second client DTO vocabulary. A unit may expose a compact or compatibility-preserving client value while retaining richer host state. Malformed cached state cannot seed `viewCheckpoint`; restore rejects malformed state and the cache's existing full-read fallback rebuilds it from the log. Host consumers can replace private log scans with the same incremental fold used by carriers. + +The original [session-projection proposal](../../proposed/architecture/2026-07-27-session-projection-and-command-log.md) now records this split. The earlier [subagent identity projection](2026-08-06-subagent-list-identity-projection.md) and [projected token usage](2026-07-29-projected-token-usage-and-request-context.md) decisions remain current; their domain folds move to the state table without changing their user-facing values. + +## Alternatives considered + +- **Rename the existing map to a state table and introduce a new client map** — rejected because it changes the established client type name and invites unnecessary client payload migrations. +- **Keep one table for both state and client values** — rejected because a richer fold state and a compatibility-preserving client value then cannot be represented accurately. +- **Opt-in persistence for host-only units** — rejected: a `persist` flag lets a unit silently skip the durable cache, and the savings (one small row per session) never justify the asymmetry or the stateVersion confusion it invites. Every unit's state is checkpointed uniformly. +- **Return copied state from `stateOf`** — rejected because cloning every host read adds work without protecting a boundary; the method documents a readonly borrowed-reference obligation for typed same-process callers. diff --git a/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.zh.md b/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.zh.md new file mode 100644 index 0000000000..edd2edaf0b --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.zh.md @@ -0,0 +1,28 @@ +# Agent Note:拆分会话投影状态与客户端视图 + +状态:已实现 + +[English](2026-08-19-session-projection-state-and-client-views.md) | 中文 + +## 问题 + +投影注册表会持久化各单元的内部折叠状态,却没有运行时 schema;与此同时,`SessionProjectionMap` 描述的是 `view` 返回的客户端值。这使恢复出的状态未经校验,也让同一张类型表看似同时描述两种可能不同的值。host 消费方还需要读取当前折叠状态,但不应为此序列化全部已注册客户端视图,也不应把内部状态暴露到客户端协议。 + +## 决策 + +`SessionProjectionStateMap` 是 host 折叠状态的 merge-extensible 类型表。每个 `ProjectionDefinition` key 都属于此表并提供 `stateSchema`;缓存行只有通过校验后才能为折叠提供初始状态。`SessionProjectionMap` 保留原有名称和语义,继续作为唯一的客户端可见全量值类型表,因此 `title: string | null` 等既有客户端数据结构保持不变。 + +如果一个单元的 key 也存在于 `SessionProjectionMap`,该单元就提供 `wire.viewSchema` 与 `wire.view`。每个单元的状态都会写入检查点——client-visible 与 host-only 一视同仁;`persist` 选择项已移除,任何单元都不能悄悄跳过持久化缓存。快照 API 只返回 `SessionProjectionMap`,因此内部状态不会进入 API 载荷。host 代码通过 `stateOf(session, key)` 读取一份当前状态;返回的是借用引用,不得修改。 + +## 结果 + +投影状态和客户端值分别获得类型与校验,同时不引入第二套客户端 DTO 词汇。单元可以保留更丰富的 host 状态,并暴露紧凑或兼容既有结构的客户端值。畸形缓存状态不能为 `viewCheckpoint` 提供数据;恢复会拒绝畸形状态,并由缓存既有的全量读取回退从日志重建。host 消费方可以用同一套增量折叠替换私有日志扫描。 + +原始 [session-projection 提案](../../proposed/architecture/2026-07-27-session-projection-and-command-log.md)已记录这次拆分。既有的 [subagent 身份投影](2026-08-06-subagent-list-identity-projection.md)与[投影化 token 用量](2026-07-29-projected-token-usage-and-request-context.md)决策仍然有效;其中的领域折叠迁入状态表,不改变面向用户的值。 + +## 考虑过的替代方案 + +- **把既有类型表改名为状态表,再引入新的客户端类型表**——不予采用,因为这会改变已经确立的客户端类型名称,并导致不必要的客户端载荷迁移。 +- **继续用一张类型表同时描述状态与客户端值**——不予采用,因为这样无法准确表达更丰富的折叠状态和保持兼容的客户端值。 +- **host-only 单元按需选择持久化**——不予采用:`persist` 标志会让单元悄悄跳过持久化缓存,而省下的(每会话一行小记录)永远不值得这种不对称或它带来的 stateVersion 困惑。每个单元的状态统一写入检查点。 +- **让 `stateOf` 返回状态副本**——不予采用,因为每次 host 读取都克隆会增加工作,却没有保护任何边界;该方法为同进程类型化调用方明确规定只读借用引用义务。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.i18n.yaml index 11c7a8935a..8bebda2bf5 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.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-29-pnpm-setup-runner-isolation.md -2026-07-29-pnpm-setup-runner-isolation.md: 74b672b3f90ea445ad1a8e283a5904056059b2f8 -2026-07-29-pnpm-setup-runner-isolation.zh.md: fde4cb747e41ea7dafe87ca3ccd0e872c45dd3b1 +2026-07-29-pnpm-setup-runner-isolation.md: c7c076f34dcd4b905a6bb54411538d6cf61bc1d0 +2026-07-29-pnpm-setup-runner-isolation.zh.md: e0eec45d5c3ea53623e95b3ff5509df5ec22457f diff --git a/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.md b/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.md index 74b672b3f9..c7c076f34d 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.md +++ b/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.md @@ -10,9 +10,9 @@ English | [中文](2026-07-29-pnpm-setup-runner-isolation.zh.md) ## Decision -Every `pnpm/action-setup` step in [the primary CI workflow](../../../../.github/workflows/ci.yml) sets `dest: ${{ runner.temp }}/setup-pnpm`. Each runner service owns its temporary directory, so one setup cannot replace another runner's install directory. Persistent store reuse remains separate through `PNPM_CONFIG_STORE_DIR`, as established by the [pnpm provisioning decision](../process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md). +Every `pnpm/action-setup` step in [the primary CI workflow](../../../../.github/workflows/ci.yml) and [the master workflow](../../../../.github/workflows/ci-master.yml) sets `dest: ${{ runner.temp }}/setup-pnpm`. Each runner service owns its temporary directory, so one setup cannot replace another runner's install directory. Persistent store reuse remains separate through `PNPM_CONFIG_STORE_DIR`, as established by the [pnpm provisioning decision](../process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md). -[The workflow regression test](../../../../scripts/ci-workflow.spec.ts) discovers every `pnpm/action-setup` step in `ci.yml` and rejects one without the runner-private destination. This keeps newly added jobs inside the same isolation boundary. +[The workflow regression test](../../../../scripts/ci-workflow.spec.ts) discovers every `pnpm/action-setup` step in `ci.yml` and `ci-master.yml` and rejects one without the runner-private destination. This keeps newly added jobs inside the same isolation boundary. ## Alternatives considered diff --git a/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.zh.md b/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.zh.md index fde4cb747e..e0eec45d5c 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-29-pnpm-setup-runner-isolation.zh.md @@ -10,9 +10,9 @@ Status: implemented ## 决策 -[主 CI 工作流](../../../../.github/workflows/ci.yml)中的每个 `pnpm/action-setup` 步骤都设置 `dest: ${{ runner.temp }}/setup-pnpm`。每个 runner 服务独占自己的临时目录,因此一个设置过程无法替换另一个 runner 的安装目录。持久 store 的复用仍由 `PNPM_CONFIG_STORE_DIR` 独立处理,遵循 [pnpm 配置决策](../process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md)。 +[主 CI 工作流](../../../../.github/workflows/ci.yml)与 [CI master 工作流](../../../../.github/workflows/ci-master.yml)中的每个 `pnpm/action-setup` 步骤都设置 `dest: ${{ runner.temp }}/setup-pnpm`。每个 runner 服务独占自己的临时目录,因此一个设置过程无法替换另一个 runner 的安装目录。持久 store 的复用仍由 `PNPM_CONFIG_STORE_DIR` 独立处理,遵循 [pnpm 配置决策](../process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md)。 -[工作流回归测试](../../../../scripts/ci-workflow.spec.ts)会找出 `ci.yml` 中的每个 `pnpm/action-setup` 步骤,并拒绝缺少 runner 专属目标目录的步骤。这可确保后续新增的作业也处于同一隔离边界内。 +[工作流回归测试](../../../../scripts/ci-workflow.spec.ts)会找出 `ci.yml` 与 `ci-master.yml` 中的每个 `pnpm/action-setup` 步骤,并拒绝缺少 runner 专属目标目录的步骤。这可确保后续新增的作业也处于同一隔离边界内。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml new file mode 100644 index 0000000000..70b0c7f01d --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.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/bug-fix/2026-08-06-bwrap-private-pid-namespace.md +2026-08-06-bwrap-private-pid-namespace.md: c7c83fd274c5dcd6634bdb78a909f1366e1926ba +2026-08-06-bwrap-private-pid-namespace.zh.md: bc3b12c4f6fff31e7dcac630ac390501e8c591b5 diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md b/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md new file mode 100644 index 0000000000..c7c83fd274 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md @@ -0,0 +1,36 @@ +# Agent Note: isolate bwrap from the host PID namespace + +Status: implemented + +English | [中文](2026-08-06-bwrap-private-pid-namespace.zh.md) + +## Problem + +The bwrap backend mounted a fresh `/proc` while retaining the host PID namespace. A confined command could therefore see host processes and follow procfs magic links such as `/proc//root`, `/proc//fd`, or `/proc//cwd` into a host process's mount view. When access controls allowed following one of those links, the path escaped the profile's read-only host-root bind and `workspace-write` allow-list. Host ptrace restrictions sometimes blocked the path, but those deployment-dependent permissions were not a confinement boundary. + +The original [sandbox decision](../feature/2026-07-06-sandbox.md) deliberately left process visibility unchanged because `SandboxMode` promises file effects rather than general process isolation. Procfs magic links make host process visibility part of the file-effect boundary for bwrap, so that choice cannot preserve the promised modes. + +## Decision + +Every bwrap profile uses `--unshare-pid` and mounts `/proc` for that private namespace. The confined command can observe and control its descendants, while host processes and their procfs magic links are absent. Bubblewrap supplies the namespace's PID 1 process to reap descendants. + +The functional bwrap probe uses the same profile builder as real wraps. A host that cannot create the PID namespace therefore rejects bwrap during selection and falls back to Landlock instead of accepting a weaker probe and failing later. + +This is a bwrap backend invariant, not a new `SandboxMode` promise. Landlock and Seatbelt continue to leave process visibility unchanged, and no backend restricts network access. + +## Alternatives considered + +- **Mask selected procfs links while retaining host process visibility.** Per-process entries are dynamic, and covering only `root` would leave equivalent crossings through `fd`, `cwd`, `exe`, and future magic links. A blocklist cannot establish the boundary. +- **Rely on ptrace and procfs ownership checks.** Their behavior depends on kernel settings, container configuration, process credentials, and dumpability. Same-user processes can be reachable, so these checks are defense in depth rather than the profile's authority. +- **Remove `/proc` entirely.** Ordinary process tooling and descendant management expect procfs. A private PID namespace with matching procfs preserves those mechanics without exposing host processes. + +## Verification + +Profile unit tests pin PID unsharing in both confined modes. Real-bwrap tests verify that both modes report a PID-namespace identity different from the harness's, reject a write through `/proc/1/root`, leave the host target absent, and still allow the command to observe, terminate, and wait for its own descendant. + +## Consequences + +- bwrap-confined commands no longer inspect or signal host processes, including same-user processes. +- `read-only` and `workspace-write` no longer depend on host procfs access policy to prevent mount-profile escapes. +- Hosts without usable PID namespaces select the next supported Linux backend through the existing fail-closed ladder. +- The changed guarantee is kernel confinement rather than model-visible output, protocol, or transcript text, so the real-backend e2e is the assembled acceptance path and no snapshot changes. diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md b/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md new file mode 100644 index 0000000000..bc3b12c4f6 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md @@ -0,0 +1,36 @@ +# Agent Note: 将 bwrap 与宿主 PID 命名空间隔离 + +Status: implemented + +[English](2026-08-06-bwrap-private-pid-namespace.md) | 中文 + +## 问题 + +bwrap 后端挂载了全新的 `/proc`,但保留宿主 PID 命名空间。因此,受约束命令可以看到宿主进程,并沿 `/proc//root`、`/proc//fd`、`/proc//cwd` 等 procfs 魔法链接进入宿主进程的挂载视图。当访问控制允许跟随其中某条链接时,该路径便可越过 profile 对宿主根目录的只读绑定挂载,以及 `workspace-write` 的 allow-list。宿主的 ptrace 限制有时会阻断该路径,但这类取决于部署环境的权限并不构成约束边界。 + +最初的[沙箱决策](../feature/2026-07-06-sandbox.md)有意维持进程可见性不变,因为 `SandboxMode` 承诺的是文件影响,而不是一般性的进程隔离。对 bwrap 而言,procfs 魔法链接使宿主进程可见性成为文件影响边界的一部分,因此该选择无法维持这些模式承诺的边界。 + +## 决策 + +每个 bwrap profile 都使用 `--unshare-pid`,并为该私有命名空间挂载 `/proc`。受约束命令可以观察和控制自己的后代进程,但宿主进程及其 procfs 魔法链接不会出现。Bubblewrap 提供该命名空间的 PID 1 进程,用于回收后代进程。 + +bwrap 功能探测与实际包装使用同一个 profile builder。因此,无法创建 PID 命名空间的宿主会在选择阶段拒绝 bwrap 并回退到 Landlock,而不是让较弱的探测通过,随后才失败。 + +这是 bwrap 后端不变式,不是 `SandboxMode` 的新承诺。Landlock 与 Seatbelt 仍保持进程可见性不变,且没有后端限制网络访问。 + +## 曾考虑的替代方案 + +- **在保留宿主进程可见性的同时屏蔽部分 procfs 链接。** 每个进程的条目都会动态变化,只覆盖 `root` 仍会留下可通过 `fd`、`cwd`、`exe` 及未来魔法链接进行的等效越界路径。阻止列表无法建立该边界。 +- **依赖 ptrace 与 procfs 所有权检查。** 其行为取决于内核设置、容器配置、进程凭据,以及进程是否可转储。同一用户的进程可能仍可访问,因此这些检查只属于纵深防御,不能取代由 profile 建立的权威边界。 +- **完全移除 `/proc`。** 常规进程工具和后代进程管理依赖 procfs。私有 PID 命名空间配合对应的 procfs,既能保留这些机制,又不会暴露宿主进程。 + +## 验证 + +profile 单元测试固定两个受约束模式均取消共享 PID 命名空间。真实 bwrap 测试验证:两个模式报告的 PID 命名空间标识都与 harness 不同,拒绝通过 `/proc/1/root` 写入,确保宿主目标文件仍不存在,同时仍允许命令观察、终止并等待自己的后代进程。 + +## 后果 + +- 受 bwrap 约束的命令无法再检查宿主进程或向其发送信号,包括同一用户的进程。 +- `read-only` 与 `workspace-write` 无需再依赖宿主 procfs 访问策略来防止绕过挂载 profile。 +- 无法使用 PID 命名空间的宿主会通过现有的失败关闭阶梯,选择下一个受支持的 Linux 后端。 +- 此次改变的是内核约束保证,不是模型可见输出、协议或 transcript(文本记录)内容;因此,真实后端 e2e 是组装应用的验收路径,无需修改快照。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.i18n.yaml new file mode 100644 index 0000000000..c24317a526 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.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/bug-fix/2026-08-17-blank-permission-default-refresh.md +2026-08-17-blank-permission-default-refresh.md: 6e54d030eed4523974c9ca55ff8a621e107704a9 +2026-08-17-blank-permission-default-refresh.zh.md: a415ed8e729023119d454ceaa24705745b883c70 diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.md b/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.md new file mode 100644 index 0000000000..6e54d030ee --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.md @@ -0,0 +1,29 @@ +# Agent Note: Refresh blank session permission defaults + +Status: implemented + +English | [中文](2026-08-17-blank-permission-default-refresh.zh.md) + +## Problem + +The Web New Session flow reuses a workspace's blank session instead of minting another hidden placeholder. Permission defaults are pinned into a session at creation time, so changing the General settings permission row after a blank placeholder already existed left that placeholder on the previous preset. The next "new" conversation could therefore reuse a blank session whose permission chip contradicted the newly saved default. + +## Decision + +The Web workspace runtime owns candidate selection: a reusable session must be blank, belong to the selected Workspace, match its canonical cwd, and not be archived. Instead of returning that id directly, `WorkspaceRuntime.connectWorkspace` explicitly adopts it through `session.create` with `reuseWorkspaceBlank: true`. The host rechecks blankness, Workspace membership, cwd, and archive state before notification, and can resume a cold persisted placeholder before notifying optional default owners about the exact eligible session. + +`dsh-permission-presets` records each `permission/preset` origin as `default`, `selection`, or `inferred`. On confirmed reuse, it advances the session to the current `defaultPreset` only when no turn has started, the latest selection is default-origin, and the effective sandbox and approval knobs still match that selection. Explicit picks, inferred or origin-less legacy selections, and independently changed knobs remain pinned. The update goes through the normal preset writer, so durable `permission/preset`, `sandbox/mode`, and `approval/policy` facts remain the source for projections and execution. + +This partially refines the earlier [permission default for new sessions](../feature/2026-07-31-permission-default-for-new-sessions.md) decision: a settings write alone does not mutate an existing session, while the later confirmed reuse of a default-origin Workspace blank may advance it after live or cold adoption. + +## Alternatives considered + +**Disable blank-session reuse after any permission settings change.** Rejected because it would leave extra hidden placeholders and make New Session less deterministic. The existing reuse policy is valuable; only stale permission defaults were wrong. + +**Have the client compare a blank session's permission projection with the Settings row.** Rejected because the workspace runtime would need to understand the permission settings namespace. The client reports only its reuse decision; the permission service owns the default-origin test and update. + +**Scan every live blank session when Settings changes.** Rejected because the live store omits cold persisted placeholders and includes blank sessions that Web cannot reuse, such as archived or non-member sessions. It also cannot distinguish an old default from an explicit selection after restart without a durable origin. + +## Consequences + +A Settings change does not rewrite an existing session. Confirmed New Session reuse may append permission facts to a live or cold default-origin placeholder, which remains blank because blankness is defined by the absence of `turn/start`. Started conversations, ordinary seeded resumes, explicit selections, and sessions outside the Web reuse decision keep their permission. diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.zh.md b/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.zh.md new file mode 100644 index 0000000000..a415ed8e72 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-17-blank-permission-default-refresh.zh.md @@ -0,0 +1,29 @@ +# Agent Note: Refresh blank session permission defaults + +Status: implemented + +[English](2026-08-17-blank-permission-default-refresh.md) | 中文 + +## Problem + +Web 新会话流程会复用工作区中的空白会话,而不是不断创建隐藏占位会话。权限默认值在会话创建时被固定到该会话中,因此当某个空白占位会话已经存在后,用户再修改「通用设置」里的权限默认值,这个占位会话仍会保留旧预设。下一次“新”对话复用它时,权限 chip 就会和刚保存的默认设置不一致。 + +## Decision + +Web workspace runtime 负责选择候选会话:可复用会话必须保持空白、属于所选 Workspace、匹配其规范 cwd,并且未归档。`WorkspaceRuntime.connectWorkspace` 不再直接返回该 id,而是通过带 `reuseWorkspaceBlank: true` 的 `session.create` 显式接纳它。host 会在通知前重新检查空白状态、Workspace 成员关系、cwd 与归档状态,并且可以先恢复冷存储中的持久占位会话,再向可选的默认值所有者通知确实符合资格的会话。 + +`dsh-permission-presets` 会把每条 `permission/preset` 的来源记录为 `default`、`selection` 或 `inferred`。复用确认后,只有在会话尚未开始轮次、最近选择来自默认值,并且有效沙箱与审批旋钮仍匹配该选择时,服务才会将它推进到当前 `defaultPreset`。显式选择、由旧旋钮推断或没有来源标记的旧选择,以及独立变更的旋钮都会保持固定。更新仍走常规 preset writer,因此持久的 `permission/preset`、`sandbox/mode` 与 `approval/policy` 事实继续作为投影和执行的来源。 + +这项修复部分细化了较早的[新会话权限默认值](../feature/2026-07-31-permission-default-for-new-sessions.md)决策:单独写入设置不会改变既有会话,而 Web 之后确认复用、且权限来自默认值的 Workspace 空白会话可以在 live 或冷接纳后推进。 + +## Alternatives considered + +**权限设置变化后禁用空白会话复用。** 拒绝,因为这会留下额外的隐藏占位会话,并让新会话行为更不确定。既有复用策略有价值;错误只在于权限默认值过期。 + +**让客户端比较空白会话的权限投影和 Settings 行。** 拒绝,因为 workspace runtime 需要理解 permission settings namespace。客户端只报告自己的复用决定;权限服务拥有默认来源检查和更新。 + +**Settings 变化时扫描所有 live 空白会话。** 拒绝,因为 live store 会漏掉冷存储中的持久占位会话,同时包含 Web 无法复用的空白会话,例如已归档或不属于 Workspace 的会话;重启后若没有持久来源,也无法区分旧默认值与显式选择。 + +## Consequences + +Settings 变更不会改写既有会话。确认的新会话复用可能向 live 或冷存储中、权限来自默认值的占位会话追加权限事实;该会话仍保持 blank,因为 blankness 由是否缺少 `turn/start` 定义。已经开始的对话、普通 seed 恢复、显式选择,以及不在 Web 复用决定中的会话都会保留原权限。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.i18n.yaml new file mode 100644 index 0000000000..8606912401 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.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/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.md +2026-08-19-deepseek-reasoning-passback-every-turn.md: 0fb9ff3cc0991bb2455bc0ef2ec6916b76e20be0 +2026-08-19-deepseek-reasoning-passback-every-turn.zh.md: d01c0c415ca4ce731cc8d9520729fe5b97119a9e diff --git a/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.md b/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.md new file mode 100644 index 0000000000..0fb9ff3cc0 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.md @@ -0,0 +1,33 @@ +# Agent Note: DeepSeek reasoning passback on every reasoned turn + +Status: implemented + +English | [中文](2026-08-19-deepseek-reasoning-passback-every-turn.zh.md) + +## Problem + +`dsh-llm-deepseek` replayed `reasoning_content` in history only on assistant turns that also carried tool calls. DeepSeek's thinking-mode guide requires the field there and ignores it elsewhere, so withholding it on plain turns bought input tokens back with nothing observable lost against `api.deepseek.com`. + +That endpoint is not the only one this adapter serves. `Config.baseURL` points it at any OpenAI-compatible endpoint, including a gateway that re-encodes a DeepSeek chat-completions conversation for another vendor. Such a gateway has no wire slot for the upstream thinking signature and recovers it by hashing the replayed chain of thought. A turn the model answered without calling a tool therefore reached the gateway with no reasoning text at all, the signature lookup found nothing, and the reconstructed conversation diverged from the recorded one. Agent runs call tools on most turns, so the loss appeared only at plain-answer turns and looked intermittent. + +## Decision + +`serializeAssistant` emits `reasoning_content` for every assistant turn whose content carried reasoning, independent of tool calls. An absent reasoning block still emits no field, so a non-thinking turn is unchanged. + +The replayed text is byte-exact with what the provider streamed: `translate.ts` accumulates the whole `reasoning_content` channel of one response into a single reasoning block, so the join in `serializeAssistant` concatenates one member and a hash taken over the replay matches a hash taken over the original delivery. + +## Alternatives considered + +- **A `Config` switch selecting the passback policy.** The two endpoint behaviors are real, but the field is inert where it is unneeded, so the switch only ever buys back one turn's chain of thought in input tokens — against a wrong setting that silently makes a session unreconstructable, with no error at either end to attribute it to. A knob whose wrong position fails silently is worse than the tokens. +- **Deciding from `baseURL`.** Whether an endpoint forwards to another vendor is not readable from its host: an internal endpoint may proxy DeepSeek directly and a public one may forward. The adapter would be guessing at a deployment it cannot see through. +- **Carrying the signature durably instead, as `dsh-llm-pi-ai` does.** That adapter persists `thinkingSignature` per block in its replay state because its providers put the signature on the wire. DeepSeek chat-completions exposes none, so this adapter has nothing to persist and the replayed text is the only channel. + +## Consequences + +Every reasoned tool-call-free turn now costs its chain of thought in input tokens on later requests. The added text sits at that turn's position and is identical on every subsequent request, so the assembled prefix stays stable and only the first request spanning the change loses cache reuse from that point. + +`WireAssistantMessage.reasoning_content` documents both endpoint behaviors, and the package README states the passback rule in the Wire-format notes and the Model Experience token and cache sections. + +## Testing + +`tests/serialize.spec.ts` pins all three assistant shapes: reasoning beside text with no tool call, reasoning beside a tool call, and a reasoning-only turn whose content stays `""`. Turns carrying no reasoning keep emitting no field, which the content-less and tool-call-only cases cover. diff --git a/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.zh.md b/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.zh.md new file mode 100644 index 0000000000..d01c0c415c --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-19-deepseek-reasoning-passback-every-turn.zh.md @@ -0,0 +1,33 @@ +# Agent Note: DeepSeek reasoning passback on every reasoned turn + +Status: implemented + +[English](2026-08-19-deepseek-reasoning-passback-every-turn.md) | 中文 + +## Problem + +`dsh-llm-deepseek` 只在同时携带工具调用的 assistant 轮次上,才把 `reasoning_content` 回放进历史。DeepSeek 思考模式文档在这类轮次上要求该字段,在其他轮次上会忽略它,因此在普通轮次上不回传能省下输入 token,对 `api.deepseek.com` 而言没有任何可观测的损失。 + +但该端点不是这个适配器唯一服务的对象。`Config.baseURL` 可以把它指向任何 OpenAI 兼容端点,包括把 DeepSeek chat-completions 对话重新编码转发给其他厂商的网关。这类网关在协议上没有承载上游思考签名的字段,只能对回放的思维链取哈希来恢复它。于是模型未调用工具就作答的轮次到达网关时完全不带推理文本,签名查找落空,重建出的对话与记录中的对话产生分叉。Agent 运行的大多数轮次都会调用工具,所以这个损失只在纯作答轮次上出现,表现为偶发。 + +## Decision + +`serializeAssistant` 对每个内容携带推理的 assistant 轮次都发出 `reasoning_content`,与是否有工具调用无关。没有推理块时仍然不发出该字段,因此非思考轮次的行为不变。 + +回放文本与提供方流式下发的内容逐字一致:`translate.ts` 会把一次响应的整个 `reasoning_content` 通道累积进单个推理块,因此 `serializeAssistant` 中的拼接只连接一个成员,对回放取的哈希与对原始下发取的哈希相同。 + +## Alternatives considered + +- **用 `Config` 开关选择回传策略。** 两种端点行为都真实存在,但该字段在不需要它的地方是惰性的,所以这个开关最多只换回一个轮次的思维链输入 token —— 代价却是一旦设置错误,会话就会静默地无法重建,两端都不会报错来归因。一个设错就静默失败的旋钮,比那点 token 更糟。 +- **根据 `baseURL` 判断。** 一个端点是否会转发给其他厂商,无法从它的主机名读出:内部端点可能直连代理 DeepSeek,公网端点也可能转发。适配器只能对自己看不透的部署方式做猜测。 +- **改为持久化签名,如 `dsh-llm-pi-ai` 的做法。** 该适配器在 replay state 中按块持久化 `thinkingSignature`,因为它的提供方会把签名放在协议里。DeepSeek chat-completions 不暴露签名,所以这个适配器没有可持久化的东西,回放文本是唯一通道。 + +## Consequences + +每个含推理且不带工具调用的轮次,如今都会在后续请求中按其思维链计入输入 token。新增文本位于该轮次所在位置,且在此后每次请求中都相同,因此组装出的前缀保持稳定,只有跨越此次变更的第一个请求会从该位置起失去缓存复用。 + +`WireAssistantMessage.reasoning_content` 记录了两种端点行为,包 README 在协议格式说明以及 Model Experience 的 token 与缓存小节中陈述了该回传规则。 + +## Testing + +`tests/serialize.spec.ts` 固定了三种 assistant 形态:推理与文本并存且无工具调用、推理与工具调用并存、以及内容保持为 `""` 的纯推理轮次。不携带推理的轮次仍不发出该字段,由无内容与仅工具调用两种用例覆盖。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.i18n.yaml new file mode 100644 index 0000000000..0dc16a86bd --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.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/bug-fix/2026-08-20-composer-reference-decoration-keys.md +2026-08-20-composer-reference-decoration-keys.md: db565e89e1c8addcd1669e295b3be4433083d6bd +2026-08-20-composer-reference-decoration-keys.zh.md: 950338bc5de5c5f45a33481865eacae53d97cb7d diff --git a/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.md b/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.md new file mode 100644 index 0000000000..db565e89e1 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.md @@ -0,0 +1,39 @@ +# Agent Note: Composer reference decorations key by draft-order ordinal + +Status: implemented + +English | [中文](2026-08-20-composer-reference-decoration-keys.zh.md) + +## Problem + +The composer backdrop renders the draft as an array of segments: plain strings, a leading claim-token mark, one element per structured reference, and one mark per plain-text reference range. React reconciles that array by key. + +Structured references carry an identity — the occurrence table mints an `occurrenceId` that survives every edit — so their chips key by it. Plain-text reference ranges have no such identity: `scanTextRefs` re-derives them from the draft on every render, and nothing outside that scan remembers a range between two keystrokes. + +Keying those ranges by their draft offset made the key change whenever earlier text changed length. React then treated the range as a different element, unmounted the mark with its nested spans and inline glyph, and mounted a replacement. Every character typed or deleted ahead of a reference rebuilt every reference after the caret, and the work grew with the reference count. [Directory-syntax ranges](../feature/2026-07-27-web-file-and-session-references.md) made that path routine: they match on `@path/` syntax without a lexicon, and each one renders an icon. + +## Decision + +A plain-text reference mark keys by its index in the offset-sorted `textRefs` list, computed where the boundary list is assembled so a skipped boundary cannot shift it. The scan already returns the ranges in draft order, so the ordinal names the render slot a range occupies, which is the only identity a scan-derived range has. + +Structured chips keep `occurrenceId`. The two key strategies differ because the two range kinds differ in identity, not by oversight: a range the occurrence table owns keeps its node across reordering, and a range only a scan knows keeps its node across offset shifts. + +A range that stops matching the scan still loses its decoration, because it disappears from `textRefs` and the ordinal it held no longer exists. + +## Testing + +A component test holds the mark element and its glyph, types a character ahead of the range, and asserts the same nodes are still mounted; it then edits the token out of match shape and asserts the decoration is gone. The test fails against an offset-derived key. + +## Alternatives considered + +**Key by the range text.** Rejected: duplicate references collide on one key, and editing inside a range changes its key, which reintroduces the remount this fixes. + +**Give scan-derived ranges an identity table.** Rejected: it adds mutable state whose only consumer is a render key, and the scan would have to diff against the previous draft to maintain it. An edit that breaks a match simply dropping the range on the next scan is what keeps `scanTextRefs` a pure derivation. + +**Drop the keys and let React match by position.** Rejected: React requires keys on elements inside an array, and the plain string segments between them already match by index, so an unkeyed element warns without changing the outcome. + +## Consequences + +Typing ahead of a reference updates text nodes only; the mark and its icon stay mounted. The backdrop's per-keystroke DOM work no longer scales with the number of references in the draft. + +Because the key names a position, inserting a reference ahead of existing ones reuses the earlier nodes with new content instead of re-creating them. That is correct for these marks, which hold no focus, selection, or animation state, and it is the condition any future decoration on this layer meets before it keys by ordinal. diff --git a/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.zh.md b/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.zh.md new file mode 100644 index 0000000000..950338bc5d --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-08-20-composer-reference-decoration-keys.zh.md @@ -0,0 +1,39 @@ +# Agent Note: 输入框引用装饰按草稿顺序序号取 key + +Status: implemented + +[English](2026-08-20-composer-reference-decoration-keys.md) | 中文 + +## 问题 + +输入框 backdrop 把草稿渲染成一组片段:纯文本字符串、开头的 claim token 标记、每个结构化引用一个元素、每个纯文本引用范围一个标记。React 按 key 协调这个数组。 + +结构化引用带有身份——occurrence 表铸造的 `occurrenceId` 在任何编辑后都保持不变——因此它们的 chip 用它作 key。纯文本引用范围没有这种身份:`scanTextRefs` 在每次渲染时从草稿重新推导它们,扫描之外没有任何东西在两次按键之间记住某个范围。 + +用草稿偏移量给这些范围取 key,会让前面文本长度一变 key 就变。React 于是把该范围当作另一个元素,卸载带嵌套 span 和内联图标的标记,再挂载一个替代品。在引用前面输入或删除任意字符,都会重建光标之后的每一个引用,工作量随引用数量增长。[目录语法范围](../feature/2026-07-27-web-file-and-session-references.md)让这条路径成为常态:它们按 `@path/` 语法匹配,不依赖 lexicon,而且每个都渲染一个图标。 + +## 决策 + +纯文本引用标记以它在按偏移排序的 `textRefs` 列表中的下标作 key,在组装 boundary 列表处计算,因此被跳过的 boundary 不会让它偏移。扫描本身已按草稿顺序返回范围,所以该序号命名的是范围占据的渲染槽位,而这正是扫描推导出的范围唯一拥有的身份。 + +结构化 chip 保留 `occurrenceId`。两种 key 策略不同,是因为两类范围的身份不同,而非疏漏:occurrence 表拥有的范围在重排后保住自己的节点,只有扫描知道的范围在偏移变化后保住自己的节点。 + +不再匹配扫描规则的范围仍然失去装饰,因为它从 `textRefs` 中消失,它占据的序号也不复存在。 + +## 测试 + +组件测试持有标记元素及其图标,在范围之前输入一个字符,断言仍是同一批节点;随后把 token 编辑成不再匹配的形态,断言装饰消失。该测试在偏移量 key 下失败。 + +## 备选方案 + +**按范围文本取 key。** 拒绝:重复引用会撞同一个 key,且在范围内部编辑会改变 key,重新引入本次修复消除的重挂载。 + +**为扫描推导的范围建立身份表。** 拒绝:这会引入唯一消费者是渲染 key 的可变状态,而且扫描必须与上一版草稿做 diff 才能维护它。破坏匹配的编辑在下一次扫描时直接丢掉该范围,正是这一点让 `scanTextRefs` 保持为纯推导。 + +**去掉 key,让 React 按位置匹配。** 拒绝:React 要求数组内的元素带 key,而它们之间的纯文本片段本就按下标匹配,因此无 key 元素只会告警,不改变结果。 + +## 后果 + +在引用之前输入只更新文本节点;标记及其图标保持挂载。backdrop 每次按键的 DOM 工作量不再随草稿中的引用数量增长。 + +由于 key 命名的是位置,在已有引用之前插入新引用会以新内容复用先前的节点,而不是重建它们。对这些不持有焦点、选择区或动画状态的标记而言这是正确的,这也是该图层上任何未来装饰按序号取 key 前需要满足的条件。 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml index 3cf7133534..e38566451c 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.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-06-sandbox.md -2026-07-06-sandbox.md: 62c46c99a2283b03cf75d8823783367dd6b3473a -2026-07-06-sandbox.zh.md: 82c2e7962800c007a207f0204bf47cef01f79a36 +2026-07-06-sandbox.md: 7c451d8fb2d59c20ad8170e8c74a93fad911573a +2026-07-06-sandbox.zh.md: 23ffdd62c2a8848cb687b4f2e63d05ca847445e8 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.md index 62c46c99a2..7c451d8fb2 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.md @@ -188,7 +188,7 @@ Costs and accepted limits: - **How is a BROKEN sandbox told apart from a failing command?** Any provider-argv spawn rejection proves the confined launch never started, but it identifies a broken runner only when the caller-owned workdir is usable and Node reports attributable `ENOENT` or `EACCES` for that argv[0]. A bare `syscall: 'spawn'` without an exact error path and all other rejections remain ordinary command-start errors. After a process starts, runner failure outranks denial only when one `runnerFailureRules` entry matches both its optional exit-code gate and a fatal stderr line after exact informational exclusions. Foreground failures throw structured `SANDBOX_UNAVAILABLE` with spawn or matched-line detail; an asynchronously rejected or settled background job stamps `sandbox.runnerFailed` and renders its own marker. A `SubprocessRuntime` that synchronously throws the same `ENOENT`/`EACCES` shape with the runner path makes background start throw the structured error; other synchronous errors propagate unchanged. A Landlock partial-enforcement notice plus an ordinary child failure remains a command result. - **What happens on a platform with no backend — Windows today?** `confine()` throws the fail-closed `SANDBOX_UNAVAILABLE` and the command never spawns; `win32` is a reserved EMPTY chain, pinned by test to fail closed identically until a Windows runner fills it (§ Deferred phases). - **`bwrap` is installed on my host but unusable (disabled unprivileged userns, an LSM denying `mount`) — what happens?** The chain probe is functional — it builds and enforces a real profile rather than checking `--version` — so a present-but-unusable `bwrap` fails its probe, selection falls to the packaged Landlock launcher, and the verdict is cached for the provider's lifetime. -- **Does the sandbox restrict network or process visibility?** No — `SandboxMode` claims FILE effects only; the bwrap profile deliberately does not unshare pid, and no backend claims network. Whether network restriction becomes its own knob is left open in § The seam. +- **Does the sandbox restrict network or process visibility?** `SandboxMode` claims FILE effects only, and no backend claims network. Process visibility is backend-specific: bwrap unshares PID and mounts matching procfs because host `/proc/` magic links otherwise bypass file confinement, while Landlock and Seatbelt leave process visibility unchanged ([decision](../bug-fix/2026-08-06-bwrap-private-pid-namespace.md)). Whether network restriction becomes its own knob is left open in § The seam. - **Which tools actually run confined?** OS subprocesses through `ctx.shell` — the bash tools, and hook commands transitively — plus the filesystem tools (`read`/`write`/`edit`) through the sandboxed `ctx.fs` provider (the [cross-family fs sandbox RFC](2026-07-14-cross-family-fs-sandbox.md)): bash confines via the OS runner, fs via an in-process path fence, both keying off the same `ctx.sandboxPolicy` mode. web/todo stay in-process and unfenced (web's only effect is network, outside the file-effect mode vocabulary). - **Does a granted escalation persist?** No. The grant is consumed by the exact foreground or background call that asked; every neighboring call keeps its own effective mode. A later background denial surfaces through `job_output` and may ground a new exact-command retry. - **When does a runtime mode switch take effect?** Once its session event commits, the next pre-step policy-context reconciliation and the next capability resolution fold the new mode. The sourced context message records what the model was told, and any later denial names the same policy at the point of use. diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md index 82c2e79628..23ffdd62c2 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md @@ -188,7 +188,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自能力边 - **如何区分损坏的沙箱与失败的命令?** 提供方 argv 的任何 spawn 拒绝都能证明受限启动从未开始,但只有在调用方拥有的 workdir 可用,且 Node 为该 argv[0] 报告可归因的 `ENOENT` 或 `EACCES` 时,才能据此判定 runner 损坏。没有精确错误路径的裸 `syscall: 'spawn'` 和其他所有拒绝仍是普通的命令启动错误。进程启动后,只有当 `runnerFailureRules` 中某一条目同时匹配其可选退出码门控,以及排除整行精确信息性行后的一行致命 stderr 诊断时,runner 失败才会优先于拒绝。前台失败会抛出结构化的 `SANDBOX_UNAVAILABLE`,并附带 spawn 错误或匹配行作为详细信息;遭异步拒绝或已结算的后台任务则盖章 `sandbox.runnerFailed` 并渲染自己的标记。如果 `SubprocessRuntime` 同步抛出同样带有 runner 路径的 `ENOENT`/`EACCES` 形态,后台启动会抛出该结构化错误;其他同步错误原样传播。Landlock 部分强制执行通知加上普通子进程失败时,仍返回命令结果。 - **在没有后端的平台上会发生什么——今天的 Windows?** `confine()` 抛出失败关闭的 `SANDBOX_UNAVAILABLE`,命令永不 spawn;`win32` 是保留的空链,由测试固定为同样失败关闭,直到 Windows runner 填充它(§ 延迟阶段)。 - **`bwrap` 已安装在我的主机上但不可用(禁用了非特权 userns、LSM 拒绝 `mount`)——会发生什么?** 链探测是功能性的——它构建并强制一个真实 profile 而非检查 `--version`——因此存在但不可用的 `bwrap` 探测失败,选择落到已打包的 Landlock launcher,结论在提供方生命周期内缓存。 -- **沙箱限制网络或进程可见性吗?** 不——`SandboxMode` 仅声称文件操作;bwrap profile 刻意不 unshare pid,没有后端声称网络。网络限制是否成为自己的旋钮留在 § seam 中开放。 +- **沙箱限制网络或进程可见性吗?** `SandboxMode` 只声称文件影响,并且没有后端声称限制网络。进程可见性因后端而异:bwrap 会取消共享 PID 命名空间,并挂载与其匹配的 procfs,因为宿主 `/proc/` 的魔法链接会绕过文件约束;Landlock 与 Seatbelt 则保持进程可见性不变(见[相关决策](../bug-fix/2026-08-06-bwrap-private-pid-namespace.md))。网络限制是否成为自己的旋钮留在 § seam 中开放。 - **哪些工具实际在约束下运行?** 通过 `ctx.shell` 的 OS 子进程——bash 工具及传递性的钩子命令——再加上通过沙箱化 `ctx.fs` 提供方运行的文件系统工具(`read`/`write`/`edit`,见[跨工具族 fs 沙箱 RFC](2026-07-14-cross-family-fs-sandbox.md)):bash 通过 OS runner 约束,fs 通过进程内路径围栏约束,二者都以同一个 `ctx.sandboxPolicy` 模式为键。web/todo 仍在进程内且不受限制(web 的唯一效果是网络,不在文件效果模式词汇内)。 - **授权的升级会持久化吗?** 不会。授权由发起请求的确切前台或后台调用消费;每个相邻调用保留自己的有效模式。后续的后台拒绝通过 `job_output` 呈现,并且可以作为一次新的精确命令重试的依据。 - **运行时模式切换何时生效?** 一旦其会话事件提交,下一次 pre-step 策略上下文协调与下一次能力解析都会折叠新模式。带来源的上下文消息会记录模型收到的内容,之后的任何拒绝都会在使用点命名同一策略。 diff --git a/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.i18n.yaml index b82a2e1fa9..a5e7814c64 100644 --- a/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.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-gui-full-access-confirmation.md -2026-07-31-gui-full-access-confirmation.md: f63502cd3e2306f36b136e6ed8543641449c3d83 -2026-07-31-gui-full-access-confirmation.zh.md: f4b3686d1e1ad9e51a08e513a7dd5930d311582d +2026-07-31-gui-full-access-confirmation.md: 5822ae26d36329b865a05c59dd672daa55fb6224 +2026-07-31-gui-full-access-confirmation.zh.md: e954e6f4ec1d5591a1d55dc1b98f2f0d01b7b242 diff --git a/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.md b/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.md index f63502cd3e..5822ae26d3 100644 --- a/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.md +++ b/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.md @@ -10,13 +10,13 @@ Switching the web client to `danger-full-access` was a single click on a permiss ## Decision -**Every permission picker gates `danger-full-access` behind the shared in-page `RiskConfirmation` dialog whose enabling action stays disabled until an explicit acknowledgement checkbox is checked; the preset renders under the product label `Full access`; every dismissal path submits nothing.** +**Every permission picker gates `danger-full-access` behind the shared in-page `RiskConfirmation` dialog whose enabling action stays disabled until an explicit acknowledgement checkbox is checked; the preset renders under the locale product label for full access; every dismissal path submits nothing.** - `RiskConfirmation` (ui-primitives) is a controlled Modal composition: title, description, acknowledgement checkbox, cancel, and a confirm button disabled until `acknowledged`. It stays an in-page dialog — the Modal portals to this document's body and never opens a native or separate browser window that could land on another display. `Modal` gains a `contentClassName` seat so the warning body scrolls inside constrained mobile/landscape viewports while the action row stays fixed. - The composer chip (`PermissionSelect`, ui-conversation) intercepts a Full-access pick before the `/permission` submit: `confirmation`/`acknowledged` component state opens the dialog, confirm submits `/permission danger-full-access` through the same injected `command` path as every other pick, and cancel/Escape/close/mask leave the current preset untouched with the checkbox reset. The confirmation revokes itself when the session locks (`locked`/value-absent effect) and resets across task switches (`key={sessionId}` remount). Copy rides the standard `conversation` locale seat as `access.confirm.*` keys. - The `/permission` popup (ui-permission over the ui-commands shell) gates through data, not a second dialog implementation: `SelectOption` grows an optional `confirmation` payload, the popup controller owns the `confirming`/`acknowledged` state transitions, and `PopupSelectView` swaps the picker card for the same `RiskConfirmation` while a gated option is pending. - The General-settings Permission row uses the same controlled `RiskConfirmation` before persisting Full access as the default for later sessions. Its warning names that future-session lifetime; cancel, Escape, close, and mask dismissal leave the stored default untouched. -- `Full access` intentionally overrides the kebab-to-title display transform in every picker; command and Settings writes keep the machine name on the wire, and each warning body remains locale-aware in Chinese and English. +- The full-access product label overrides the kebab-to-title display transform when the host keeps the canonical built-in name; an explicit host label wins. Command and Settings writes keep the machine name on the wire, and each warning body remains locale-aware in Chinese and English. The same conditional localization applies to the safer shipped presets ([blank permission default refresh](../bug-fix/2026-08-17-blank-permission-default-refresh.md)). ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.zh.md b/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.zh.md index f4b3686d1e..e954e6f4ec 100644 --- a/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-gui-full-access-confirmation.zh.md @@ -10,13 +10,13 @@ Status: implemented ## 决策 -**每个权限选择器都把 `danger-full-access` 关进共享的页面内 `RiskConfirmation` 对话框:启用按钮在用户勾选明确的风险确认复选框前保持禁用;预设以产品标签 `Full access` 展示;所有取消路径都不作任何提交。** +**每个权限选择器都把 `danger-full-access` 关进共享的页面内 `RiskConfirmation` 对话框:启用按钮在用户勾选明确的风险确认复选框前保持禁用;预设以完全权限的本地化产品标签展示;所有取消路径都不作任何提交。** - `RiskConfirmation`(ui-primitives)是受控的 Modal 组合:标题、说明、确认复选框、取消,以及 `acknowledged` 勾选前禁用的确认按钮。它始终是页面内对话框——Modal portal 到本文档 body,绝不打开可能落在另一块显示器上的原生或独立浏览器窗口。`Modal` 新增 `contentClassName` slot,令警示正文在受限的移动端/横屏视口内滚动,动作行保持固定。 - composer chip(ui-conversation 的 `PermissionSelect`)在 `/permission` 提交前拦截 Full-access 选择:`confirmation`/`acknowledged` 组件状态打开对话框,确认后经与其他选择完全相同的注入 `command` 通道提交 `/permission danger-full-access`;取消、Escape、关闭与遮罩点击均保持当前预设不变并重置复选框。会话锁定时确认自行撤销(`locked`/值缺席 effect),切换任务时随 `key={sessionId}` 重挂载而重置。文案经标准 `conversation` locale slot 以 `access.confirm.*` 键供给。 - `/permission` popup(ui-permission 构建于 ui-commands 外壳之上)以数据而非第二套对话框实现完成把关:`SelectOption` 新增可选的 `confirmation` 载荷,popup 控制器拥有 `confirming`/`acknowledged` 状态迁移,`PopupSelectView` 在门控选项未决期间把选择卡换成同一个 `RiskConfirmation`。 - 「通用」设置中的「权限」行在把 Full access 持久化为后续会话的默认值前,也使用同一个受控 `RiskConfirmation`。警示会明确说明该设置只影响后续会话;取消、Escape、关闭与点击遮罩均不会改动已存默认值。 -- `Full access` 在每个选择器中都有意覆盖 kebab 转 Title Case 的显示变换;命令与 Settings 写入在 wire 上保留机器名,每份警示正文都保持中英文 locale 感知。 +- 当 host 保留内置预设的规范名称时,完全权限产品标签会覆盖 kebab 转 Title Case 的显示变换;显式 host 标签优先。命令与 Settings 写入在 wire 上保留机器名,每份警示正文都保持中英文 locale 感知。同一个条件式本地化规则也适用于更安全的随附预设([空白权限默认值刷新](../bug-fix/2026-08-17-blank-permission-default-refresh.md))。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml index c28df0e4cf..9276d1df2d 100644 --- a/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.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-permission-default-for-new-sessions.md -2026-07-31-permission-default-for-new-sessions.md: ebf7fe39712d64c18e12b9b26d86201a61ad6cfd -2026-07-31-permission-default-for-new-sessions.zh.md: c56a1b4ac3a6bc88a489dd7e945fa1c11581e7a6 +2026-07-31-permission-default-for-new-sessions.md: 818c105ce880f7b67bfd28030502bf6775e1a039 +2026-07-31-permission-default-for-new-sessions.zh.md: a443190c2e53b6195edc44e98085b276eb9cd8b4 diff --git a/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.md b/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.md index ebf7fe3971..818c105ce8 100644 --- a/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.md +++ b/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.md @@ -12,7 +12,7 @@ The Web General-settings page displayed Permission as a disabled skeleton even t `dsh-permission-presets` owns a `permission` Settings namespace with one `defaultPreset` field. Its base value is `Config.defaultPreset`, or the preset matching the composed sandbox and approval defaults when the config omits it. The schema derives its enum from the configured preset table, so Settings validates stored values and the Web client discovers the deployment's actual choices without duplicating them. -The service reads the current Settings value synchronously at `session/created`. A genuinely fresh session receives three explicit events: `permission/preset`, `sandbox/mode`, and `approval/policy`. Those facts pin the permission selected at creation, so a later Settings change affects only later sessions. A seeded or partially initialized session preserves its effective knobs and receives only missing facts; it never adopts the latest user default while resuming. `Session` marks even an explicitly empty constructor seed with `session/end-seed`, so an empty persisted log cannot be mistaken for a fresh session. +The service reads the current Settings value synchronously at `session/created`. A genuinely fresh session receives three explicit events: `permission/preset`, `sandbox/mode`, and `approval/policy`; the preset fact records that the selection came from the default. Those facts pin the permission selected at creation, so a later Settings change does not change started conversations. When Web later confirms a Workspace blank for New Session reuse, the host explicitly adopts that live or cold session and advances it only if its latest selection remains default-origin and effective; this refinement is recorded in [blank permission default refresh](../bug-fix/2026-08-17-blank-permission-default-refresh.md). An ordinary seeded or partially initialized session preserves its effective knobs and receives only missing facts; it never adopts the latest user default merely by resuming. `Session` marks even an explicitly empty constructor seed with `session/end-seed`, so an empty persisted log cannot be mistaken for a fresh session. The existing `/permission` command and `permissions` projection remain the current-session path. The browser plugin now contributes the Permission row to `settings.general.item`, reads the dynamic enum from the redacted Settings descriptor, and writes only `defaultPreset` through a revision-checked `settings.mutate`. The row injects its observable through the slot `hooks` compartment instead of binding a renderer-specific hook, and the Permission service sweeps already-live sessions when it mounts so HMR cannot leave an unpinned session. The ownerless General-settings package contributes no placeholder rows. @@ -20,7 +20,7 @@ ApiProxy explicitly adds `permission` to its Web settings allowlist beside the c ## Consequences -Changing Permission in Settings updates `settings.yaml` and the selector immediately, but does not alter the open session. Every later session is reconstructable from its three pinned permission facts, including after the user changes the default again or the process restarts. Deployments whose composed sandbox and approval defaults match no preset must configure `defaultPreset` explicitly. +Changing Permission in Settings updates `settings.yaml` and the selector immediately, but does not alter an existing session by itself. Every later session is reconstructable from its three pinned permission facts, including after the user changes the default again or the process restarts. A Workspace blank may receive a new pinned triplet only when Web confirms it as the reuse target and its effective selection is still default-origin. Deployments whose composed sandbox and approval defaults match no preset must configure `defaultPreset` explicitly. The assembled Web snapshot contains a functional Permission selector. Its keyless browser scenario writes `read-only`, verifies an existing `workspace-write` session is unchanged, and verifies a subsequently created session starts with the read-only event triplet. diff --git a/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.zh.md b/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.zh.md index c56a1b4ac3..a443190c2e 100644 --- a/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.zh.md @@ -12,7 +12,7 @@ Web「通用」设置页将「权限」显示为禁用的骨架控件,尽管 ` `dsh-permission-presets` 拥有一个 `permission` Settings namespace,其中只有 `defaultPreset` 字段。它的基础值是 `Config.defaultPreset`;省略该配置时,则使用与组合后的沙箱和审批默认值匹配的 preset。schema 的 enum 从已配置的 preset 表派生,因此 Settings 既能校验已存储的值,Web 客户端也能发现部署中的实际选项,而无需重复定义。 -服务会在 `session/created` 时同步读取当前 Settings 值。真正的新会话会收到三个显式事件:`permission/preset`、`sandbox/mode` 和 `approval/policy`。这些事实将创建时选中的权限固定下来,因此后续 Settings 变更只影响之后的会话。带 seed 或只完成部分初始化的会话会保留其有效调节项,只补齐缺失的事实;恢复时绝不会采用最新的用户默认值。`Session` 甚至会用 `session/end-seed` 标记显式为空的构造器 seed,因此不能把空的持久化日志误认为新会话。 +服务会在 `session/created` 时同步读取当前 Settings 值。真正的新会话会收到三个显式事件:`permission/preset`、`sandbox/mode` 和 `approval/policy`;preset 事实会记录该选择来自默认值。这些事实将创建时选中的权限固定下来,因此后续 Settings 变更不会改变已经开始的对话。Web 之后把某个 Workspace 空白会话确认为新会话复用目标时,host 会显式接纳这个 live 或冷会话,并且只在其最近选择仍来自默认值且继续有效时推进;这项细化记录在[空白权限默认值刷新](../bug-fix/2026-08-17-blank-permission-default-refresh.md)。普通的带 seed 或只完成部分初始化的会话会保留其有效调节项,只补齐缺失的事实;仅仅恢复会话时绝不会采用最新的用户默认值。`Session` 甚至会用 `session/end-seed` 标记显式为空的构造器 seed,因此不能把空的持久化日志误认为新会话。 现有 `/permission` 命令和 `permissions` 投影仍是当前会话的操作路径。浏览器插件现在向 `settings.general.item` 贡献「权限」行,从脱敏后的 Settings 描述符读取动态 enum,并只通过经过 revision 校验的 `settings.mutate` 写入 `defaultPreset`。该行通过 slot 的 `hooks` 格注入 observable,而不是绑定渲染器专用钩子;权限服务挂载时会遍历并固定所有已存活会话,因此 HMR(热模块替换)不会遗留未固定的会话。无归属的「通用」设置包不贡献任何占位行。 @@ -20,7 +20,7 @@ ApiProxy 在可配置提供方 namespace 之外,将 `permission` 显式加入 ## 后果 -在 Settings 中更改「权限」会立即更新 `settings.yaml` 和选择器,但不会改变已打开的会话。之后的每个会话都可以从三个已固定的权限事实中重建,即使用户再次更改默认值或进程重启也不受影响。如果部署中组合后的沙箱和审批默认值与任何 preset 都不匹配,则必须显式配置 `defaultPreset`。 +在 Settings 中更改「权限」会立即更新 `settings.yaml` 和选择器,但单凭该操作不会改变既有会话。之后的每个会话都可以从三个已固定的权限事实中重建,即使用户再次更改默认值或进程重启也不受影响。只有当 Web 把某个 Workspace 空白会话确认为复用目标,并且其有效选择仍来自默认值时,该会话才可能收到新的固定三元组。如果部署中组合后的沙箱和审批默认值与任何 preset 都不匹配,则必须显式配置 `defaultPreset`。 组装后的 Web 快照包含功能完整的「权限」选择器。其无密钥浏览器场景会写入 `read-only`,验证现有的 `workspace-write` 会话保持不变,并验证随后创建的会话以 read-only 事件三元组启动。 diff --git a/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.i18n.yaml index 1ba8316340..0f031da815 100644 --- a/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.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-workspace-write-surface-default.md -2026-07-31-workspace-write-surface-default.md: 2ce4d7b45ad7058768f412302eb86ac332cabfe9 -2026-07-31-workspace-write-surface-default.zh.md: 40b97812eb17b5c11e422ff23cd718576c9c4364 +2026-07-31-workspace-write-surface-default.md: e096028e07f1e7905fef568c435ad7f3d1d19138 +2026-07-31-workspace-write-surface-default.zh.md: f1794ca6a1850441acb0c45274ee94b6dd7ae919 diff --git a/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.md b/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.md index 2ce4d7b45a..e096028e07 100644 --- a/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.md +++ b/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.md @@ -12,7 +12,7 @@ The shipped terminal and browser surfaces exposed the same coding tools under di [`base.cordis.yml`](../../../../packages/bundle/base/cordis.patch.yml) owns one sandbox and permission stack for every shipped TUI, Web, and browser-backed headless session: `dsh-sandbox-local`, `dsh-sandbox-policy`, `dsh-bash-sandbox`, `dsh-fs-sandbox`, `dsh-user-approval`, and `dsh-permission-presets`. The composition fallback is the `workspace-write` preset, which bundles `workspace-write` file effects with the `ask` approval policy. `DSH_PERMISSION_MODE` remains an explicit process override; a stored `permission.defaultPreset` remains the user preference for later sessions and outranks the fallback through the Settings seam. -A genuinely fresh session pins `permission/preset: workspace-write`, `sandbox/mode: workspace-write`, and `approval/policy: ask` before execution. Existing and resumed sessions retain their logged permission, and changing the General-settings default affects only sessions created afterward. The browser keeps its Access picker, answerable approval cards, and risk confirmation for Full access. The TUI gains the existing `/permission` command because the shared Permission service activates its command child there. +A genuinely fresh session pins `permission/preset: workspace-write`, `sandbox/mode: workspace-write`, and `approval/policy: ask` before execution. Changing the General-settings default affects fresh sessions, while started and ordinary resumed sessions retain their logged permission. A later Web New Session action may advance a default-origin Workspace blank after confirming and adopting that exact reuse target, as recorded in [blank permission default refresh](../bug-fix/2026-08-17-blank-permission-default-refresh.md). The browser keeps its Access picker, answerable approval cards, and risk confirmation for Full access. The TUI gains the existing `/permission` command because the shared Permission service activates its command child there. The mode governs file effects only. Sandboxed bash and filesystem mutations admit the session workspace and platform temporary roots; reads, network access, and process visibility remain outside this policy. If no platform runner can enforce a confined bash call, execution fails closed instead of falling through to an unrestricted command. @@ -30,6 +30,6 @@ The keyless shipped-TUI pseudo-terminal smoke boots the real Loader tree, reads ## Consequences -Fresh sessions can modify the active workspace and temporary roots without extra prompts, while an attempted mutation elsewhere is denied before it reaches the target. Full access remains available by explicit selection, and browser selection retains its acknowledgement dialog. Stored user defaults and logged session permissions are not rewritten. +Fresh sessions can modify the active workspace and temporary roots without extra prompts, while an attempted mutation elsewhere is denied before it reaches the target. Full access remains available by explicit selection, and browser selection retains its acknowledgement dialog. A Settings write does not rewrite logged session permissions; confirmed reuse may append a new default-origin permission triplet to the selected blank Workspace session. The browser-backed headless entry inherits the Web composition and therefore the same default. The TUI's missing approval answerer is a deliberate limitation of this change: automatic wider retries fail closed there instead of displaying a permission question. diff --git a/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.zh.md b/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.zh.md index 40b97812eb..f1794ca6a1 100644 --- a/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-workspace-write-surface-default.zh.md @@ -12,7 +12,7 @@ Status: implemented [`base.cordis.yml`](../../../../packages/bundle/base/cordis.patch.yml) 为所有已交付的 TUI、Web 以及由浏览器支撑的无头会话统一持有一套沙箱与权限栈:`dsh-sandbox-local`、`dsh-sandbox-policy`、`dsh-bash-sandbox`、`dsh-fs-sandbox`、`dsh-user-approval` 和 `dsh-permission-presets`。组合回退值为 `workspace-write` preset,其中包含 `workspace-write` 文件效果模式与 `ask` 审批策略。`DSH_PERMISSION_MODE` 仍是显式的进程级覆盖;已存储的 `permission.defaultPreset` 仍是面向后续会话的用户偏好,并通过 Settings seam 优先于该回退值。 -真正的新会话会在执行前固定 `permission/preset: workspace-write`、`sandbox/mode: workspace-write` 和 `approval/policy: ask`。现有会话和恢复的会话保留日志中记录的权限,更改「通用」设置中的默认值只影响之后创建的会话。浏览器保留 Access 选择器、可应答的审批卡片,以及选择 Full access 时的风险确认。共享 Permission 服务在 TUI 中激活其命令子件,因此 TUI 会获得现有的 `/permission` 命令。 +真正的新会话会在执行前固定 `permission/preset: workspace-write`、`sandbox/mode: workspace-write` 和 `approval/policy: ask`。更改「通用」设置中的默认值会影响新建会话,而已经开始的会话和普通恢复会话保留日志中记录的权限。Web 之后执行新会话操作时,可以在确认并接纳确切复用目标后推进权限来自默认值的 Workspace 空白会话;该规则记录在[空白权限默认值刷新](../bug-fix/2026-08-17-blank-permission-default-refresh.md)。浏览器保留 Access 选择器、可应答的审批卡片,以及选择 Full access 时的风险确认。共享 Permission 服务在 TUI 中激活其命令子件,因此 TUI 会获得现有的 `/permission` 命令。 该模式只管辖文件效果。受沙箱约束的 bash 与文件系统修改只允许写入会话工作区和平台临时根目录;读取、网络访问与进程可见性仍不受该策略约束。若没有平台 runner 能强制执行受限的 bash 调用,执行会以拒绝告终,不会退回不受限命令。 @@ -30,6 +30,6 @@ Status: implemented ## 后果 -全新的会话无需额外提示即可修改当前工作区与临时根目录,尝试修改其他位置则会在触及目标前被拒绝。Full access 仍可通过显式选择获得,浏览器选择时也仍会显示确认对话框。系统不会重写已存储的用户默认值和会话日志中记录的权限。 +全新的会话无需额外提示即可修改当前工作区与临时根目录,尝试修改其他位置则会在触及目标前被拒绝。Full access 仍可通过显式选择获得,浏览器选择时也仍会显示确认对话框。Settings 写入不会改写会话日志中的权限;复用确认可以向选中的空白 Workspace 会话追加新的默认来源权限三元组。 由浏览器支撑的无头入口继承 Web 组合,因此默认值相同。TUI 缺少审批应答者是本次变更的明确限制:自动请求更宽权限的重试会在那里以拒绝告终,而不会显示权限询问。 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml index f5ec3193a1..432b45f329 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml @@ -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-05-agent-teams.md -2026-08-05-agent-teams.md: 43e92d4a9807e6a178436d96722b141b1bac45bb -2026-08-05-agent-teams.zh.md: 72989623bb49334beae3b49ce2f6973781558c69 +2026-08-05-agent-teams.md: d37777a0cca467edcec5d38999aee53aaf14dc36 +2026-08-05-agent-teams.zh.md: 78bb212d687f89da0911983ea9d7fa5e5bbe95a7 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md index 43e92d4a98..d37777a0cc 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md @@ -16,7 +16,7 @@ The model-visible Team tools remain opt-in so the default tool catalog and simpl Every ordinary runtime root is the implicit Lead of a Team identified by that root's `SessionId`. The Team has no creation event: its Lead pseudo-row exists by identity, while durable state begins with the first member, message, or task event. A roster is flat and contains at most the configured number of immutable lowercase-kebab-case names. Each teammate is a continuable direct child with a reserved Session id; only the Lead creates or interrupts teammates. Ordinary provider-owned subagents outside the roster are not Team members, and an ordinary fork is a new root whose inherited Team records are excluded by their ancestor `TeamId`. -The implementation is split into `@deepseek-ai/dsh-team`, which owns `ctx.teams` and durable semantics, and `@deepseek-ai/dsh-tool-team`, which owns scoped schemas and model guidance. Every Team tool declares its complete result schema and renders that value as compact JSON, so the compiler checks each `execute` against what the model is promised and no result spends tokens on indentation. Deployments mount both plugins explicitly and may disable legacy continuable controls with the same model-visible names. The explicit delegation policy permits Team creation only when the user asks for Agent Teams or teammates. Both packages are private members of `packages/experimental/`; the [experimental package decision](../architecture/2026-08-18-experimental-agent-teams-packages.md) owns release exclusion, dependency isolation, and promotion. +The implementation is split into `@deepseek-ai/dsh-experimental-agent-team`, which owns `ctx.agentTeams` and durable semantics, and `@deepseek-ai/dsh-experimental-tool-agent-team`, which owns scoped schemas and model guidance. Every Team tool declares its complete result schema and renders that value as compact JSON, so the compiler checks each `execute` against what the model is promised and no result spends tokens on indentation. Deployments mount both plugins explicitly and may disable legacy continuable controls with the same model-visible names. The explicit delegation policy permits Team creation only when the user asks for Agent Teams or teammates. Both packages are private members of `packages/experimental/`; the [experimental package decision](../architecture/2026-08-18-experimental-agent-teams-packages.md) owns release exclusion, dependency isolation, and promotion. The Lead must wait for required work before its final answer. Process teardown remains the final lifecycle owner and drains continuation Activations; a Team task owner is durable state and is not automatically released by idle, interruption, or process exit. diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md index 72989623bb..78bb212d68 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md @@ -16,7 +16,7 @@ subagent seam 已提供 fresh/fork provider、持久 child Session、FIFO foll 每个普通运行时 Root 都是一个隐式 Team 的 Lead,Team id 等于该 Root 的 `SessionId`。Team 没有 creation event:Lead pseudo-row 由身份直接存在,持久状态从第一条 member、message 或 task event 开始。roster 是扁平结构,最多包含配置数量、不可变且采用小写 kebab-case 的名字。每个 teammate 都是使用预留 Session id 的 continuable 直接 child;只有 Lead 可以创建或 interrupt teammate。roster 外由 provider 管理的普通 subagent 不是 Team member;普通 fork 是新的 Root,继承的 Team 记录会因 ancestor `TeamId` 被排除。 -实现拆分为 `@deepseek-ai/dsh-team` 与 `@deepseek-ai/dsh-tool-team`:前者负责 `ctx.teams` 和持久语义,后者负责 scoped schema 与模型指引。每个 Team 工具都声明完整的结果 schema,并把该值渲染为紧凑 JSON,因此编译器会检查每个 `execute` 是否符合对模型的承诺,也没有结果把 token 花在缩进上。部署显式挂载两个插件,并可禁用具有相同模型可见名称的旧 continuable control。显式 delegation 策略只允许在用户要求 Agent Teams 或 teammate 时创建 Team。 两个包都是 `packages/experimental/` 的私有成员;[实验性包决策](../architecture/2026-08-18-experimental-agent-teams-packages.md)负责发布排除、依赖隔离与 promotion。 +实现拆分为 `@deepseek-ai/dsh-experimental-agent-team` 与 `@deepseek-ai/dsh-experimental-tool-agent-team`:前者负责 `ctx.agentTeams` 和持久语义,后者负责 scoped schema 与模型指引。每个 Team 工具都声明完整的结果 schema,并把该值渲染为紧凑 JSON,因此编译器会检查每个 `execute` 是否符合对模型的承诺,也没有结果把 token 花在缩进上。部署显式挂载两个插件,并可禁用具有相同模型可见名称的旧 continuable control。显式 delegation 策略只允许在用户要求 Agent Teams 或 teammate 时创建 Team。 两个包都是 `packages/experimental/` 的私有成员;[实验性包决策](../architecture/2026-08-18-experimental-agent-teams-packages.md)负责发布排除、依赖隔离与 promotion。 Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍是最终生命周期 owner,并会 drain continuation Activation;Team task owner 是持久状态,不会因 idle、interrupt 或进程退出自动释放。 diff --git a/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.i18n.yaml b/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.i18n.yaml index 1020c5a243..3a2a837ce9 100644 --- a/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.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-11-workspace-sidebar-order-and-folding.md -2026-08-11-workspace-sidebar-order-and-folding.md: d683d782454bb9fe1fad1fdc1d1a5fc3184a697b -2026-08-11-workspace-sidebar-order-and-folding.zh.md: 99e1991cfbb7b1540235ab0190defb31ad0ed6d7 +2026-08-11-workspace-sidebar-order-and-folding.md: ad079cfc71d6efff7679ce3b8512167bce95e6c8 +2026-08-11-workspace-sidebar-order-and-folding.zh.md: fdc23fbc3a745b030c296d91257adb505b282471 diff --git a/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.md b/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.md index d683d78245..ad079cfc71 100644 --- a/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.md +++ b/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.md @@ -24,6 +24,8 @@ Each Workspace persists one browser-local open state: closed means zero Session The combined view menu offers **Manual** and **Last updated** in grouped and flat presentation, with one browser-local persisted order per account. A real Workspace initializes from `WorkspaceView.sessionIds`; Ungrouped and the cross-Workspace flat list initialize from recency and have no Host Session account. Entering Last updated performs one complete recency sort; a later user prompt or steer promotes that Session once, and dragging may edit the resulting order. Returning to Manual preserves the current order and only disables later activity promotion. Manual-mode drags for a real Workspace also write the Host Session account, while Ungrouped and flat-list drags and activity promotion remain browser-local. Flat rows omit an empty leading status slot because they have no parent hierarchy, while a visible status retains its slot. +When New Session creation selects a blank Session, the browser promotes it once in both its grouped account and the flat-list account. This explicit creation promotion does not advance `updatedAt`; later drag ordering treats the blank like any other Session, and the first prompt does not undo a Manual-mode drag. + ### Drag and compact chrome Workspace hit testing uses the complete rendered group section, including visible Session rows. One insertion boundary is shared by the preceding group's lower half and the following group's upper half, and the indicator is an absolutely positioned line with a joined right-facing chevron that does not affect layout. A tree-body overlay draws the first boundary at the same negative offset outside the scrolling clip, so the leading chevron remains visible without moving the list. During a Workspace or Session drag, document-level `dragover` and `drop` handlers accept the native operation; if release occurs outside the Workspace list, `dragend` commits the last valid marker. @@ -48,9 +50,10 @@ Search is a header action while collapsed and expands across the title and trail - Workspace order is durable and shared through the Host, while grouping, open state, per-account Session view order, and query state remain browser-local presentation preferences. Ungrouped and the flat list support the same drag and promotion rules, but their orders are browser-local because neither has one Workspace account. - Last updated performs a complete recency sort on entry, then preserves manual adjustments until a user prompt or steer advances one Session and moves it to the front. Returning to Manual preserves every current position. +- A newly selected blank New Session row enters grouped and flat orders first once, then follows the same drag and activity rules as every other Session. - Opening a Workspace never shows more than five Sessions without an explicit **Show more** gesture, and closing it resets only that transient gesture. - The Host Session account retains the manual-order meaning established by [Session List Browsing and Manual Workspace Order](2026-07-25-session-list-browsing-and-manual-order.md). ## Testing -Domain and Host tests cover durable Workspace moves, no-op and invalid anchors, restart recovery, full-order RPC responses, order frames, and one Workspace snapshot per Host-stream baseline. Runtime tests cover optimistic order, frame/response precedence, overlapping rejection rollback to Host-confirmed order, reconnect baselines, and New Session target priority. UI tests cover five-row folding, transient expansion reset, pruning persisted state after Workspace removal, order-preserving mode switches, one-time recent-update promotion, browser-local Ungrouped and flat-list drag persistence, hierarchy-free flat-row leading spacing, selected view indicators, expanded-section Workspace hit testing, an unclipped first insertion boundary, outside-list Workspace and Session drops, search collapse rules, and compact CSS dimensions. +Domain and Host tests cover durable Workspace moves, no-op and invalid anchors, restart recovery, full-order RPC responses, order frames, and one Workspace snapshot per Host-stream baseline. Runtime tests cover optimistic order, frame/response precedence, overlapping rejection rollback to Host-confirmed order, reconnect baselines, and New Session target priority. UI tests cover five-row folding, transient expansion reset, pruning persisted state after Workspace removal, order-preserving mode switches, one-time recent-update and New Session promotion, Manual drag retention after the first prompt, browser-local Ungrouped and flat-list drag persistence, hierarchy-free flat-row leading spacing, selected view indicators, expanded-section Workspace hit testing, an unclipped first insertion boundary, outside-list Workspace and Session drops, search collapse rules, and compact CSS dimensions. diff --git a/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.zh.md b/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.zh.md index 99e1991cfb..fdc23fbc3a 100644 --- a/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.zh.md +++ b/.agents/notes/implemented/feature/2026-08-11-workspace-sidebar-order-and-folding.zh.md @@ -24,6 +24,8 @@ Workspace 注册表持有持久 `workspaceIds` 顺序,并提供采用 DOM `ins 组合视图菜单在分组和单列表呈现中都提供**手动排序**和**最近更新**,每个记账各自持有一份浏览器本地持久顺序。真实 Workspace 从 `WorkspaceView.sessionIds` 初始化;Ungrouped 和跨 Workspace 的单列表从最近更新时间顺序初始化,且没有 Host Session 记账。进入最近更新时会执行一次完整的时间排序;后续 user prompt 或 steer 会将对应 Session 置顶一次,拖拽仍可编辑所得顺序。返回手动排序会保留当前顺序,只停用后续活动置顶。真实 Workspace 在手动模式下的拖拽还会写入 Host Session 记账,而 Ungrouped 和单列表的拖拽与活动置顶保留在浏览器本地。单列表没有父级层次,因此不显示空的左侧状态槽;存在可见状态时仍保留该槽。 +创建“新会话”并选中空白 Session 时,浏览器会在其分组记账和单列表记账中各置顶一次。这次明确的创建置顶不会推进 `updatedAt`;后续拖拽把空白 Session 当作普通 Session,首条提示词落地也不会撤销手动模式下的拖拽。 + ### 拖拽与紧凑界面 Workspace 命中测试使用完整渲染分组区段,包括可见 Session 行。前一分组的下半部与后一分组的上半部共享同一条插入边界,指示器是一条带有相连右向尖角且不影响布局的绝对定位横线。树主体覆盖层会在滚动裁切区外以相同的负偏移绘制第一条边界,因此左侧尖角保持可见,列表位置也不会改变。Workspace 或 Session 拖拽期间,文档级 `dragover` 与 `drop` 处理器会接受原生操作;若在 Workspace 列表外松手,`dragend` 会提交最后一个有效标记。 @@ -48,9 +50,10 @@ Workspace 命中测试使用完整渲染分组区段,包括可见 Session 行 - Workspace 顺序通过 Host 持久并共享;分组方式、打开状态、每个记账的 Session 视图顺序和查询状态仍是浏览器本地呈现偏好。Ungrouped 和单列表支持相同的拖拽与置顶规则,但因没有单一 Workspace 记账,其顺序只保存在浏览器本地。 - 最近更新模式会在进入时执行完整时间排序,随后保持手动调整,直到 user prompt 或 steer 推进某条 Session 并将其置顶。返回手动排序会保留所有当前位置。 +- 新选中的空白“新会话”行会在分组和单列表顺序中各置顶一次,之后遵循与其他 Session 相同的拖拽和活动规则。 - 未执行明确的**展开其余**手势时,打开 Workspace 最多显示五条 Session;关闭分组只重置这项临时手势。 - Host Session 记账继续采用[会话列表浏览与 Workspace 手动排序](2026-07-25-session-list-browsing-and-manual-order.md)确立的手动顺序含义。 ## 测试 -领域与 Host 测试覆盖持久 Workspace 移动、无操作与无效锚点、重启恢复、完整顺序 RPC 响应、顺序帧以及每条 Host stream 基线只读取一份 Workspace 快照。运行时测试覆盖乐观顺序、帧/响应优先级、重叠拒绝后恢复 Host 已确认顺序、重连基线以及 New Session 目标优先级。UI 测试覆盖五行折叠、临时展开重置、Workspace 移除后清理持久状态、保持顺序的模式切换、一次性最近更新置顶、浏览器本地 Ungrouped 与单列表拖拽持久化、无层级单列表行左侧间距、当前视图标记、展开区段的 Workspace 命中、未裁切的第一条插入边界、列表外 Workspace 与 Session 松手、搜索收起规则和紧凑 CSS 尺寸。 +领域与 Host 测试覆盖持久 Workspace 移动、无操作与无效锚点、重启恢复、完整顺序 RPC 响应、顺序帧以及每条 Host stream 基线只读取一份 Workspace 快照。运行时测试覆盖乐观顺序、帧/响应优先级、重叠拒绝后恢复 Host 已确认顺序、重连基线以及 New Session 目标优先级。UI 测试覆盖五行折叠、临时展开重置、Workspace 移除后清理持久状态、保持顺序的模式切换、一次性最近更新与“新会话”置顶、首条提示词落地后保留手动拖拽、浏览器本地 Ungrouped 与单列表拖拽持久化、无层级单列表行左侧间距、当前视图标记、展开区段的 Workspace 命中、未裁切的第一条插入边界、列表外 Workspace 与 Session 松手、搜索收起规则和紧凑 CSS 尺寸。 diff --git a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml index f0213f0864..de2f71a53b 100644 --- a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.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-21-serial-cross-platform-ci-reference.md -2026-07-21-serial-cross-platform-ci-reference.md: d1ab9590df1252c9c91e7ec53dc1559e221d8f68 -2026-07-21-serial-cross-platform-ci-reference.zh.md: c9ffbac42858a19cca7c5fef6fd8f583030c195d +2026-07-21-serial-cross-platform-ci-reference.md: bb2437139a3eb746ca9eb7752979cd0a6fa947d3 +2026-07-21-serial-cross-platform-ci-reference.zh.md: fd3f762c98fe58d184844297da2fc92a84489501 diff --git a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md index d1ab9590df..bb2437139a 100644 --- a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md +++ b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md @@ -16,7 +16,7 @@ Real-kernel sandbox proofs require specific hosted operating systems and archite ## Decision -[CI](../../../../.github/workflows/ci.yml) gives pull-request and master-push events complementary responsibilities. Pull requests run consolidated Linux and Wine-hosted Windows jobs plus the Node compatibility and Python contracts on standard GitHub-hosted capacity; an independent native Windows job reports the complete Windows inventory without participating in the required aggregate. On a push to `master`, the active references are `serial / linux (self-hosted standby)` on the in-house `vm-backup` pool and `serial / windows (self-hosted standby)` on the in-house `dsh-win-ci` pool — the hot-standby drills that continuously re-prove the failover targets described in the [failover runbook](2026-07-26-ci-failover-runbook.md). There is no standard-hosted `serial / linux` definition; the standard-hosted `serial / macos` remains disabled under `TODO(hosted-serial-ci)` until its portable capacity can be restored. The current `serial / windows` definition is the in-house `dsh-win-ci` standby. The separate job definitions intentionally keep their short checkout, runtime setup, and immutable install sequences visible instead of hiding operating systems behind a matrix or reusable workflow. `workflow_dispatch` is reserved for runner benchmarks. +[CI](../../../../.github/workflows/ci.yml) (pull-request-only) and [CI master](../../../../.github/workflows/ci-master.yml) (master-push + workflow_dispatch) give pull-request and master-push events complementary responsibilities. Pull requests run consolidated Linux and Wine-hosted Windows jobs plus the Node compatibility and Python contracts on standard GitHub-hosted capacity; an independent native Windows job reports the complete Windows inventory without participating in the required aggregate. On a push to `master`, the active references are `serial / linux (self-hosted standby)` on the in-house `vm-backup` pool and `serial / windows (self-hosted standby)` on the in-house `dsh-win-ci` pool — the hot-standby drills that continuously re-prove the failover targets described in the [failover runbook](2026-07-26-ci-failover-runbook.md). There is no standard-hosted `serial / linux` definition; the standard-hosted `serial / macos` remains disabled under `TODO(hosted-serial-ci)` until its portable capacity can be restored. The current `serial / windows` definition is the in-house `dsh-win-ci` standby. The separate job definitions intentionally keep their short checkout, runtime setup, and immutable install sequences visible instead of hiding operating systems behind a matrix or reusable workflow. `workflow_dispatch` is reserved for runner benchmarks. Each reference job runs `pnpm run check:ci` without any shard selector. `DSH_GATE_CONCURRENCY=1` makes the top-level aggregate execute one ready gate at a time; coverage, snapshot replay, built-bin smoke, and publication validation also receive worker counts of one. The reference jobs may run beside one another, but each host's repository gates are serial and complete. Linux installs bubblewrap before replaying snapshots, and Windows enables Developer Mode before installing the symlinked workspace. @@ -26,7 +26,7 @@ The macOS reference runs the ordinary Vitest project in forked processes. Node 2 The standalone [Sandbox](../../../../.github/workflows/sandbox.yml) workflow belongs to the reference side of the same split. Its bwrap, Landlock x64/arm64, and Seatbelt real-kernel matrix runs only after a push to `master`. Those four jobs are diagnostic: they are not branch-protection requirements and do not feed `all checks passed` across workflow files. Pull-request CI still checks sandbox source through its ordinary unit and coverage inventory; the host-kernel and packed-install proofs report after merge. -Master reference jobs are diagnostic and do not participate in the pull request's required `all checks passed` result. The CI and Sandbox workflows keep their cross-platform references on master pushes. Performance is evaluated from completed hosted-job timestamps and reported as a measurement; it is not encoded as a `timeout-minutes` value. +Master reference jobs are diagnostic and do not participate in the pull request's required `all checks passed` result. The ci-master and Sandbox workflows keep their cross-platform references on master pushes. Performance is evaluated from completed hosted-job timestamps and reported as a measurement; it is not encoded as a `timeout-minutes` value. The active serial references run on the self-hosted `vm-backup` (`serial / linux`) and `dsh-win-ci` (`serial / windows`) pools; the one remaining disabled hosted serial reference (`serial-macos`) uses `macos-latest`, and there is no standard-hosted `serial / linux` label. The required pull-request Windows job runs under Wine on `ubuntu-latest`, while the independent pull-request native job uses the hosted `dsh-windows-2025-16core` runner under normal operation and the self-hosted `[self-hosted, dsh-win-ci, windows]` pool under failover (see the [failover runbook](2026-07-26-ci-failover-runbook.md)), and is absent from the required aggregate under the [dual Windows decision](2026-08-08-native-windows-pull-request-ci.md). Required pull-request jobs use portable standard capacity under the [required-CI decision](2026-07-23-portable-required-pull-request-ci.md). Higher-core hosted runners remain manual benchmarks because a correctness path must remain runnable without repository-external runner configuration. diff --git a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md index c9ffbac428..fd3f762c98 100644 --- a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md +++ b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md @@ -16,7 +16,7 @@ Status: implemented ## 决策 -[CI](../../../../.github/workflows/ci.yml) 为拉取请求事件与 master 推送事件赋予互补的职责。拉取请求在 GitHub 标准托管容量上运行合并后的 Linux 和由 Wine 承载的 Windows 作业,以及 Node 兼容性与 Python 约定;一个独立的原生 Windows 作业会报告完整的 Windows 清单,但不参与必需聚合流程。向 `master` 推送时,当前启用的参考作业是公司自有 `vm-backup` 池上的 `serial / linux (self-hosted standby)` 和 `dsh-win-ci` 池上的 `serial / windows (self-hosted standby)`——这些热备演练持续验证[故障切换手册](2026-07-26-ci-failover-runbook.md)所描述的切换目标。不存在标准托管的 `serial / linux` 定义;标准托管的 `serial / macos` 仍处于禁用状态,并由 `TODO(hosted-serial-ci)` 标记,直到其可移植容量恢复。当前 `serial / windows` 定义是公司自有 `dsh-win-ci` 池的 standby。各自独立的作业定义有意显式保留简短的代码检出、运行时设置和依赖锁定的安装步骤,而不是用矩阵或可复用工作流隐藏操作系统差异。`workflow_dispatch` 仅用于运行器基准测试。 +[CI](../../../../.github/workflows/ci.yml)(仅 pull request)与 [CI master](../../../../.github/workflows/ci-master.yml)(master 推送 + `workflow_dispatch`)为拉取请求事件与 master 推送事件赋予互补的职责。拉取请求在 GitHub 标准托管容量上运行合并后的 Linux 和由 Wine 承载的 Windows 作业,以及 Node 兼容性与 Python 约定;一个独立的原生 Windows 作业会报告完整的 Windows 清单,但不参与必需聚合流程。向 `master` 推送时,当前启用的参考作业是公司自有 `vm-backup` 池上的 `serial / linux (self-hosted standby)` 和 `dsh-win-ci` 池上的 `serial / windows (self-hosted standby)`——这些热备演练持续验证[故障切换手册](2026-07-26-ci-failover-runbook.md)所描述的切换目标。不存在标准托管的 `serial / linux` 定义;标准托管的 `serial / macos` 仍处于禁用状态,并由 `TODO(hosted-serial-ci)` 标记,直到其可移植容量恢复。当前 `serial / windows` 定义是公司自有 `dsh-win-ci` 池的 standby。各自独立的作业定义有意显式保留简短的代码检出、运行时设置和依赖锁定的安装步骤,而不是用矩阵或可复用工作流隐藏操作系统差异。`workflow_dispatch` 仅用于运行器基准测试。 每个参考作业均在不设置任何分片选择器的情况下运行 `pnpm run check:ci`。`DSH_GATE_CONCURRENCY=1` 使顶层聚合每次只执行一个已经就绪的门禁;覆盖率、快照回放、built-bin 冒烟测试和发布验证的 worker 数量也设为 1。各参考作业可以彼此并行,但每台主机上的仓库门禁都串行运行且完整执行。Linux 在回放快照前安装 bubblewrap,Windows 则在安装采用符号链接的工作区前启用开发人员模式。 @@ -26,7 +26,7 @@ macOS 参考流程使用 fork 进程运行常规 Vitest 项目。macOS arm64 上 独立的 [Sandbox](../../../../.github/workflows/sandbox.yml) 工作流属于同一职责划分中的参考侧。其 bwrap、Landlock x64/arm64 与 Seatbelt 真实内核矩阵只在向 `master` 推送后运行。这四个作业仅用于诊断:它们既不是分支保护的必需项,也不会跨工作流计入 `all checks passed`。拉取请求 CI 仍通过常规的单元测试与覆盖率清单检查沙箱源码;宿主内核与 packed-install 验证在合并后报告结果。 -master 分支的参考作业仅用于诊断,不参与拉取请求所要求的 `all checks passed` 结果。CI 与 Sandbox 工作流把跨平台参考流程保留在 master 推送上。系统根据已完成托管作业的时间戳评估性能,并将其报告为测量结果,而不是写成 `timeout-minutes` 值。 +master 分支的参考作业仅用于诊断,不参与拉取请求所要求的 `all checks passed` 结果。ci-master 与 Sandbox 工作流把跨平台参考流程保留在 master 推送上。系统根据已完成托管作业的时间戳评估性能,并将其报告为测量结果,而不是写成 `timeout-minutes` 值。 当前启用的参考流程运行在公司自有 `vm-backup`(`serial / linux`)与 `dsh-win-ci`(`serial / windows`)自托管池上;唯一剩余的禁用托管参考作业(`serial-macos`)使用 `macos-latest`,且不存在标准托管的 `serial / linux` 标签。拉取请求必需的 Windows 作业在 `ubuntu-latest` 上通过 Wine 运行,而独立的拉取请求原生作业在正常运行下使用托管的 `dsh-windows-2025-16core` 运行器,故障切换时使用自托管 `[self-hosted, dsh-win-ci, windows]` 池(参见[故障切换手册](2026-07-26-ci-failover-runbook.md)),依据[双 Windows 决策](2026-08-08-native-windows-pull-request-ci.md)不参与必需聚合流程。依据[必需 CI 决策](2026-07-23-portable-required-pull-request-ci.md),拉取请求必需作业使用可移植的标准容量。更高核心数的托管运行器仍仅用于手动基准测试,因为正确性路径必须无需仓库外部的运行器配置即可运行。 diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml index 5b9f89e609..a6a067b6e5 100644 --- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.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-portable-required-pull-request-ci.md -2026-07-23-portable-required-pull-request-ci.md: 740ef20d6b7a1edc1a010f37bcbcddab2981e8ad -2026-07-23-portable-required-pull-request-ci.zh.md: 5b24af59fb13e32a642e15c79aee3910601ad45b +2026-07-23-portable-required-pull-request-ci.md: 6520a16fb4aa5f03e364a17392c87fe0df459ea1 +2026-07-23-portable-required-pull-request-ci.zh.md: 4cea1525d32af847d6936f348aaf2fba369e4dba diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md index 740ef20d6b..6520a16fb4 100644 --- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md +++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md @@ -12,7 +12,7 @@ Billing health, a runner definition's `Ready` state, and a large autoscaling cei ## Decision -[CI](../../../../.github/workflows/ci.yml) runs the required primary Node 24 jobs, plus the stable `all checks passed` aggregate, on repo-restricted enterprise 32-core pools. The aggregate performs no checkout or repository gate, but sharing the enterprise pool prevents the required verdict from introducing a separate standard-hosted billing dependency after its substantive jobs have already succeeded. The required Windows job runs Windows Node under Wine on standard `ubuntu-latest` for the blocking surfaces; an independent native `windows-2025` job starts automatically but does not participate in the aggregate ([dual Windows decision](2026-08-08-native-windows-pull-request-ci.md)). Standard `ubuntu-latest` jobs retain Node 22.19, Node 26, the Python SDK unit suite, and the [release-shaped Linux x64 Python runtime validation](../testing/2026-08-12-required-python-runtime-pull-request-ci.md), while the serial references remain the complete unsharded cross-platform definitions. Those standard-hosted jobs keep the portable execution boundary observable without duplicating the primary inventory on every pull request. +[CI](../../../../.github/workflows/ci.yml) (pull-request-only) runs the required primary Node 24 jobs, plus the stable `all checks passed` aggregate, on repo-restricted enterprise 32-core pools. The aggregate performs no checkout or repository gate, but sharing the enterprise pool prevents the required verdict from introducing a separate standard-hosted billing dependency after its substantive jobs have already succeeded. The required Windows job runs Windows Node under Wine on standard `ubuntu-latest` for the blocking surfaces; an independent native `windows-2025` job starts automatically but does not participate in the aggregate ([dual Windows decision](2026-08-08-native-windows-pull-request-ci.md)). Standard `ubuntu-latest` jobs retain Node 22.19, Node 26, the Python SDK unit suite, and the [release-shaped Linux x64 Python runtime validation](../testing/2026-08-12-required-python-runtime-pull-request-ci.md), while the serial references (in `ci-master.yml`) remain the complete unsharded cross-platform definitions. Those standard-hosted jobs keep the portable execution boundary observable without duplicating the primary inventory on every pull request. The three Linux primary jobs, Node compatibility, Python SDK unit suite, Python runtime validation, and `windows node 24 / wine blocking` remain dependencies of `all checks passed`; `windows node 24 / native complete` is deliberately absent. Branch protection continues to require `e2e` and `all checks passed`. There is no automatic fallback when a remaining enterprise Linux label cannot allocate: the standard jobs continue to report their own contracts, but they cannot manufacture the missing required result. diff --git a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md index 5b24af59fb..4cea1525d3 100644 --- a/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md +++ b/.agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -[CI](../../../../.github/workflows/ci.yml) 在仅限本仓库使用的企业级 32 核运行器池上运行必需的主 Node 24 作业,以及稳定的 `all checks passed` 聚合流程。该聚合流程不执行代码检出或仓库门禁;但让它与所依赖的实质性作业共用企业级运行器池,可以避免这些作业已经成功后,必需判定结果又引入一项单独的标准托管计费依赖。必需的 Windows 作业在标准 `ubuntu-latest` 上通过 Wine 运行 Windows Node,覆盖阻断性检查范围;一个独立的原生 `windows-2025` 作业会自动启动,但不参与聚合流程([双 Windows 决策](2026-08-08-native-windows-pull-request-ci.md))。标准 `ubuntu-latest` 作业保留 Node 22.19、Node 26、Python SDK 单元测试套件与[发布形态的 Linux x64 Python 运行时验证](../testing/2026-08-12-required-python-runtime-pull-request-ci.md),串行参考流程仍是完整且未分片的跨平台定义。这些标准托管作业让可移植执行边界保持可观测,而不必在每个拉取请求中重复主清单。 +[CI](../../../../.github/workflows/ci.yml)(仅 pull request)在仅限本仓库使用的企业级 32 核运行器池上运行必需的主 Node 24 作业,以及稳定的 `all checks passed` 聚合流程。该聚合流程不执行代码检出或仓库门禁;但让它与所依赖的实质性作业共用企业级运行器池,可以避免这些作业已经成功后,必需判定结果又引入一项单独的标准托管计费依赖。必需的 Windows 作业在标准 `ubuntu-latest` 上通过 Wine 运行 Windows Node,覆盖阻断性检查范围;一个独立的原生 `windows-2025` 作业会自动启动,但不参与聚合流程([双 Windows 决策](2026-08-08-native-windows-pull-request-ci.md))。标准 `ubuntu-latest` 作业保留 Node 22.19、Node 26、Python SDK 单元测试套件与[发布形态的 Linux x64 Python 运行时验证](../testing/2026-08-12-required-python-runtime-pull-request-ci.md),串行参考流程(在 `ci-master.yml` 中)仍是完整且未分片的跨平台定义。这些标准托管作业让可移植执行边界保持可观测,而不必在每个拉取请求中重复主清单。 三项 Linux 主作业、Node 兼容性、Python SDK 单元测试套件、Python 运行时验证和 `windows node 24 / wine blocking` 继续作为 `all checks passed` 的依赖项;`windows node 24 / native complete` 被刻意排除。分支保护继续要求 `e2e` 和 `all checks passed`。剩余的企业级 Linux 运行器标签无法分配运行器时没有自动后备机制:标准作业会继续报告各自的约定,但无法产出缺失的必需结果。 diff --git a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.i18n.yaml index f8cdf8e924..f8e8ce4bee 100644 --- a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.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-ci-failover-runbook.md -2026-07-26-ci-failover-runbook.md: e8a1d1dc339cc5d9be3db3be395e2cddad93b6fc -2026-07-26-ci-failover-runbook.zh.md: 8f92b7b60c075f21b6f2c83dc46a6e0e5d8acce2 +2026-07-26-ci-failover-runbook.md: bfed4e6e15311d0191c1379a5822b0daf46f4ed3 +2026-07-26-ci-failover-runbook.zh.md: 86007d5b189ccc883dc96d68bc9e54f38bb09e2a diff --git a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md index e8a1d1dc33..bfed4e6e15 100644 --- a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md +++ b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md @@ -12,11 +12,11 @@ The three required Linux worker jobs in [CI](../../../../.github/workflows/ci.ym Each of the three required Linux worker jobs, the independent native Windows job, and the `all checks passed` verdict job — which would otherwise stay queued on the failed pool even after every worker passed — resolves its runner pool through a repository variable, and the switch is split by platform so an outage on one platform does not retarget the other. The three Linux workers and the `all checks passed` verdict (whose `needs` are the required Linux workers and which runs on the `vm-backup` pool) resolve through `DSH_CI_FAILOVER_LINUX`; the native Windows job resolves through `DSH_CI_FAILOVER_WINDOWS`. Unset (normal), they run on the hosted enterprise pools. Set to `selfhosted` by any repository writer, the corresponding jobs retarget onto the in-house self-hosted pool: under `DSH_CI_FAILOVER_LINUX`, the Linux jobs and verdict move onto the `vm-backup` pool, snapshot concurrency drops to the shared-VM bound, and the hosted-path pnpm cache restores are skipped; under `DSH_CI_FAILOVER_WINDOWS`, the native Windows job moves onto the `dsh-win-ci` pool. Each switch is writer-manageable repository state, not a merge, so it works while every check is red. The in-house pools' readiness is continuously re-proven by the `serial / linux (self-hosted standby)` and `serial / windows (self-hosted standby)` lanes, which run the complete unsharded aggregates on every master push. -`ci.yml` exempts exactly one event from `cancel-in-progress` (`${{ github.event_name != 'push' }}`), so one master push does not cancel the drill still running from the previous one. Each drill runs its complete unsharded aggregate with one gate worker, which takes longer than the interval between master merges; under unconditional cancellation a drill is superseded before reaching a verdict and the lane yields no readiness evidence for a responder to check. +`ci-master.yml` exempts exactly one event from `cancel-in-progress` (`${{ github.event_name != 'push' }}`), so one master push does not cancel the drill still running from the previous one. Each drill runs its complete unsharded aggregate with one gate worker, which takes longer than the interval between master merges; under unconditional cancellation a drill is superseded before reaching a verdict and the lane yields no readiness evidence for a responder to check. -The exemption is narrower than "a drill always finishes", in two ways. GitHub keeps a single pending entry per group, so a newer pending run displaces an older one and intermediate push runs still end as `cancelled` during busy periods. And the expression is evaluated against the *newly triggered* run, so a run whose own event is not `push` — a benchmark dispatched on master, sharing the group `CI-` — evaluates to `true` and does cancel a drill that is mid-flight. That is a rare manual action and the next master push restores the evidence, so it does not warrant further mechanism. What the carve-out buys is that the lane periodically reaches a verdict at all, which is what makes it usable as evidence. +The exemption is narrower than "a drill always finishes", in two ways. GitHub keeps a single pending entry per group, so a newer pending run displaces an older one and intermediate push runs still end as `cancelled` during busy periods. And the expression is evaluated against the *newly triggered* run, so a run whose own event is not `push` — a benchmark dispatched on master within `ci-master.yml`, sharing its group `CI master-` — evaluates to `true` and does cancel a drill that is mid-flight. That is a rare manual action and the next master push restores the evidence, so it does not warrant further mechanism. What the carve-out buys is that the lane periodically reaches a verdict at all, which is what makes it usable as evidence. -The decision belongs at workflow level because cancellation applies to the whole superseded run: a job-level `concurrency` group does not exempt its job. The negated form is load-bearing rather than cosmetic: naming `pull_request` alone would also stop cancelling `workflow_dispatch`, and each runner benchmark fans out to twelve larger runners for up to fifteen minutes inside this same group on master, so a re-dispatch would queue ahead of a drill instead of replacing a stale measurement. What bounds the cost is that a master push carries only `wine-apt-cache` and these two drills; every other job is pull-request-gated, `workflow_dispatch`-gated, or `if: false`, and `scripts/ci-workflow.spec.ts` pins that set — classifying by exact condition, since a negated event test mentions the event it excludes — so a new push-reachable job cannot quietly start accumulating uncancelled runs. +The decision belongs at workflow level because cancellation applies to the whole superseded run: a job-level `concurrency` group does not exempt its job. The negated form is load-bearing rather than cosmetic: naming `pull_request` alone would also stop cancelling `workflow_dispatch`, and each runner benchmark fans out to twelve larger runners for up to fifteen minutes inside this same group on master, so a re-dispatch would queue ahead of a drill instead of replacing a stale measurement. What bounds the cost is that a master push in `ci-master.yml` carries only `wine-apt-cache` and these two drills; the pull-request jobs live in the separate `ci.yml` (which does not see `push`), and the benchmarks are `workflow_dispatch`-gated within `ci-master.yml`. `scripts/ci-workflow.spec.ts` pins that push-reachable set — classifying by exact condition, since a negated event test mentions the event it excludes — so a new push-reachable job cannot quietly start accumulating uncancelled runs. ### What the in-house pool is diff --git a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md index 8f92b7b60c..86007d5b18 100644 --- a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md @@ -12,11 +12,11 @@ Status: implemented 三个必需的 Linux 工作作业、独立的原生 Windows 作业,以及 `all checks passed` 判定作业(若不随切换,即使全部工作作业通过,它仍会滞留在故障池的队列中)——各自通过仓库变量解析运行器池,且开关按平台拆分,使一个平台的故障不会重定向另一个平台。三个 Linux 工作作业与 `all checks passed` 判定作业(其 `needs` 是必需的 Linux 工作作业,且运行在 `vm-backup` 池上)通过 `DSH_CI_FAILOVER_LINUX` 解析;原生 Windows 作业通过 `DSH_CI_FAILOVER_WINDOWS` 解析。变量不存在(正常)时它们运行在托管企业池上;由任何具备写权限的协作者设为 `selfhosted` 时,对应作业切换到公司自有的自托管池:`DSH_CI_FAILOVER_LINUX` 下,Linux 作业与判定作业切到 `vm-backup` 池,快照并发降到共享虚拟机上限,并跳过托管路径的 pnpm 缓存恢复;`DSH_CI_FAILOVER_WINDOWS` 下,原生 Windows 作业切到 `dsh-win-ci` 池。每个开关都是写者可管理的仓库状态而非一次合并,因此在所有检查都是红色时仍然有效。自有池的就绪状态由 `serial / linux (self-hosted standby)` 与 `serial / windows (self-hosted standby)` 通道持续验证——每次 master 推送都在其上运行完整的未分片聚合流程。 -`ci.yml` 只豁免一个事件不做取消(`${{ github.event_name != 'push' }}`),因此一次 master 推送不会取消上一次推送留下的、仍在运行的演练。每次演练以单门禁工作进程执行完整的未分片聚合流程,耗时长于 master 合并的间隔;在无条件取消下,演练会在得出结论前被后续运行取代,该通道无法产出供响应者查看的就绪证据。 +`ci-master.yml` 只豁免一个事件不做取消(`${{ github.event_name != 'push' }}`),因此一次 master 推送不会取消上一次推送留下的、仍在运行的演练。每次演练以单门禁工作进程执行完整的未分片聚合流程,耗时长于 master 合并的间隔;在无条件取消下,演练会在得出结论前被后续运行取代,该通道无法产出供响应者查看的就绪证据。 -这项豁免比「演练总能跑完」要窄,有两点限制。其一,GitHub 每个组只保留一个待运行条目,更新的待运行条目会顶掉更早的,繁忙时段中间的推送运行仍会以 `cancelled` 结束。其二,该表达式是针对**新触发的运行**求值的,因此自身事件不是 `push` 的运行——例如在 master 上派发的基准测试,与演练共用 `CI-` 组——求值为 `true`,会取消正在运行中的演练。这属于罕见的手动操作,且下一次 master 推送即可恢复证据,因此不值得为它再加机制。这项豁免换来的是该通道**周期性**地得出结论,而这正是它能作为证据的前提。 +这项豁免比「演练总能跑完」要窄,有两点限制。其一,GitHub 每个组只保留一个待运行条目,更新的待运行条目会顶掉更早的,繁忙时段中间的推送运行仍会以 `cancelled` 结束。其二,该表达式是针对**新触发的运行**求值的,因此自身事件不是 `push` 的运行——例如在 `ci-master.yml` 内的 master 上派发的基准测试,与其演练共用 `CI master-` 组——求值为 `true`,会取消正在运行中的演练。这属于罕见的手动操作,且下一次 master 推送即可恢复证据,因此不值得为它再加机制。这项豁免换来的是该通道**周期性**地得出结论,而这正是它能作为证据的前提。 -这个决定必须放在工作流级:取消作用于被取代的整个运行,作业级 `concurrency` 组并不能豁免其所属作业。采用否定式写法而非仅指名 `pull_request`,是有实质作用的:后者会连 `workflow_dispatch` 一起停止取消,而每次运行器基准测试会在 master 上的同一并发组内同时占用 12 台大规格运行器、最长 15 分钟,届时重复派发会排在演练之前,而不是替换掉已过时的测量。成本之所以可控,是因为一次 master 推送只承载 `wine-apt-cache` 和这两条演练;其余作业都受拉取请求门控、`workflow_dispatch` 门控或 `if: false`,并且 `scripts/ci-workflow.spec.ts` 会锁定这个集合——按条件精确匹配,因为否定式事件判断会包含它所排除的事件名——使新的推送可达作业无法悄悄开始累积未取消的运行。 +这个决定必须放在工作流级:取消作用于被取代的整个运行,作业级 `concurrency` 组并不能豁免其所属作业。采用否定式写法而非仅指名 `pull_request`,是有实质作用的:后者会连 `workflow_dispatch` 一起停止取消,而每次运行器基准测试会在 master 上的同一并发组内同时占用 12 台大规格运行器、最长 15 分钟,届时重复派发会排在演练之前,而不是替换掉已过时的测量。成本之所以可控,是因为 `ci-master.yml` 中一次 master 推送只承载 `wine-apt-cache` 和这两条演练;拉取请求作业位于独立的 `ci.yml`(不监听 `push`),而基准测试在 `ci-master.yml` 内受 `workflow_dispatch` 门控。`scripts/ci-workflow.spec.ts` 会锁定这个推送可达集合——按条件精确匹配,因为否定式事件判断会包含它所排除的事件名——使新的推送可达作业无法悄悄开始累积未取消的运行。 ### 自有池是什么 diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml index c75e36f054..9aa6dfc8d2 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.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-pnpm-action-setup-for-symmetric-ci-caching.md -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: 31d1ebf009a6e044722081985546e87b9d4ae0e2 -2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 84445abfdd42671f2f5c9de8403ed7891f9cc292 +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: c998986501eacfca88c8f7125f7ff4bc7f9a7101 +2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 6e1b61e64995d0fb68c7509a0cd374b2a98d16ae diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md index 31d1ebf009..c998986501 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md @@ -12,7 +12,7 @@ Outside `landlock-run.yml`, each workflow that installed pnpm hand-provisioned i `pnpm/action-setup@v4` is the only pnpm provisioning mechanism in CI: no workflow runs `corepack enable`. The root dev dependency on `@yarnpkg/cli-dist` separately supplies the modern Yarn CLI exercised by the generated-project e2e; package-manager coverage therefore does not inherit the runner image's Yarn Classic. Caching remains per-job policy on top of pnpm provisioning, in three deliberate shapes: -- **Symmetric cache** (restore and save): `actions/setup-node` with `cache: pnpm` — `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, and the node-compat and two benchmark jobs of `ci.yml`. The larger-runner benchmark keeps its store cache Linux-only through a conditional `cache:` input; the consolidated benchmark caches on both platforms. +- **Symmetric cache** (restore and save): `actions/setup-node` with `cache: pnpm` — `e2e.yml`, `docs-pages.yml`, `pi-ai-provider-e2e.yml`, `build-exe-for-python-sdk.yml`, the node-compat job of `ci.yml`, and the two benchmark jobs of `ci-master.yml`. The larger-runner benchmark keeps its store cache Linux-only through a conditional `cache:` input; the consolidated benchmark caches on both platforms. - **Restore-only caching** (hand-rolled `actions/cache` steps): the three enterprise-runner PR jobs and the Wine-based required Windows job restore without saving, keeping cache compression/upload off their latency-sensitive paths — an asymmetry `setup-node`'s cache cannot express. Each configures a store outside the action's replaceable install directory and resolves that path. No master job produces these hosted caches, so these restores hit matching archived entries until they evict. The enterprise jobs skip restore during self-hosted failover because that VM's persistent store is already warm. - **Cache-less or persistent** (no store-cache action): the independent native Windows job, native serial-windows and serial-macos, plus `sandbox.yml` install from a cold or runner-local store. Extracting the many-file pnpm store costs more than a clean Windows install; the self-hosted standby and failover jobs instead reuse their VM's persistent pnpm store without transferring a hosted cache archive. diff --git a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md index 84445abfdd..6e1b61e649 100644 --- a/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md @@ -12,7 +12,7 @@ Status: implemented `pnpm/action-setup@v4` 是 CI 中提供 pnpm 的唯一机制:没有任何工作流运行 `corepack enable`。根目录的 `@yarnpkg/cli-dist` 开发依赖另行提供 generated-project e2e 所运行的现代 Yarn CLI(命令行界面);因此,用于包管理器覆盖率的 Yarn 不会沿用 runner 镜像里的 Yarn Classic。缓存仍是叠加在 pnpm 提供机制上的按作业策略,保留三种有意采用的形态: -- **对称缓存**(既恢复也保存):带 `cache: pnpm` 的 `actions/setup-node`——`e2e.yml`、`docs-pages.yml`、`pi-ai-provider-e2e.yml`、`build-exe-for-python-sdk.yml`,以及 `ci.yml` 的 node-compat 与两个 benchmark 作业。larger-runner benchmark 通过条件化的 `cache:` 输入让 store 缓存仅限 Linux;consolidated benchmark 在两个平台上都启用缓存。 +- **对称缓存**(既恢复也保存):带 `cache: pnpm` 的 `actions/setup-node`——`e2e.yml`、`docs-pages.yml`、`pi-ai-provider-e2e.yml`、`build-exe-for-python-sdk.yml`、`ci.yml` 的 node-compat 作业,以及 `ci-master.yml` 的两个 benchmark 作业。larger-runner benchmark 通过条件化的 `cache:` 输入让 store 缓存仅限 Linux;consolidated benchmark 在两个平台上都启用缓存。 - **只恢复不上传**(手写的 `actions/cache` 步骤):企业 runner 上的三个 PR(Pull Request)作业和基于 Wine 的必需 Windows 作业只恢复不保存,把缓存压缩/上传挡在它们的延迟敏感路径之外——这种不对称是 `setup-node` 的缓存无法表达的。每个作业都在 action 可替换的安装目录之外配置 store,并解析该路径。没有任何 master 作业生产这些 hosted 缓存,这些恢复步骤只能命中仍有归档的旧条目,直至其被逐出;企业作业在自托管故障切换期间跳过恢复,因为该 VM 的持久 store 已经预热。 - **无缓存或持久化**(不使用 store 缓存 action):独立的原生 Windows 作业、原生 serial-windows 和 serial-macos,以及 `sandbox.yml` 均从冷 store 或 runner 本地 store 安装。解压含有大量文件的 pnpm store,成本高于在 Windows 上进行一次全新安装;自托管热备与故障切换作业则复用其 VM 的持久 pnpm store,不传输托管缓存归档。 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml index 851f0e9d2d..e3a181f63d 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.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-10-npm-release-sequences.md -2026-08-10-npm-release-sequences.md: d8495f158482d5d6e06a1752a096d1e9200b6070 -2026-08-10-npm-release-sequences.zh.md: 24b466f6b7b10d31ac2e025da6e12ec3c91c7548 +2026-08-10-npm-release-sequences.md: efeda91b6a85e1316c563cc04411878122096953 +2026-08-10-npm-release-sequences.zh.md: d905ac4b58691890d2aad955b87713a278dcb4f8 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md index d8495f1584..efeda91b6a 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md @@ -22,7 +22,7 @@ Two hard blockers sat in the way. All 217 workspace manifests set `private: true | Sequence | Members | Version baseline | Tag | Workflow | |---|---|---|---|---| -| dsh | `packages/*/*` + `apps/*` (`@deepseek-ai/dsh` and `@deepseek-ai/dsh-web-frontend`) | one version for the family and the workspace root, `0.0.x` | `dsh-v` | `release.yml` | +| dsh | Publish set: non-experimental `packages/*/*` + `apps/*`; private experimental packages join only the shared version bump | one version for the publish set, private dsh packages, and workspace root, `0.0.x` | `dsh-v` | `release.yml` | | vendored framework | the nine `vendor/*` packages | each package on its own version line | `vendor--v` (one per package) | `release-vendor.yml` | | native | `native/landlock-run/packages/*` | its own `0.0.x` | `landlock-run-v` | `landlock-run-release.yml` | @@ -32,7 +32,7 @@ All three publish to the `@deepseek-ai` scope on npmjs.com, and access is per se Each sequence has one bump-and-commit command: it derives the target version, writes it into the relevant manifests, runs `pnpm install --lockfile-only`, and commits the manifests with the lockfile. The published version is therefore readable from the repository. A human creates the tag after the commit merges to master; CI never writes to the repository and needs no write permission. -`release:dsh` accepts `major`, `minor`, `patch`, or an explicit version, and writes one version across the family **and the workspace root** — the workspace constraint requires every member's version to equal the root's, so the root carries the family version, and the root check accepts a prerelease segment. A prerelease such as `0.0.1-rc.1` drives pack, the installed-artifact probe, and one real private publication before numbered versions follow. The dist-tag decision is the one `landlock-run-release.yml` already made: a version with a prerelease segment publishes under `--tag next`, anything else takes `latest`. +`release:dsh` accepts `major`, `minor`, `patch`, or an explicit version, and writes one version across the publishable family, every private package under `packages/*/*`, **and the workspace root**. Private packages receive no release tag and remain outside pack and publish; they follow the version because the workspace constraint requires every dsh package's version to equal the root's. The root check accepts a prerelease segment. A prerelease such as `0.0.1-rc.1` drives pack, the installed-artifact probe, and one real private publication before numbered versions follow. The dist-tag decision is the one `landlock-run-release.yml` already made: a version with a prerelease segment publishes under `--tag next`, anything else takes `latest`. ### vendor: publish what changed, and let tags be the ledger diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md index 24b466f6b7..d905ac4b58 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md @@ -22,7 +22,7 @@ Status: implemented | 序列 | 成员 | 版本基线 | tag | workflow | |---|---|---|---|---| -| dsh | `packages/*/*` + `apps/*`(`@deepseek-ai/dsh` 与 `@deepseek-ai/dsh-web-frontend`) | 全族与 workspace 根共用一个 `0.0.x` | `dsh-v<版本>` | `release.yml` | +| dsh | 发布集:非 experimental 的 `packages/*/*` + `apps/*`;私有实验性包仅加入共享版本 bump | 发布集、私有 dsh 包与 workspace 根共用一个 `0.0.x` | `dsh-v<版本>` | `release.yml` | | vendored framework | `vendor/*` 九个包 | 每包各自一条版本线 | `vendor-<包名>-v<版本>`(每包一个) | `release-vendor.yml` | | native | `native/landlock-run/packages/*` | 自己的 `0.0.x` | `landlock-run-v<版本>` | `landlock-run-release.yml` | @@ -32,7 +32,7 @@ Status: implemented 每条序列有一条 bump-and-commit 命令:算出目标版本,写进相关 manifest,跑 `pnpm install --lockfile-only`,再把 manifest 连 lockfile 一起 commit。发布版本因此在仓库里查得到。tag 由人工在 commit 合入 master 后打;CI 不写仓库,也不需要写权限。 -`release:dsh` 接受 `major`、`minor`、`patch` 或显式版本号,把同一个版本写进全族**以及 workspace 根**——workspace 约束要求每个成员的版本等于根版本,所以根承载族版本,而根的检查接受预发布段。像 `0.0.1-rc.1` 这样的预发布号先把 pack、已安装产物探针和一次真实私有发布跑通,数字版本随后。dist-tag 沿用 `landlock-run-release.yml` 已有的判定:版本带预发布段就 `--tag next`,否则进 `latest`。 +`release:dsh` 接受 `major`、`minor`、`patch` 或显式版本号,把同一个版本写进可发布族、`packages/*/*` 下的每个私有包**以及 workspace 根**。私有包不会获得发布 tag,仍位于 pack 与 publish 之外;它们跟随版本是因为 workspace 约束要求每个 dsh 包的版本等于根版本。根的检查接受预发布段。像 `0.0.1-rc.1` 这样的预发布号先把 pack、已安装产物探针和一次真实私有发布跑通,数字版本随后。dist-tag 沿用 `landlock-run-release.yml` 已有的判定:版本带预发布段就 `--tag next`,否则进 `latest`。 ### vendor:谁改了谁发版,tag 就是账本 diff --git a/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.i18n.yaml index 226d604242..b763e848d1 100644 --- a/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.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-08-12-trim-agent-teams-read-and-lifecycle-surface.md -2026-08-12-trim-agent-teams-read-and-lifecycle-surface.md: 736ed2587c8b2ffd9d824efa14dd0d249c43f669 -2026-08-12-trim-agent-teams-read-and-lifecycle-surface.zh.md: be30d9239d4dc0c44adda63d9488e7a25d217a9e +2026-08-12-trim-agent-teams-read-and-lifecycle-surface.md: 69a54dae6e43ac4a93ebcf825bdeb1bad29a23fe +2026-08-12-trim-agent-teams-read-and-lifecycle-surface.zh.md: f43dc680069855651a9087776987e8998790d829 diff --git a/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.md b/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.md index 736ed2587c..69a54dae6e 100644 --- a/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.md +++ b/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.md @@ -18,7 +18,7 @@ Durable member, task, message, and acknowledgement payloads copied timestamps al The Team service keeps the distinct product responsibilities: durable named roster, Lead-log mailbox, and task DAG. It does not merge with the generic subagent catalog or task service. -Inside `@deepseek-ai/dsh-team`, `TeamService` is the Cordis-facing façade and disposal coordinator. `TeamJournal` owns per-Lead transaction order and append-plus-flush publication; `TeamRoster` owns membership and provisioning; `TeamMailbox` owns target-local dispatch, acknowledgement, and retry state; `TeamTaskBoard` owns task authorization, DAG transitions, and derived views; `TeamActivity` owns current waiters; and `TeamRuntimeLifecycle` owns the single admission cutoff and bounded settlement. These package-private collaborators share the existing service capability without publishing additional Cordis services. +Inside `@deepseek-ai/dsh-experimental-agent-team`, `TeamService` is the Cordis-facing façade and disposal coordinator. `TeamJournal` owns per-Lead transaction order and append-plus-flush publication; `TeamRoster` owns membership and provisioning; `TeamMailbox` owns target-local dispatch, acknowledgement, and retry state; `TeamTaskBoard` owns task authorization, DAG transitions, and derived views; `TeamActivity` owns current waiters; and `TeamRuntimeLifecycle` owns the single admission cutoff and bounded settlement. These package-private collaborators share the existing service capability without publishing additional Cordis services. The unused snapshot API and global Team revision are removed. Host reads return only roster and task views; they do not repeat the already-addressed Team id. Member failures appear once in `diagnostics`. Task views expose `ownerName` but keep `ownerId` inside the durable service implementation. Spawn returns the member view only, and validated config is private. diff --git a/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.zh.md b/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.zh.md index be30d9239d..f43dc68006 100644 --- a/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-12-trim-agent-teams-read-and-lifecycle-surface.zh.md @@ -18,7 +18,7 @@ Agent Teams 合理地拥有持久 roster、peer mailbox 与共享任务策略, Team 服务保留独立的产品职责:持久具名 roster、Lead-log mailbox 与 task DAG。它不会与通用 subagent catalog 或 task service 合并。 -在 `@deepseek-ai/dsh-team` 内,`TeamService` 是面向 Cordis 的 façade 与 disposal 协调者。`TeamJournal` 负责每个 Lead 的 transaction 顺序以及 append-plus-flush 发布;`TeamRoster` 负责 membership 与 provisioning;`TeamMailbox` 负责 target-local dispatch、acknowledgement 与 retry 状态;`TeamTaskBoard` 负责 task 授权、DAG transition 与派生 view;`TeamActivity` 负责当前 waiter;`TeamRuntimeLifecycle` 负责唯一的准入截止与有界 settlement。这些包内 collaborator 共享现有 service capability,不发布额外 Cordis service。 +在 `@deepseek-ai/dsh-experimental-agent-team` 内,`TeamService` 是面向 Cordis 的 façade 与 disposal 协调者。`TeamJournal` 负责每个 Lead 的 transaction 顺序以及 append-plus-flush 发布;`TeamRoster` 负责 membership 与 provisioning;`TeamMailbox` 负责 target-local dispatch、acknowledgement 与 retry 状态;`TeamTaskBoard` 负责 task 授权、DAG transition 与派生 view;`TeamActivity` 负责当前 waiter;`TeamRuntimeLifecycle` 负责唯一的准入截止与有界 settlement。这些包内 collaborator 共享现有 service capability,不发布额外 Cordis service。 删除未使用的 snapshot API 与全局 Team revision。Host 读取只返回 roster 与 task view,不重复已经寻址的 Team id。member failure 只在 `diagnostics` 出现一次。task view 暴露 `ownerName`,把 `ownerId` 留在持久服务实现内部。spawn 只返回 member view,已校验 config 改为私有。 diff --git a/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.i18n.yaml new file mode 100644 index 0000000000..fd04333398 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.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-19-knip-config-cleanup.md +2026-08-19-knip-config-cleanup.md: 91bfd0b9cf19db0c62831f74a9621ac99b7d44f2 +2026-08-19-knip-config-cleanup.zh.md: 123434b9017b66dfa4f4b1c1cb4aa62d42db5d8c diff --git a/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.md b/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.md new file mode 100644 index 0000000000..91bfd0b9cf --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.md @@ -0,0 +1,31 @@ +# Agent Note: Deleted stale and duplicative knip.json workspace entries + +Status: implemented + +[中文](2026-08-19-knip-config-cleanup.zh.md) | English + +## Problem + +`knip.json` carried workspace entries that did no work. Some pointed at packages that no longer exist, and some duplicated the `packages/*/*` glob default exactly. Both kinds made the file larger — 790 lines — and signaled a config that had outgrown the packages it described, so a reader could not tell which entries protected real behavior and which were inert. + +## Decision + +Deleted 15 `workspaces` entries: 2 stale keys naming packages absent from the working tree and from `HEAD`, and 13 entries whose `entry`/`project` values were byte-identical to the `packages/*/*` glob default. + +- Stale keys: `packages/util/home` (removed in `4a09d9b34d`, the harness-home resolver collapse) and `packages/client/web-ui` (no directory and no git history, an orphan key). knip 6.16 does not flag stale workspace keys — that stability check arrived in knip 6.18 — so these were inert config that only deleted when their packages disappeared. +- Glob-duplicate entries: `packages/host/webserver`, `packages/client/runtime`, `packages/core/tools`, `packages/context/tmux-context`, `packages/util/timeout`, `packages/util/output-retention`, `packages/goal/goal-round-driver`, `packages/goal/tool-goal`, `packages/util/home-paths`, `packages/fs/tool-fs-search`, `packages/client/ui-settings`, `packages/client/modules`, `packages/client/hmr`. Each declared exactly `entry: ["tests/**/*.spec.ts"]` and `project: ["src/**/*.ts", "tests/**/*.ts"]`, which equals the `packages/*/*` glob, and each package still exists, so the glob now covers it identically. + +The change is a deletion only: `knip.json` went from 790 to 655 lines with no behavioral change. `pnpm run knip` runs clean (zero issues, exit 0) before and after, because knip selects one workspace config per matched key (`getConfigKeyForWorkspace` uses specificity, not array merge), so a removed entry either lost an unresolvable target or fell back to an identical glob config. + +## Alternatives considered + +- Fold `zod` and other workspace-level `ignoreDependencies` up to the root. Rejected: the root `ignoreDependencies` is a repository-wide fallback, and these exemptions are deliberately workspace-scoped (the README of `cordis-host-runner` records why `src` cannot import the flagged dependency while the generated TypeRT face in `lib` needs it). Widening scope would mask a genuinely misplaced dependency in any future package. +- Upgrade knip to 6.18+ to get an automatic stale-workspace check. Deferred: 6.32.2 (latest at the time) re-flags many `@deepseek-ai/...` test dependencies as unused, i.e. it changes analysis semantics, not just adds hints. That is a separate dependency-upgrade decision with its own CI blast radius, not part of this cleanup. +- Keep the entries as documentation of intent. Rejected: an entry identical to the glob it sits under documents nothing beyond the glob itself, and a key naming an absent package actively misleads. + +## Consequences + +- `knip.json` is 135 lines shorter and names only packages that exist with config that differs from the glob default. +- Still-explicit entries (54) all carry a real reason to differ — an `e2e`/fixture/tsx `entry`, a `project` outside the default, or a workspace-scoped `ignoreDependencies`. +- knip 6.16 cannot itself detect the next stale key, so a package removal must still remember to drop its `knip.json` key; upgrading to 6.18+ (after the analysis-semantics change is separately assessed) restores that guard. +- This realizes the "never a restatement of the default stanza" criterion of the package-inventory proposal ([topic](../../proposed/process/2026-06-20-discover-package-inventory.md)); its remaining items — the e2e entry folding and the generated inventory — stay open there. diff --git a/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.zh.md b/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.zh.md new file mode 100644 index 0000000000..123434b901 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-08-19-knip-config-cleanup.zh.md @@ -0,0 +1,31 @@ +# Agent Note: 删除 knip.json 中失效与重复的 workspace 条目 + +Status: implemented + +[English](2026-08-19-knip-config-cleanup.md) | 中文 + +## 问题 + +`knip.json` 携带了大量不产生任何作用的 workspace 条目。其中一些指向已经不复存在的包,另一些与 `packages/*/*` 通配默认完全重复。这两类都让文件变大——790 行——并显现出配置已经超出了它所描述的包:读者无法分辨哪些条目在保护真实行为、哪些是惰性的。 + +## 决策 + +删除了 15 个 `workspaces` 条目:2 个指向工作树与 `HEAD` 中都不存在的包的失效键,以及 13 个 `entry`/`project` 与 `packages/*/*` 通配默认逐字节相同的条目。 + +- 失效键:`packages/util/home`(在 `4a09d9b34d`,harness home 解析器的合并改动中删除)和 `packages/client/web-ui`(无对应目录、无 git 历史,是孤儿键)。knip 6.16 不会标记失效的 workspace 键——这项稳定性检查在 knip 6.18 才引入——所以这些是本应在包消失时一并删除、却残留的惰性配置。 +- 通配重复条目:`packages/host/webserver`、`packages/client/runtime`、`packages/core/tools`、`packages/context/tmux-context`、`packages/util/timeout`、`packages/util/output-retention`、`packages/goal/goal-round-driver`、`packages/goal/tool-goal`、`packages/util/home-paths`、`packages/fs/tool-fs-search`、`packages/client/ui-settings`、`packages/client/modules`、`packages/client/hmr`。每个都恰好声明了 `entry: ["tests/**/*.spec.ts"]` 和 `project: ["src/**/*.ts", "tests/**/*.ts"]`,与 `packages/*/*` 通配相等,且这些包仍然存在,因此通配现在以完全相同的方式覆盖它们。 + +本改动只做删除:`knip.json` 从 790 行降到 655 行,行为不变。`pnpm run knip` 在改动前后都干净通过(零问题、退出码 0),因为 knip 为每个已匹配的键选取一条 workspace 配置(`getConfigKeyForWorkspace` 按特定优先、不做数组合并),所以被删条目要么丢掉了无法解析的目标,要么回退到一个完全相同的通配配置。 + +## 备选方案 + +- 把 `zod` 及其它 workspace 级 `ignoreDependencies` 上提到根级。否决:根级 `ignoreDependencies` 是全仓库兜底,而这些豁免是刻意限定在 workspace 的(`cordis-host-runner` 的 README 记录了为什么 `src` 无法 import 被标记的依赖、而生成的 `lib` 里的 TypeRT 契约面需要它)。扩大作用域会掩盖未来任何包里真正放错位置的依赖。 +- 升级 knip 到 6.18+ 以获得自动的失效 workspace 检查。延后:撰写时的最新版 6.32.2 会把大量 `@deepseek-ai/...` 测试依赖重新标记为未使用——也就是改变了分析语义,而不仅是新增提示。那是独立的依赖升级决定,带自己的 CI 影响面,不属于本次清理。 +- 保留这些条目作为意图的文档。否决:与它挂在下面的通配完全相同的条目,除了通配本身外不记录任何东西;而指向不存在包的键确实会误导人。 + +## 结果 + +- `knip.json` 缩短了 135 行,并且只列出确实存在、且配置与通配默认有差异的包。 +- 仍然显式的条目(54 个)都带有真实的特例理由——`e2e`/fixture/tsx 的 `entry`、超出默认的 `project`、或 workspace 级的 `ignoreDependencies`。 +- knip 6.16 自身无法检测下一个失效键,因此删除包时仍须记得清理它的 `knip.json` 键;升级到 6.18+(在分析语义的改动被单独评估之后)会恢复这道守卫。 +- 本改动落实了包清单提案中「绝不复述默认 stanza」的标准([议题](../../proposed/process/2026-06-20-discover-package-inventory.md));其剩余项——e2e 入口折叠与生成的清单——仍在提案中保持开放。 diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml index 2adf6c62ff..6e22c2a8d2 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.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-27-session-projection-and-command-log.md -2026-07-27-session-projection-and-command-log.md: aa83ae4d0e96bc1681aec22da854ba258b9e19a9 -2026-07-27-session-projection-and-command-log.zh.md: 00349ac46dc300c60f725b257e0fff9bcdf3a586 +2026-07-27-session-projection-and-command-log.md: 838d5888429d449144ef59734743bcd9b1a8568e +2026-07-27-session-projection-and-command-log.zh.md: b2d26811770ee7d9f511c56e381294462d7a9f31 diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md index aa83ae4d0e..838d588842 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md @@ -24,22 +24,27 @@ A state-carrying log event MUST carry the complete post-change state, never a ba ### Host projection registry (`dsh-session-projection`, new package) -A light Service Definition package: the merge-extensible type map, the registry service, zod at the boundary. Capability-seam roles: domain host plugins provide projection units, carriers consume them, and neither knows the other. +A light Service Definition package: merge-extensible host-state and client-view type maps, the registry service, and zod validation for persisted state and client values. Capability-seam roles: domain host plugins provide projection units, carriers consume them, and neither knows the other. -What a domain registers is a **state-driven computation unit** — three pure functions plus declarations — never an opaque getter. The framework owns driving it (subscription, watermark, caching, and later checkpointing); the domain owns only the mathematics. Projections serve every business domain (session title, plan, goal, permission, todos); commands are merely one trigger path and hold no special position in this contract. +What a domain registers is a **state-driven computation unit** — a pure fold plus declarations and an optional client view — never an opaque getter. The framework owns driving it (subscription, watermark, caching, and later checkpointing); the domain owns only the computation. Projections serve every business domain (session title, plan, goal, permission, todos); commands are merely one trigger path and hold no special position in this contract. ```ts ignore-check -export interface SessionProjectionMap {} // the single type table for the whole chain +export interface SessionProjectionStateMap {} // host fold states +export interface SessionProjectionMap {} // client-visible whole values -export interface ProjectionDefinition { +export interface ProjectionDefinition { key: K - schema: ZodType // validates the payload before it leaves the host + stateSchema: ZodType + persist?: boolean // host-only units opt in; client-visible units always persist /** State for the empty log. */ init(): S /** Pure transition: previous state + one event → next state. The framework drives it; domains hold no subscriptions. */ apply(state: S, event: SessionEvent): S - /** State → wire payload (the read-side projection). */ - view(state: S): SessionProjectionMap[K] + /** Client view; omitted for host-only units. */ + wire?: K extends keyof SessionProjectionMap ? { + viewSchema: ZodType + view(state: S): SessionProjectionMap[K] + } : never /** State must be plain JSON (persisted-cache precondition); bump to invalidate persisted rows. */ stateVersion: number } @@ -49,7 +54,7 @@ declare module 'cordis' { } ``` -- Values are wire JSON payloads; the same map typed end to end (host unit, wire block, React hook) via `import type` — no second DTO table, no separate client-side "views" map. How a value is *rendered* is the slot system's business, never the projection layer's. +- `SessionProjectionStateMap` types host fold states; `SessionProjectionMap` remains the one client DTO table shared by the wire block and React hook via `import type`. A unit may remain host-only by omitting `wire`. How a client value is *rendered* is the slot system's business, never the projection layer's. The state/view split is specified by the [implemented state and client-view note](../../implemented/architecture/2026-08-19-session-projection-state-and-client-views.md). - **The host is the only place a projection is computed.** The framework drives every registered unit forward eagerly: each committed session event passes through `apply`; a unit uninterested in an event returns the same state reference, and an unchanged reference (`Object.is`) produces no downstream work. Clients never fold domain events — they receive finished values (baseline block + push frame below). This removes the double-implementation trap (plan's two-event fold written once, on the host) and any client-side domain code. - **State is always computed, never logged.** The log holds events only; the unit's state lives in the framework's per-session watermark cache (`{state, observedSeq}` per unit) and, in a later phase, in a **persisted projection cache** on the domain-KV storage seam: rows of `(sessionId, key, ver, seq, val)` (`ver` = the unit's `stateVersion`, `seq` = the watermark, `val` = the state JSON). A row is never wrong, only possibly stale — its `seq` says exactly how stale. The one read recipe, cold and live alike: take the cached state (or `init()`), forward-apply only the events past its watermark, `view` the result. Cold listings (every session's title across all workspaces) become an index read plus, at worst, a short tail replay; the session-persistence seam grows a read-from-seq primitive for that tail in the same later phase. Write policy: throttled (count/interval, configurable) plus two mandatory points — `turn/end` and detach (the live-to-cold moment). A crash between writes costs a longer tail replay, never a wrong value. - A domain's input event set is its own choice: todos folds `todo/write` alone; plan folds `plan/mode` plus its own `/plan` `command/run` records (see the plan section); goal folds `goal/change` metadata; session title folds its title events (retiring the bespoke `session/title` frame and the client's title-snapshot map — the fourth hand-rolled projection this seam absorbs). @@ -145,7 +150,7 @@ Infrastructure first; the three in-flight PRs are left untouched and re-target a **An opaque `get(agent)` provider contract** — rejected: with the computation model hidden inside the domain, the framework can never checkpoint the state, serve cold sessions (no agent, no loaded log — `get` has nothing to run against), or resume from a mid-log position. Registering the `(init, apply, view)` unit hands the framework the drive and keeps the domain to pure mathematics; a domain with host-side behavioral needs still keeps its own service subscriptions independently of the projection unit. -**A live-only overlay hook (`live?(agent, base)`) for plan's pending intent** — rejected: it existed solely because the user's plan *selection* was not in the log. Routing the selection through the standard command channel puts `command/run` on the account, pending becomes a pure replay quantity, and the projection contract stays exactly three pure functions. +**A live-only overlay hook (`live?(agent, base)`) for plan's pending intent** — rejected: it existed solely because the user's plan *selection* was not in the log. Routing the selection through the standard command channel puts `command/run` on the account, pending becomes a pure replay quantity, and the projection remains a pure fold with an optional client view. **Naming the registration API `registerFold`** — superseded by the unit contract: the registered object now genuinely is a fold, but `fold*` in this repo names pure `(events) => state` helper functions while this registry accepts a keyed, schema'd, versioned unit. Projection remains the event-sourcing term for the read-model role, and both #587's note title and #497's comments already use it. @@ -157,7 +162,7 @@ Infrastructure first; the three in-flight PRs are left untouched and re-target a **Hanging the registry off `ctx.apiProxy`** — rejected: session projections are not web-specific (TUI, ACP, headless are future consumers), and domain packages must not depend on the apiproxy package. The independent seam also deletes #587's type-only import edge from api-proxy into the plan package. -**A separate client-side `SessionProjectionViews` type table** — rejected: one `SessionProjectionMap` typed end to end is the wire-passthrough discipline (no second DTO vocabulary); values are JSON payloads and rendering belongs to slots. +**A second client DTO table** — rejected: `SessionProjectionMap` remains the single client vocabulary shared by wire and UI. `SessionProjectionStateMap` is not another client view table; it types host fold state so internal state may differ from the value sent to clients. **Event-broadcast collection instead of a registry walk** — rejected: async listeners cannot yield the single synchronous cut that makes `asOfSeq` one consistent snapshot across all keys; registries are this repo's shape for contributions (`ctx.tools`, prompt sections, slots). diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md index 00349ac46d..b2d2681177 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md @@ -24,22 +24,27 @@ Status: proposed ### host 侧投影注册表(`dsh-session-projection`,新包) -一个轻量的 Service Definition 包:merge-extensible 类型表、注册表服务、边界上的 zod 校验。能力 seam 的角色如下:领域 host 插件提供投影单元,载体消费这些单元,两侧互不相识。 +一个轻量的 Service Definition 包:merge-extensible 的 host 状态与客户端视图类型表、注册表服务,以及针对持久状态和客户端值的 zod 校验。能力 seam 的角色如下:领域 host 插件提供投影单元,载体消费这些单元,两侧互不相识。 -领域注册的是一个**状态驱动计算单元(state-driven computation unit)**——三个纯函数外加若干声明——绝不是一个不透明的 getter。驱动它是框架的职责(订阅、水位线(watermark)、缓存,以及后续的检查点机制),领域只负责数学本身。投影服务于所有业务领域(会话标题、plan、goal、权限、todos);命令只是其中一条触发路径,在本约定中没有任何特殊地位。 +领域注册的是一个**状态驱动计算单元(state-driven computation unit)**——纯折叠、若干声明及可选客户端视图——绝不是一个不透明的 getter。驱动它是框架的职责(订阅、水位线(watermark)、缓存,以及后续的检查点机制),领域只负责计算。投影服务于所有业务领域(会话标题、plan、goal、权限、todos);命令只是其中一条触发路径,在本约定中没有任何特殊地位。 ```ts ignore-check -export interface SessionProjectionMap {} // the single type table for the whole chain +export interface SessionProjectionStateMap {} // host fold states +export interface SessionProjectionMap {} // client-visible whole values -export interface ProjectionDefinition { +export interface ProjectionDefinition { key: K - schema: ZodType // validates the payload before it leaves the host + stateSchema: ZodType + persist?: boolean // host-only units opt in; client-visible units always persist /** State for the empty log. */ init(): S /** Pure transition: previous state + one event → next state. The framework drives it; domains hold no subscriptions. */ apply(state: S, event: SessionEvent): S - /** State → wire payload (the read-side projection). */ - view(state: S): SessionProjectionMap[K] + /** Client view; omitted for host-only units. */ + wire?: K extends keyof SessionProjectionMap ? { + viewSchema: ZodType + view(state: S): SessionProjectionMap[K] + } : never /** State must be plain JSON (persisted-cache precondition); bump to invalidate persisted rows. */ stateVersion: number } @@ -49,7 +54,7 @@ declare module 'cordis' { } ``` -- 值就是协议层的 JSON 载荷;同一张类型表经 `import type` 端到端贯通(host 侧单元、协议块、React 钩子)——没有第二张 DTO 表,也没有独立的客户端「views」表。值如何*渲染*是 slot 体系的事,永远不归投影层管。 +- `SessionProjectionStateMap` 描述 host 折叠状态;`SessionProjectionMap` 继续作为协议块和 React 钩子经 `import type` 共享的唯一客户端 DTO 表。单元省略 `wire` 即保持 host-only。客户端值如何*渲染*是 slot 体系的事,永远不归投影层管。状态/视图拆分见[已实现的状态与客户端视图记录](../../implemented/architecture/2026-08-19-session-projection-state-and-client-views.md)。 - **host 是投影唯一的计算地点。** 框架主动驱动(eager drive)每个已注册的单元:每个已提交的会话事件都经过 `apply`;对某事件不感兴趣的单元返回同一个状态引用,而引用未变(`Object.is`)就不产生任何下游工作。客户端从不折叠领域事件——它们收到的是成品值(基线块 + 下文的推送帧)。这消除了双重实现陷阱(plan 的双事件折叠只在 host 写一遍),也消除了一切客户端侧领域代码。 - **状态永远靠计算得出,绝不入日志。** 日志只存事件;单元的状态住在框架的按会话水位线缓存里(每单元一份 `{state, observedSeq}`),并在后续阶段进入 domain-KV 存储 seam 上的**持久投影缓存(persisted projection cache)**:形如 `(sessionId, key, ver, seq, val)` 的行(`ver` = 单元的 `stateVersion`,`seq` = 水位线,`val` = 状态 JSON)。一行永远不会是错的,至多是陈旧的——其 `seq` 精确说明陈旧到哪。冷读与活读共用同一套读取配方:取缓存状态(或 `init()`),只对超出其水位线的事件做正向 `apply`,再对结果做 `view`。冷列表(跨全部 workspace 列出每个会话的标题)变成一次索引读,至多外加一小段尾部回放;session-persistence seam 在同一后续阶段为这段尾部补一个按 seq 起读的原语。写入策略:节流(次数/间隔,可配置)外加两个强制点——`turn/end` 与 detach(由活转冷的时刻)。两次写入之间崩溃的代价是尾部回放更长一些,绝不会是值出错。 - 领域的输入事件集由领域自己选择:todos 只折叠 `todo/write`;plan 折叠 `plan/mode` 外加它自己的 `/plan` `command/run` 记录(见 plan 一节);goal 折叠 `goal/change` 元数据;会话标题折叠其标题事件(顺带下线专设的 `session/title` 帧与客户端的标题快照表——这是该 seam 收编的第四个手工投影)。 @@ -145,7 +150,7 @@ host 侧命令执行器(`packages/interaction/commands`)在调用处理器 **不透明的 `get(agent)` 提供方约定**——否决:计算模型藏在领域内部时,框架永远无法为状态做检查点、无法服务冷会话(没有 agent、没有已加载的日志——`get` 无处可跑)、也无法从日志中段续算。注册 `(init, apply, view)` 单元把驱动权交给框架,领域只留纯数学;有 host 侧行为需求的领域,其服务订阅照旧自持,与投影单元互不牵连。 -**为 plan 待定意图专设的仅实时叠加钩子(`live?(agent, base)`)**——不予采纳:它存在的唯一理由是用户的 plan *选择*不在日志里。让选择走标准命令通道后,`command/run` 上了账,待定态成为纯回放量,投影约定保持恰好三个纯函数。 +**为 plan 待定意图专设的仅实时叠加钩子(`live?(agent, base)`)**——不予采纳:它存在的唯一理由是用户的 plan *选择*不在日志里。让选择走标准命令通道后,`command/run` 上了账,待定态成为纯回放量,投影继续由纯折叠与可选客户端视图构成。 **把注册 API 命名为 `registerFold`**——已被单元约定取代:注册对象如今确实是一个折叠,但本仓库里 `fold*` 专指纯 `(events) => state` 辅助函数,而该注册表接收的是带 key、带 schema、带版本的单元。投影仍是事件溯源中指称读模型角色的术语,#587 的 Note 标题与 #497 的评论也都已在使用它。 @@ -157,7 +162,7 @@ host 侧命令执行器(`packages/interaction/commands`)在调用处理器 **把注册表挂到 `ctx.apiProxy` 名下**——不予采纳:会话投影并非 web 专属(TUI、ACP(Agent Client Protocol)、headless 都是未来消费方),且领域包不得依赖 apiproxy 包。独立 seam 还顺带删掉了 #587 从 api-proxy 指向 plan 包的 type-only 导入边。 -**独立的客户端 `SessionProjectionViews` 类型表**——不予采纳:一张 `SessionProjectionMap` 端到端贯通正是协议直通纪律(不设第二套 DTO 词汇);值就是 JSON 载荷,渲染归 slot 管。 +**第二张客户端 DTO 类型表**——不予采纳:`SessionProjectionMap` 仍是协议与 UI 共享的唯一客户端词汇。`SessionProjectionStateMap` 不是另一张客户端视图表;它描述 host 折叠状态,使内部状态可以不同于发往客户端的值。 **用事件广播收集、替代注册表遍历**——不予采纳:异步监听器给不出那个单一的同步切面,而正是它让 `asOfSeq` 成为横跨所有 key 的一致快照;注册表才是本仓库承接贡献的通行形状(`ctx.tools`、提示词片段、slot)。 diff --git a/.github/AGENTS.md b/.github/AGENTS.md index 0c1f0dc780..4efddc1bca 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -1,3 +1,3 @@ # AGENTS.md — GitHub Actions -Run jobs on Windows runners (`windows-*` labels) under native `pwsh`. The pull-request `windows` job is the deliberate exception: it runs Windows Node under Wine on hosted Linux and blocks `all checks passed`; `windows-native` runs automatically on `windows-2025` (or the self-hosted `[self-hosted, dsh-win-ci, windows]` pool under `DSH_CI_FAILOVER_WINDOWS=selfhosted`) but reports independently. The master `serial-windows` standby continuously validates the self-hosted failover target — see the [failover runbook](../.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md). +Run jobs on Windows runners (`windows-*` labels) under native `pwsh`. The pull-request `windows` job is the deliberate exception: it runs Windows Node under Wine on hosted Linux and blocks `all checks passed`; `windows-native` runs automatically on `windows-2025` (or the self-hosted `[self-hosted, dsh-win-ci, windows]` pool under `DSH_CI_FAILOVER_WINDOWS=selfhosted`) but reports independently. `ci.yml` is pull-request-only; the master `serial-windows` standby, the Linux `serial-linux-selfhosted` standby, the `wine-apt-cache` seeder, and the two manual runner benchmarks live in `ci-master.yml` (master-push + `workflow_dispatch`). Because `ci-master.yml` does not listen to `pull_request`, those master-only jobs never appear in PR check panels (a job a workflow defines for a given event is listed and shows `skipped` when its `if` is false); keeping them in a separate workflow is what stops PR check circles from showing gray segments. The master `serial-windows` standby continuously validates the self-hosted failover target — see the [failover runbook](../.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md). diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml new file mode 100644 index 0000000000..f324900c9e --- /dev/null +++ b/.github/workflows/ci-master.yml @@ -0,0 +1,407 @@ +name: CI master + +on: + push: + branches: [master] + workflow_dispatch: + inputs: + suite: + description: Manual CI suite to run + required: true + default: larger-runner-benchmark + type: choice + options: + - larger-runner-benchmark + - consolidated-runner-benchmark + +# A master push may carry only the two self-hosted standby drills and the Wine +# apt cache seeder; those drills outlast the interval between master merges, so +# push is exempt from cancellation (see ci-failover-runbook). workflow_dispatch +# keeps cancelling: a re-dispatched runner benchmark holds up to 12 larger +# runners for 15 minutes in this same group. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name != 'push' }} + +permissions: + contents: read + +env: + PRIMARY_NODE_VERSION: '24' + # CI runs must never report to the production telemetry endpoint baked + # into apps/cli/cordis.yml (AppCLIEntry disables the row when set). + DSH_TELEMETRY_DISABLED: '1' + +jobs: + # Master seeds the Wine apt-archive cache in the default-branch scope, + # which every pull request's windows job can restore; saves from + # pull-request runs are scoped to their own merge ref and help nobody + # else. Runs in seconds when the image version already has a cache. + wine-apt-cache: + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + name: wine apt cache + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Compose Wine apt cache key + id: wine-cache-key + run: echo "key=wine-debs-${ImageOS:-linux}-${ImageVersion:-v0}" >> "$GITHUB_OUTPUT" + + - uses: actions/cache@v4 + id: wine-cache + with: + path: ~/wine-debs + key: ${{ steps.wine-cache-key.outputs.key }} + + - name: Download the Wine dependency closure + if: steps.wine-cache.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends --download-only wine + mkdir -p "$HOME/wine-debs" + cp /var/cache/apt/archives/*.deb "$HOME/wine-debs/" + du -sh "$HOME/wine-debs" + + # Hot-standby drill for the in-house self-hosted pool: every master move + # re-runs the complete unsharded aggregate on the persistent 64-core VM, + # continuously proving that environment can take over a required lane if + # the hosted pools degrade (the switch is then setting the writer-manageable + # DSH_CI_FAILOVER_LINUX variable — see the failover runbook, no merge required). + # Push-triggered, so this lane always executes the base branch's own + # workflow definition. This workflow never listens to pull_request, so the + # drill does not appear in PR checks. No cache steps because the VM's + # persistent pnpm store and tool caches make them redundant (and saving here + # would poison the hosted cache namespace with self-hosted paths). + serial-linux-selfhosted: + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + name: serial / linux (self-hosted standby) + runs-on: [self-hosted, linux, x64, vm-backup] + steps: + # DSH_ARCHIVE_BASE_REF below compares the frozen-archive gate against + # github.event.before, so full history is required: depth 2 would miss it + # on multi-commit or force pushes; full fetch is cheap here because + # checkout resolves against the VM's local mirror. + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: pnpm/action-setup@v4 + with: + dest: ${{ runner.temp }}/setup-pnpm + + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Configure persistent pnpm store + run: echo "PNPM_CONFIG_STORE_DIR=$HOME/.local/share/pnpm/store" >> "$GITHUB_ENV" + + - name: Install (immutable) + run: pnpm install --frozen-lockfile + + # The persistent VM image owns Playwright's Linux system packages; this + # step also proves that browser provisioning remains usable for failover. + - name: Install Playwright Chromium + run: pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install chromium + + - name: Prepare bubblewrap (unrestrict userns) + run: bash scripts/prepare-ci-bubblewrap.sh + + - name: Run complete unsharded primary Node CI serially + env: + DSH_ARCHIVE_BASE_REF: ${{ github.event.before }} + DSH_COVERAGE_MAX_WORKERS: '1' + DSH_E2E_MAX_WORKERS: '1' + DSH_GATE_CONCURRENCY: '1' + DSH_OXLINT_THREADS: '1' + DSH_PUBLINT_CONCURRENCY: '1' + DSH_SNAPSHOT_MAX_CONCURRENCY: '1' + run: pnpm run check:ci:linux-primary + + # TODO(hosted-serial-ci): Re-enable the one remaining disabled hosted serial + # reference job (serial-macos) before release. Re-enabling serial-macos does + # not restore a Linux hosted-cache producer: decide whether to add a master + # seeder or remove the restore-only steps if cold starts become a concern. + serial-macos: + if: false + name: serial / macos + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + with: + dest: ${{ runner.temp }}/setup-pnpm + + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Install (immutable) + run: pnpm install --frozen-lockfile + + - name: Run complete unsharded primary Node CI serially + env: + DSH_COVERAGE_MAX_WORKERS: '1' + DSH_E2E_MAX_WORKERS: '1' + DSH_GATE_CONCURRENCY: '1' + DSH_PUBLINT_CONCURRENCY: '1' + DSH_SNAPSHOT_MAX_CONCURRENCY: '1' + run: pnpm run check:ci + + # Hot-standby drill for the in-house self-hosted Windows pool: every master + # move re-runs the complete unsharded Windows gate inventory on the persistent + # VM, continuously proving that environment can take over the required + # `windows` lane if the hosted pool degrades (the switch is setting the + # writer-manageable DSH_CI_FAILOVER_WINDOWS variable — see the failover + # runbook, no merge required). Push-triggered, so this lane always executes + # the base branch's own workflow definition. This workflow never listens to + # pull_request, so the drill does not appear in PR checks. No cache steps + # because the VM's persistent pnpm store and tool caches make them redundant + # (and saving here would poison the hosted cache namespace with self-hosted + # paths). + serial-windows: + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + name: serial / windows (self-hosted standby) + runs-on: [self-hosted, dsh-win-ci, windows] + timeout-minutes: 120 + steps: + - uses: actions/checkout@v6 + + - name: Enable Developer Mode (symlink support) + shell: pwsh + run: >- + reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" + /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" + + - uses: pnpm/action-setup@v4 + with: + dest: ${{ runner.temp }}/setup-pnpm + + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Configure persistent pnpm store + shell: pwsh + run: | + $storeRoot = "$env:LOCALAPPDATA\pnpm\store" + echo "PNPM_CONFIG_STORE_DIR=$storeRoot" >> $env:GITHUB_ENV + + - name: Install (immutable) + shell: pwsh + run: pnpm install --frozen-lockfile + + - name: Run complete unsharded Windows gate inventory serially + shell: pwsh + env: + DSH_COVERAGE_MAX_WORKERS: '1' + DSH_GATE_CONCURRENCY: '1' + DSH_PUBLINT_CONCURRENCY: '1' + run: pnpm run check:ci:windows-complete + + # Manual, bounded comparison of the actual critical Linux and Windows lanes. + # The named pools are restricted at the organization level to this repository. + larger-runner-benchmark: + if: github.event_name == 'workflow_dispatch' && inputs.suite == 'larger-runner-benchmark' + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + strategy: + fail-fast: false + max-parallel: 12 + matrix: + include: + - platform: linux + cores: '4' + runner: dsh-ubuntu-24-04-4core + workload: typecheck + - platform: linux + cores: '8' + runner: dsh-ubuntu-24-04-8core + workload: typecheck + - platform: linux + cores: '16' + runner: dsh-ubuntu-24-04-16core + workload: typecheck + - platform: linux + cores: '32' + runner: dsh-ubuntu-24-04-32core + workload: typecheck + - platform: linux + cores: '64' + runner: dsh-ubuntu-24-04-64core + workload: typecheck + - platform: linux + cores: '96' + runner: dsh-ubuntu-24-04-96core + workload: typecheck + - platform: windows + cores: '4' + runner: dsh-windows-2025-4core + workload: production-site + - platform: windows + cores: '8' + runner: dsh-windows-2025-8core + workload: production-site + - platform: windows + cores: '16' + runner: dsh-windows-2025-16core + workload: production-site + - platform: windows + cores: '32' + runner: dsh-windows-2025-32core + workload: production-site + - platform: windows + cores: '64' + runner: dsh-windows-2025-64core + workload: production-site + - platform: windows + cores: '96' + runner: dsh-windows-2025-96core + workload: production-site + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + with: + dest: ${{ runner.temp }}/setup-pnpm + + # The benchmark's Windows lanes deliberately skip the store cache like + # the independent native Windows job; an empty input disables caching. + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + cache: ${{ matrix.platform == 'linux' && 'pnpm' || '' }} + + - name: Report runner capacity + run: >- + node -e "const os = require('node:os'); + console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, + memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" + + - name: Install (immutable) + run: pnpm install --frozen-lockfile + + - name: Run critical Linux typecheck lane + if: matrix.platform == 'linux' + run: pnpm run typecheck + + - name: Run critical Windows production-site lane + if: matrix.platform == 'windows' + run: pnpm run docs:build + + # Manual comparison of the intended low-fanout topology. Linux runs the + # complete unsharded primary aggregate with bounded in-runner parallelism; + # Windows runs both blocking build targets concurrently through run-gates. + consolidated-runner-benchmark: + if: github.event_name == 'workflow_dispatch' && inputs.suite == 'consolidated-runner-benchmark' + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + strategy: + fail-fast: false + max-parallel: 12 + matrix: + include: + - platform: linux + cores: '4' + runner: dsh-ubuntu-24-04-4core + workers: '4' + - platform: linux + cores: '8' + runner: dsh-ubuntu-24-04-8core + workers: '8' + - platform: linux + cores: '16' + runner: dsh-ubuntu-24-04-16core + workers: '16' + - platform: linux + cores: '32' + runner: dsh-ubuntu-24-04-32core + workers: '32' + - platform: linux + cores: '64' + runner: dsh-ubuntu-24-04-64core + workers: '32' + - platform: linux + cores: '96' + runner: dsh-ubuntu-24-04-96core + workers: '32' + - platform: windows + cores: '4' + runner: dsh-windows-2025-4core + workers: '2' + - platform: windows + cores: '8' + runner: dsh-windows-2025-8core + workers: '2' + - platform: windows + cores: '16' + runner: dsh-windows-2025-16core + workers: '2' + - platform: windows + cores: '32' + runner: dsh-windows-2025-32core + workers: '2' + - platform: windows + cores: '64' + runner: dsh-windows-2025-64core + workers: '2' + - platform: windows + cores: '96' + runner: dsh-windows-2025-96core + workers: '2' + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + with: + dest: ${{ runner.temp }}/setup-pnpm + + # Unlike the larger-runner suite, both platforms cache the store here: + # the consolidated topology measures cache mechanics as workload. + - uses: actions/setup-node@v6 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + cache: pnpm + + - name: Report runner capacity + run: >- + node -e "const os = require('node:os'); + console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, + memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" + + - name: Install and prepare Linux + if: matrix.platform == 'linux' + run: | + pnpm install --frozen-lockfile & + install_pid=$! + bash scripts/prepare-ci-bubblewrap.sh & + sandbox_pid=$! + install_status=0 + wait "$install_pid" || install_status=$? + sandbox_status=0 + wait "$sandbox_pid" || sandbox_status=$? + if (( install_status != 0 )); then exit "$install_status"; fi + exit "$sandbox_status" + + - name: Install (immutable) + if: matrix.platform == 'windows' + shell: pwsh + run: pnpm install --frozen-lockfile + + - name: Run complete unsharded primary Node CI concurrently + if: matrix.platform == 'linux' + env: + DSH_COVERAGE_MAX_WORKERS: ${{ matrix.workers }} + DSH_GATE_CONCURRENCY: ${{ matrix.workers }} + DSH_OXLINT_THREADS: ${{ matrix.workers }} + DSH_PUBLINT_CONCURRENCY: ${{ matrix.workers }} + DSH_SNAPSHOT_MAX_CONCURRENCY: ${{ matrix.workers }} + run: pnpm run check:ci + + - name: Run blocking Windows builds concurrently + if: matrix.platform == 'windows' + shell: pwsh + env: + DSH_GATE_CONCURRENCY: ${{ matrix.workers }} + run: pnpm run check:ci:windows-blocking diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 741a6c4d5a..df3c4e7e8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,36 +1,7 @@ name: CI on: - push: - branches: [master] pull_request: - workflow_dispatch: - inputs: - suite: - description: Manual CI suite to run - required: true - default: larger-runner-benchmark - type: choice - options: - - larger-runner-benchmark - - consolidated-runner-benchmark - -# Cancel a superseded run on every event EXCEPT push. A push run carries the two -# self-hosted standby drills, which take longer than the interval between master -# merges, so cancelling supersedes a drill before it reaches a verdict and the -# lane yields no readiness evidence. Must be decided here: cancellation applies -# to the whole superseded run, so a job-level group cannot exempt its job. -# Negated rather than `== 'pull_request'` so workflow_dispatch keeps cancelling: -# a re-dispatched runner benchmark holds up to 12 larger runners for 15 minutes -# and shares this group with the drills on master, so queueing it would delay -# them. The guarantee is narrow — evaluated against the newly triggered run, so a -# dispatch on master still cancels a mid-flight drill, and a newer pending push -# displaces an older one. Bounds and rationale: -# .agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name != 'push' }} - permissions: contents: read @@ -40,13 +11,14 @@ env: # into apps/cli/cordis.yml (AppCLIEntry disables the row when set). DSH_TELEMETRY_DISABLED: '1' -jobs: +# Cancel a superseded pull-request run on a new push so a fresh head does not +# queue a second full 9-job run behind a stale one (paid enterprise runners +# would otherwise stack with no auto-cancellation). +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true - # TODO(hosted-serial-ci): Re-enable the one remaining disabled hosted serial - # reference job (serial-macos) before release. The self-hosted standby lane - # below remains active on every master push. Re-enabling serial-macos does not - # restore a Linux hosted-cache producer: decide whether to add a master seeder - # or remove the restore-only steps if cold starts become a concern. +jobs: # Three enterprise jobs isolate coverage, static analysis, and the # build-backed consumer tail. The consumer job owns the only Linux build so @@ -61,8 +33,9 @@ jobs: # vm-backup pool and re-running the failed jobs is the entire switch — # see .agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md. The # in-house pool's readiness is re-proven on every master push by the - # serial-linux-selfhosted standby lane below. The Windows failover switch is - # the separate DSH_CI_FAILOVER_WINDOWS variable on the windows-native job below. + # serial-linux-selfhosted standby lane in ci-master.yml. The Windows failover + # switch is the separate DSH_CI_FAILOVER_WINDOWS variable on the windows-native + # job below. node-24: if: github.event_name == 'pull_request' runs-on: >- @@ -376,9 +349,9 @@ jobs: restore-keys: | ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - # Master's wine-apt-cache job seeds the default-branch scope every pull - # request can read; a save from this job only reaches reruns of the - # same merge ref. + # Master's wine-apt-cache job in ci-master.yml seeds the default-branch + # scope every pull request can read; a save from this job only reaches + # reruns of the same merge ref. - name: Compose Wine apt cache key id: wine-cache-key run: echo "key=wine-debs-${ImageOS:-linux}-${ImageVersion:-v0}" >> "$GITHUB_OUTPUT" @@ -423,35 +396,6 @@ jobs: if: always() run: wineserver -k 2>/dev/null || true - # Master seeds the Wine apt-archive cache in the default-branch scope, - # which every pull request's windows job can restore; saves from - # pull-request runs are scoped to their own merge ref and help nobody - # else. Runs in seconds when the image version already has a cache. - wine-apt-cache: - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - name: wine apt cache - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Compose Wine apt cache key - id: wine-cache-key - run: echo "key=wine-debs-${ImageOS:-linux}-${ImageVersion:-v0}" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - id: wine-cache - with: - path: ~/wine-debs - key: ${{ steps.wine-cache-key.outputs.key }} - - - name: Download the Wine dependency closure - if: steps.wine-cache.outputs.cache-hit != 'true' - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends --download-only wine - mkdir -p "$HOME/wine-debs" - cp /var/cache/apt/archives/*.deb "$HOME/wine-debs/" - du -sh "$HOME/wine-debs" - # Every pull request also gets a real Windows-kernel signal. This job keeps # its own unmasked conclusion but is deliberately absent from # all-checks-passed.needs, so it never delays or changes that required @@ -510,358 +454,13 @@ jobs: shell: pwsh run: pnpm run check:ci:windows-complete - # Hot-standby drill for the in-house self-hosted pool: every master move - # re-runs the complete unsharded aggregate on the persistent 64-core VM, - # continuously proving that environment can take over a required lane if - # the hosted pools degrade (the switch is then setting the writer-manageable - # DSH_CI_FAILOVER_LINUX variable — see the failover runbook, no merge required). - # Push-triggered, so this lane always executes the base branch's own - # workflow definition. (Under failover, pull_request jobs do reach these - # runners with the PR merge ref's workflow — the boundary there is - # repository membership: private, forking disabled, Dependabot excluded.) - # Non-blocking for - # pull requests; no cache steps because the VM's persistent pnpm store and - # tool caches make them redundant (and saving here would poison the hosted - # cache namespace with self-hosted paths). - serial-linux-selfhosted: - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - name: serial / linux (self-hosted standby) - runs-on: [self-hosted, linux, x64, vm-backup] - steps: - # DSH_ARCHIVE_BASE_REF below compares the frozen-archive gate against - # github.event.before, so full history is required: depth 2 would miss it - # on multi-commit or force pushes; full fetch is cheap here because - # checkout resolves against the VM's local mirror. - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - uses: pnpm/action-setup@v4 - with: - dest: ${{ runner.temp }}/setup-pnpm - - - uses: actions/setup-node@v6 - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - - - name: Configure persistent pnpm store - run: echo "PNPM_CONFIG_STORE_DIR=$HOME/.local/share/pnpm/store" >> "$GITHUB_ENV" - - - name: Install (immutable) - run: pnpm install --frozen-lockfile - - # The persistent VM image owns Playwright's Linux system packages; this - # step also proves that browser provisioning remains usable for failover. - - name: Install Playwright Chromium - run: pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install chromium - - - name: Prepare bubblewrap (unrestrict userns) - run: bash scripts/prepare-ci-bubblewrap.sh - - - name: Run complete unsharded primary Node CI serially - env: - DSH_ARCHIVE_BASE_REF: ${{ github.event.before }} - DSH_COVERAGE_MAX_WORKERS: '1' - DSH_E2E_MAX_WORKERS: '1' - DSH_GATE_CONCURRENCY: '1' - DSH_OXLINT_THREADS: '1' - DSH_PUBLINT_CONCURRENCY: '1' - DSH_SNAPSHOT_MAX_CONCURRENCY: '1' - run: pnpm run check:ci:linux-primary - - # The one remaining disabled hosted serial reference job; see - # TODO(hosted-serial-ci) above. - serial-macos: - if: false - name: serial / macos - runs-on: macos-latest - steps: - - uses: actions/checkout@v6 - - - uses: pnpm/action-setup@v4 - with: - dest: ${{ runner.temp }}/setup-pnpm - - - uses: actions/setup-node@v6 - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - - - name: Install (immutable) - run: pnpm install --frozen-lockfile - - - name: Run complete unsharded primary Node CI serially - env: - DSH_COVERAGE_MAX_WORKERS: '1' - DSH_E2E_MAX_WORKERS: '1' - DSH_GATE_CONCURRENCY: '1' - DSH_PUBLINT_CONCURRENCY: '1' - DSH_SNAPSHOT_MAX_CONCURRENCY: '1' - run: pnpm run check:ci - - # Hot-standby drill for the in-house self-hosted Windows pool: every master - # move re-runs the complete unsharded Windows gate inventory on the persistent - # VM, continuously proving that environment can take over the required - # `windows` lane if the hosted pool degrades (the switch is setting the - # writer-manageable DSH_CI_FAILOVER_WINDOWS variable — see the failover - # runbook, no merge required). Push-triggered, so this lane always executes - # the base branch's own workflow definition. Non-blocking for pull requests; - # absent from all-checks-passed.needs by design — the required `windows` job - # owns the PR verdict. No cache steps because the VM's persistent pnpm store - # and tool caches make them redundant (and saving here would poison the - # hosted cache namespace with self-hosted paths). - serial-windows: - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - name: serial / windows (self-hosted standby) - runs-on: [self-hosted, dsh-win-ci, windows] - timeout-minutes: 120 - steps: - - uses: actions/checkout@v6 - - - name: Enable Developer Mode (symlink support) - shell: pwsh - run: >- - reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" - /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - - - uses: pnpm/action-setup@v4 - with: - dest: ${{ runner.temp }}/setup-pnpm - - - uses: actions/setup-node@v6 - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - - - name: Configure persistent pnpm store - shell: pwsh - run: | - $storeRoot = "$env:LOCALAPPDATA\pnpm\store" - echo "PNPM_CONFIG_STORE_DIR=$storeRoot" >> $env:GITHUB_ENV - - - name: Install (immutable) - shell: pwsh - run: pnpm install --frozen-lockfile - - - name: Run complete unsharded Windows gate inventory serially - shell: pwsh - env: - DSH_COVERAGE_MAX_WORKERS: '1' - DSH_GATE_CONCURRENCY: '1' - DSH_PUBLINT_CONCURRENCY: '1' - run: pnpm run check:ci:windows-complete - - # Manual, bounded comparison of the actual critical Linux and Windows lanes. - # The named pools are restricted at the organization level to this repository. - larger-runner-benchmark: - if: github.event_name == 'workflow_dispatch' && inputs.suite == 'larger-runner-benchmark' - runs-on: ${{ matrix.runner }} - timeout-minutes: 15 - strategy: - fail-fast: false - max-parallel: 12 - matrix: - include: - - platform: linux - cores: '4' - runner: dsh-ubuntu-24-04-4core - workload: typecheck - - platform: linux - cores: '8' - runner: dsh-ubuntu-24-04-8core - workload: typecheck - - platform: linux - cores: '16' - runner: dsh-ubuntu-24-04-16core - workload: typecheck - - platform: linux - cores: '32' - runner: dsh-ubuntu-24-04-32core - workload: typecheck - - platform: linux - cores: '64' - runner: dsh-ubuntu-24-04-64core - workload: typecheck - - platform: linux - cores: '96' - runner: dsh-ubuntu-24-04-96core - workload: typecheck - - platform: windows - cores: '4' - runner: dsh-windows-2025-4core - workload: production-site - - platform: windows - cores: '8' - runner: dsh-windows-2025-8core - workload: production-site - - platform: windows - cores: '16' - runner: dsh-windows-2025-16core - workload: production-site - - platform: windows - cores: '32' - runner: dsh-windows-2025-32core - workload: production-site - - platform: windows - cores: '64' - runner: dsh-windows-2025-64core - workload: production-site - - platform: windows - cores: '96' - runner: dsh-windows-2025-96core - workload: production-site - steps: - - uses: actions/checkout@v6 - - - uses: pnpm/action-setup@v4 - with: - dest: ${{ runner.temp }}/setup-pnpm - - # The benchmark's Windows lanes deliberately skip the store cache like - # the independent native Windows job; an empty input disables caching. - - uses: actions/setup-node@v6 - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - cache: ${{ matrix.platform == 'linux' && 'pnpm' || '' }} - - - name: Report runner capacity - run: >- - node -e "const os = require('node:os'); - console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, - memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" - - - name: Install (immutable) - run: pnpm install --frozen-lockfile - - - name: Run critical Linux typecheck lane - if: matrix.platform == 'linux' - run: pnpm run typecheck - - - name: Run critical Windows production-site lane - if: matrix.platform == 'windows' - run: pnpm run docs:build - - # Manual comparison of the intended low-fanout topology. Linux runs the - # complete unsharded primary aggregate with bounded in-runner parallelism; - # Windows runs both blocking build targets concurrently through run-gates. - consolidated-runner-benchmark: - if: github.event_name == 'workflow_dispatch' && inputs.suite == 'consolidated-runner-benchmark' - runs-on: ${{ matrix.runner }} - timeout-minutes: 15 - strategy: - fail-fast: false - max-parallel: 12 - matrix: - include: - - platform: linux - cores: '4' - runner: dsh-ubuntu-24-04-4core - workers: '4' - - platform: linux - cores: '8' - runner: dsh-ubuntu-24-04-8core - workers: '8' - - platform: linux - cores: '16' - runner: dsh-ubuntu-24-04-16core - workers: '16' - - platform: linux - cores: '32' - runner: dsh-ubuntu-24-04-32core - workers: '32' - - platform: linux - cores: '64' - runner: dsh-ubuntu-24-04-64core - workers: '32' - - platform: linux - cores: '96' - runner: dsh-ubuntu-24-04-96core - workers: '32' - - platform: windows - cores: '4' - runner: dsh-windows-2025-4core - workers: '2' - - platform: windows - cores: '8' - runner: dsh-windows-2025-8core - workers: '2' - - platform: windows - cores: '16' - runner: dsh-windows-2025-16core - workers: '2' - - platform: windows - cores: '32' - runner: dsh-windows-2025-32core - workers: '2' - - platform: windows - cores: '64' - runner: dsh-windows-2025-64core - workers: '2' - - platform: windows - cores: '96' - runner: dsh-windows-2025-96core - workers: '2' - steps: - - uses: actions/checkout@v6 - - - uses: pnpm/action-setup@v4 - with: - dest: ${{ runner.temp }}/setup-pnpm - - # Unlike the larger-runner suite, both platforms cache the store here: - # the consolidated topology measures cache mechanics as workload. - - uses: actions/setup-node@v6 - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - cache: pnpm - - - name: Report runner capacity - run: >- - node -e "const os = require('node:os'); - console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, - memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" - - - name: Install and prepare Linux - if: matrix.platform == 'linux' - run: | - pnpm install --frozen-lockfile & - install_pid=$! - bash scripts/prepare-ci-bubblewrap.sh & - sandbox_pid=$! - install_status=0 - wait "$install_pid" || install_status=$? - sandbox_status=0 - wait "$sandbox_pid" || sandbox_status=$? - if (( install_status != 0 )); then exit "$install_status"; fi - exit "$sandbox_status" - - - name: Install (immutable) - if: matrix.platform == 'windows' - shell: pwsh - run: pnpm install --frozen-lockfile - - - name: Run complete unsharded primary Node CI concurrently - if: matrix.platform == 'linux' - env: - DSH_COVERAGE_MAX_WORKERS: ${{ matrix.workers }} - DSH_GATE_CONCURRENCY: ${{ matrix.workers }} - DSH_OXLINT_THREADS: ${{ matrix.workers }} - DSH_PUBLINT_CONCURRENCY: ${{ matrix.workers }} - DSH_SNAPSHOT_MAX_CONCURRENCY: ${{ matrix.workers }} - run: pnpm run check:ci - - - name: Run blocking Windows builds concurrently - if: matrix.platform == 'windows' - shell: pwsh - env: - DSH_GATE_CONCURRENCY: ${{ matrix.workers }} - run: pnpm run check:ci:windows-blocking - # Single stable required check for branch protection: require "all checks # passed" instead of enumerating matrix legs whose names change as lanes and # node versions evolve. Every blocking job in THIS workflow must be listed in # `needs`. The required Wine job is listed as `windows`; `windows-native` is # deliberately absent so its independent result never delays or changes this - # verdict. (`needs` cannot reach across workflow files; e2e.yml stays its own - # check.) + # verdict. (`needs` cannot reach across workflow files; the master-only jobs in + # ci-master.yml are intentionally not part of this PR verdict.) # `if: always()` is load-bearing: without it a failed dependency # would SKIP this job, and GitHub counts a skipped required check as passing # — so this job always runs and fails on any non-success result, including diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3cd2f515ab..9c377deb79 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -107,7 +107,8 @@ jobs: run: pnpm run build:official # Real-API end-to-end tests only. The keyless gates (lint/typecheck/ - # coverage/snapshot/etc.) already run in ci.yml on every push/PR. + # coverage/snapshot/etc.) already run in ci.yml (pull requests) and + # ci-master.yml (master push standby). # DEEPSEEK_BASE_URL is pinned to the external API; the secret is scoped to # this step (and preflight) only — never exposed to checkout/setup/install. - name: E2E tests (real DeepSeek API) diff --git a/BRAND_GUIDELINES.i18n.yaml b/BRAND_GUIDELINES.i18n.yaml new file mode 100644 index 0000000000..c7147b4425 --- /dev/null +++ b/BRAND_GUIDELINES.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 BRAND_GUIDELINES.md +BRAND_GUIDELINES.md: 900cf7bcec70ff78c6f55ed535897239cd4648df +BRAND_GUIDELINES.zh.md: 94b239b6f6f4021f52da233cbefd23b4cc11f675 diff --git a/BRAND-GUIDELINES.md b/BRAND_GUIDELINES.md similarity index 55% rename from BRAND-GUIDELINES.md rename to BRAND_GUIDELINES.md index 7220a570a0..900cf7bcec 100644 --- a/BRAND-GUIDELINES.md +++ b/BRAND_GUIDELINES.md @@ -1,16 +1,7 @@ -# DeepSeek Harness 品牌素材使用规范 - -为了维护 DeepSeek Harness 生态的长期健康发展,避免用户混淆,方便用户对相关资源进行检索和识别,我们特别制定本规范,希望大家共同遵守: - -- 在项目的描述性文字中,您可以使用“DeepSeek Harness”真实、准确地说明您的项目与 DeepSeek Harness 的关系,例如“基于 DeepSeek Harness 构建”或“兼容 DeepSeek Harness”等。这类说明符合许可证的要求,也有助于用户理解项目的定位。 -- 如果您希望项目名称能体现与 DeepSeek Harness 生态的关联,我们建议使用缩写的 **“DSH”** 标识来命名,这样既清晰又便于社区内的交流。 -- 在项目命名时,请避免直接使用完整的 **“DeepSeek Harness”** 商标。**“DeepSeek Harness”** 是深度求索公司的注册商标,未经授权用于项目名,容易引发用户的误解和混淆,从而影响整个生态的清晰度。同时,也可能涉及商标侵权行为。 -- 此外,请您避免在宣传或展示时,以容易引起误解的方式使用官方品牌素材,以免让用户产生官方背书、合作或授权等不实印象。 - -我们相信,一个清晰、有序的社区环境,能让每一位开发者的努力都更容易被看见和认可。对于少数不符合上述规范的情况,我们可能会联系相关方进行适当的调整,以维护生态整体的秩序。感谢大家的理解与支持,让我们一起构建一个更友好、更可持续发展的开源社区。 - # DeepSeek Harness Brand Asset Usage Guidelines +English | [中文](BRAND_GUIDELINES.zh.md) + To maintain the long\-term healthy development of the DeepSeek Harness ecosystem, avoid user confusion, and facilitate the retrieval and identification of related resources, we have established these specifications and hope that everyone will adhere to them: - In your project's descriptive text, you may use "DeepSeek Harness" to truthfully and accurately describe your project's relationship with DeepSeek Harness, for example, "built on DeepSeek Harness" or "compatible with DeepSeek Harness\." Such descriptions comply with license requirements and help users understand your project's positioning\. @@ -19,4 +10,3 @@ To maintain the long\-term healthy development of the DeepSeek Harness ecosystem - Additionally, please avoid using official brand materials in your promotions or presentations in a way that could cause misunderstanding, so as not to give users the false impression of official endorsement, cooperation, or authorization\. We believe that a clear and orderly community environment will make every developer's efforts more visible and more readily recognized\. For the few cases that do not comply with the above specifications, we may contact the relevant parties to make appropriate adjustments in order to maintain the overall order of the ecosystem\. Thank you for your understanding and support—let us work together to build a more friendly and sustainable open\-source community\. - diff --git a/BRAND_GUIDELINES.zh.md b/BRAND_GUIDELINES.zh.md new file mode 100644 index 0000000000..94b239b6f6 --- /dev/null +++ b/BRAND_GUIDELINES.zh.md @@ -0,0 +1,12 @@ +# DeepSeek Harness 品牌素材使用规范 + +[English](BRAND_GUIDELINES.md) | 中文 + +为了维护 DeepSeek Harness 生态的长期健康发展,避免用户混淆,方便用户对相关资源进行检索和识别,我们特别制定本规范,希望大家共同遵守: + +- 在项目的描述性文字中,您可以使用“DeepSeek Harness”真实、准确地说明您的项目与 DeepSeek Harness 的关系,例如“基于 DeepSeek Harness 构建”或“兼容 DeepSeek Harness”等。这类说明符合许可证的要求,也有助于用户理解项目的定位。 +- 如果您希望项目名称能体现与 DeepSeek Harness 生态的关联,我们建议使用缩写的 **“DSH”** 标识来命名,这样既清晰又便于社区内的交流。 +- 在项目命名时,请避免直接使用完整的 **“DeepSeek Harness”** 商标。**“DeepSeek Harness”** 是深度求索公司的注册商标,未经授权用于项目名,容易引发用户的误解和混淆,从而影响整个生态的清晰度。同时,也可能涉及商标侵权行为。 +- 此外,请您避免在宣传或展示时,以容易引起误解的方式使用官方品牌素材,以免让用户产生官方背书、合作或授权等不实印象。 + +我们相信,一个清晰、有序的社区环境,能让每一位开发者的努力都更容易被看见和认可。对于少数不符合上述规范的情况,我们可能会联系相关方进行适当的调整,以维护生态整体的秩序。感谢大家的理解与支持,让我们一起构建一个更友好、更可持续发展的开源社区。 diff --git a/apps/cli/package.json b/apps/cli/package.json index ae07ed5df5..30e6f5da57 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh", "description": "dsh CLI: profile boot, plugin management, and the browser UI alias", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/apps/cli/reference/README.i18n.yaml b/apps/cli/reference/README.i18n.yaml index a5851993dc..68d249bee0 100644 --- a/apps/cli/reference/README.i18n.yaml +++ b/apps/cli/reference/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 apps/cli/reference/README.md -README.md: e60f9d9e00dc77c8b2f22edcba67f9e8f3ba2f07 -README.zh.md: a99531a67d447039803a7a8248f2cc30a19d9be9 +README.md: dfddd177a78c348793d3e5c2d290fa62c5ac850b +README.zh.md: 8fc28bd8c08581236bd28cfe16942fb05d1659c3 diff --git a/apps/cli/reference/README.md b/apps/cli/reference/README.md index e60f9d9e00..dfddd177a7 100644 --- a/apps/cli/reference/README.md +++ b/apps/cli/reference/README.md @@ -80,7 +80,7 @@ Process shutdown gives the plugin tree up to five seconds to dispose. The first All modes treat the invoking directory as the default workspace root, load applicable `AGENTS.md` or `CLAUDE.md` instructions with a 65,536-byte render budget, and use an in-memory SQLite session content index. Every profile boot watches valid edits of both `cordis.patch.yml` layers (profile and home) and reapplies them transactionally; a one-shot surface exits through its bounded shutdown, which disposes the watchers. -New sessions default to the `workspace-write` permission preset. Bash and filesystem mutations are restricted to the session workspace and platform temporary roots; reads, network access, and process visibility are not confined. `DSH_PERMISSION_MODE` changes the process fallback. Stored General-settings permissions affect later Web sessions, not an already-open one. +New sessions default to the `workspace-write` permission preset. Bash and filesystem mutations are restricted to the session workspace and platform temporary roots; reads and network access are not confined, while process visibility depends on the selected sandbox backend — bwrap runs commands in a private PID namespace that hides host processes, and Landlock and Seatbelt leave host process visibility unchanged. `DSH_PERMISSION_MODE` changes the process fallback. Stored General-settings permissions affect later Web sessions, not an already-open one. `DSH_TOOLS_MODE` selects `native`, `code`, or `both` for the process; another value fails at boot. The shipped `minimal` agent preset keeps that deployment presentation, fixes the complete system prompt to `You are a helpful software engineer assistant.`, and composes only persistent `bash` plus `str_replace_editor`. Select 极简模式 when creating a Web session; every other prompt section and model-facing plugin remains absent from that agent while the shared browser, workspace, persistence, sandbox, and permission host stays in place. diff --git a/apps/cli/reference/README.zh.md b/apps/cli/reference/README.zh.md index a99531a67d..8fc28bd8c0 100644 --- a/apps/cli/reference/README.zh.md +++ b/apps/cli/reference/README.zh.md @@ -80,7 +80,7 @@ dsh web --help 所有模式都将运行命令时所在的目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md` 或 `CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。每次启动 profile 时,系统都会监视 profile 与 home 两个 `cordis.patch.yml` 配置层的有效变更,并以事务方式重新应用;一次性运行模式通过有界关闭流程退出,该流程会先 dispose 监视器。 -新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取、网络访问和进程可见性不受限制。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。 +新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取和网络访问不受限制,进程可见性则取决于所选沙箱后端——bwrap 在私有 PID 命名空间中运行命令并隐藏宿主进程,Landlock 与 Seatbelt 保持宿主进程可见性不变。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。 `DSH_TOOLS_MODE` 为进程选择 `native`、`code` 或 `both`;其他值会导致启动失败。随附的 `minimal` agent preset 会保留该部署的呈现方式,将完整系统提示词固定为 `You are a helpful software engineer assistant.`,并且仅组合持久 `bash` 和 `str_replace_editor`。创建 Web 会话时请选择极简模式;该 agent 不包含任何其他提示词段落或面向模型的插件,而共享的浏览器、workspace、持久化、沙箱与权限宿主保持不变。 diff --git a/apps/web/package.json b/apps/web/package.json index db7a5a786d..581ba55672 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-frontend", "description": "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/apps/web/tests/access-confirmation.e2e.ts b/apps/web/tests/access-confirmation.e2e.ts index a173f8399e..414aacfb60 100644 --- a/apps/web/tests/access-confirmation.e2e.ts +++ b/apps/web/tests/access-confirmation.e2e.ts @@ -50,13 +50,13 @@ describe('web e2e: Full access confirmation', () => { const access = page.locator('button[aria-label^="访问模式"]').first() await access.waitFor({ timeout: 10_000 }) - expect(await access.getAttribute('aria-label')).toBe('访问模式,当前:Workspace Write') + expect(await access.getAttribute('aria-label')).toBe('访问模式,当前:可写入工作区') await access.click() - await page.getByRole('menuitem', { name: 'Full access' }).click() - const dialog = page.getByRole('dialog', { name: '确认启用 Full access?' }) + await page.getByRole('menuitem', { name: '完全权限' }).click() + const dialog = page.getByRole('dialog', { name: '确认启用完全权限?' }) await dialog.waitFor({ timeout: 10_000 }) - const enable = dialog.getByRole('button', { name: '启用 Full access' }) + const enable = dialog.getByRole('button', { name: '启用完全权限' }) expect(await enable.isDisabled()).toBe(true) // The modal is in this page's body (not a native/new window) and escapes @@ -69,7 +69,7 @@ describe('web e2e: Full access confirmation', () => { expect(await enable.isEnabled()).toBe(true) await enable.click() await expect.poll(() => access.getAttribute('aria-label'), { timeout: 10_000 }) - .toBe('访问模式,当前:Full access') + .toBe('访问模式,当前:完全权限') expect(await dialog.count()).toBe(0) expect(tripwire.pageErrors).toEqual([]) }, 60_000) diff --git a/apps/web/tests/settings-chrome.e2e.ts b/apps/web/tests/settings-chrome.e2e.ts index 61f5af88c5..cf2e4b9d10 100644 --- a/apps/web/tests/settings-chrome.e2e.ts +++ b/apps/web/tests/settings-chrome.e2e.ts @@ -62,7 +62,7 @@ describe('web e2e: settings modal and General preferences', () => { expect(await trigger.getAttribute('aria-expanded')).toBe('true') // General is active by default; Permission, Language and Appearance are functional. expect(await dialog.getByRole('button', { name: '通用设置' }).getAttribute('aria-current')).toBe('true') - await dialog.getByRole('button', { name: 'Workspace Write' }).waitFor({ timeout: 10_000 }) + await dialog.getByRole('button', { name: '可写入工作区' }).waitFor({ timeout: 10_000 }) await expect.poll(() => dialog.getByText('语言', { exact: true }).count(), { timeout: 5_000 }).toBe(1) await expect.poll(() => dialog.getByText('外观', { exact: true }).count(), { timeout: 5_000 }).toBe(1) const openDocument = dialog.getByRole('button', { name: '打开配置文件' }) @@ -135,44 +135,44 @@ describe('web e2e: settings modal and General preferences', () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-settings-permission')) const existing = scaffold.ctx.sessions.create(SessionId('settings-permission-before')) expect(existing.events.find(event => event.type === 'permission/preset')?.data) - .toEqual({ preset: 'workspace-write' }) + .toEqual({ preset: 'workspace-write', origin: 'default' }) await page.getByRole('button', { name: '设置', exact: true }).click() const dialog = page.getByRole('dialog', { name: '设置' }) await dialog.waitFor({ timeout: 10_000 }) - const selector = dialog.getByRole('button', { name: 'Workspace Write' }) + const selector = dialog.getByRole('button', { name: '可写入工作区' }) await selector.waitFor({ timeout: 10_000 }) await expect.poll(() => selector.isEnabled(), { timeout: 5_000 }).toBe(true) await selector.click() - await page.getByRole('menuitem', { name: 'Read Only' }).click() - await dialog.getByRole('button', { name: 'Read Only' }).waitFor({ timeout: 10_000 }) + await page.getByRole('menuitem', { name: '仅可查看' }).click() + await dialog.getByRole('button', { name: '仅可查看' }).waitFor({ timeout: 10_000 }) const document = await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8') expect(document).toContain('permission:') expect(document).toContain('defaultPreset: read-only') expect(existing.events.find(event => event.type === 'permission/preset')?.data) - .toEqual({ preset: 'workspace-write' }) + .toEqual({ preset: 'workspace-write', origin: 'default' }) const created = scaffold.ctx.sessions.create(SessionId('settings-permission-after')) expect(created.events.map(event => [event.type, event.data])).toEqual([ - ['permission/preset', { preset: 'read-only' }], + ['permission/preset', { preset: 'read-only', origin: 'default' }], ['sandbox/mode', { mode: 'read-only' }], ['approval/policy', { policy: 'ask' }], ]) - await dialog.getByRole('button', { name: 'Read Only' }).click() - await page.getByRole('menuitem', { name: 'Full access' }).click() - const confirmation = page.getByRole('dialog', { name: '确认启用 Full access?' }) - const enable = confirmation.getByRole('button', { name: '启用 Full access' }) + await dialog.getByRole('button', { name: '仅可查看' }).click() + await page.getByRole('menuitem', { name: '完全权限' }).click() + const confirmation = page.getByRole('dialog', { name: '确认启用完全权限?' }) + const enable = confirmation.getByRole('button', { name: '启用完全权限' }) expect(await enable.isDisabled()).toBe(true) await confirmation.getByRole('checkbox').click() await enable.click() - await dialog.getByRole('button', { name: 'Full access' }).waitFor({ timeout: 10_000 }) + await dialog.getByRole('button', { name: '完全权限' }).waitFor({ timeout: 10_000 }) const confirmedDocument = await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8') expect(confirmedDocument).toContain('defaultPreset: danger-full-access') const confirmed = scaffold.ctx.sessions.create(SessionId('settings-permission-confirmed')) expect(confirmed.events.map(event => [event.type, event.data])).toEqual([ - ['permission/preset', { preset: 'danger-full-access' }], + ['permission/preset', { preset: 'danger-full-access', origin: 'default' }], ['sandbox/mode', { mode: 'danger-full-access' }], ['approval/policy', { policy: 'never' }], ]) diff --git a/apps/web/tests/snapshots/access-confirmation/ui.expected.md b/apps/web/tests/snapshots/access-confirmation/ui.expected.md index 1287e6e565..c083b3aea0 100644 --- a/apps/web/tests/snapshots/access-confirmation/ui.expected.md +++ b/apps/web/tests/snapshots/access-confirmation/ui.expected.md @@ -1,10 +1,10 @@ -- dialog "确认启用 Full access?": - - heading "确认启用 Full access?" [level=2] +- dialog "确认启用完全权限?": + - heading "确认启用完全权限?" [level=2] - button "Close": - img - img - - paragraph: 启用 Full access 后,agent 将减少确认步骤,并且可以直接执行更多操作,包括敏感操作、文件修改或外部命令。仅建议在你信任当前任务时使用。 + - paragraph: 启用完全权限后,智能体将减少确认步骤,并且可以直接执行更多操作,包括敏感操作、文件修改或外部命令。仅建议在你信任当前任务时使用。 - checkbox "我已了解风险,并愿意继续" - text: 我已了解风险,并愿意继续 - button "取消" - - button "启用 Full access" [disabled] + - button "启用完全权限" [disabled] diff --git a/apps/web/tests/snapshots/settings-chrome/dialog.expected.md b/apps/web/tests/snapshots/settings-chrome/dialog.expected.md index 89cff5df3f..00dcd44f53 100644 --- a/apps/web/tests/snapshots/settings-chrome/dialog.expected.md +++ b/apps/web/tests/snapshots/settings-chrome/dialog.expected.md @@ -22,8 +22,8 @@ - text: 标准模式 - img - text: 权限 选择新会话的默认权限模式 - - button "Workspace Write": - - text: Workspace Write + - button "可写入工作区": + - text: 可写入工作区 - img - text: 语言 - button "中文": diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index f4d299db18..27c511bca5 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.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 docs/architecture.md -architecture.md: 2a51992a4f99d1827259c0a2ca2daa309f0ddd95 -architecture.zh.md: d1629a088ed8d2fe61a7e20ab2ab1fd3709abd50 +architecture.md: 622d074c3d873181d764cfe53f64485a2c2e0372 +architecture.zh.md: 2b38d9fb71fd358b78608680208c6071e59db89e diff --git a/docs/architecture.md b/docs/architecture.md index 2a51992a4f..622d074c3d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -101,7 +101,7 @@ A **seam** is a swappable capability with three roles: a **Service Definition** Seams are why one provider swap changes the whole product. Filesystem and subprocess providers share one execution world, so pointing them at a remote sandbox moves Bash, PTY, and LSP with them, with no provider forks. [Subagent providers](subsystems/subagent.md) vary just as widely behind one interface, from a fresh child agent to a delegated turn in another product. -[Experimental Agent Teams](subsystems/team.md) is a private opt-in coordination seam on `ctx.teams`, with a durable roster, task board, and mailbox layered over continuable subagents. +[Experimental Agent Teams](subsystems/agent-team.md) is a private opt-in coordination seam on `ctx.agentTeams`, with a durable roster, task board, and mailbox layered over continuable subagents. ## Where new behavior goes diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index d1629a088e..2b38d9fb71 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -105,7 +105,7 @@ turn/end seam 正是替换一个提供方就能改变整个产品的原因。文件系统与进程提供方共享同一个执行世界,因此把它们指向远程沙箱,也就把 Bash、PTY 和 LSP 一并搬了过去,无需提供方专用 fork。[subagent 提供方](subsystems/subagent.md)在同一个接口之后同样千差万别,从新建一个子 agent,到把一个轮次委派给另一个产品。 -[实验性 Agent Teams](subsystems/team.md) 是 `ctx.teams` 上的私有显式启用协作 seam,在可继续 subagent 之上提供持久 roster、任务板和 mailbox。 +[实验性 Agent Teams](subsystems/agent-team.md) 是 `ctx.agentTeams` 上的私有显式启用协作 seam,在可继续 subagent 之上提供持久 roster、任务板和 mailbox。 ## 新行为的归属位置 diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index 12740a7ebd..e696bc3423 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.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 docs/capability-seams.md -capability-seams.md: 23fc498cc280518489aadbe752a1702d43f17972 -capability-seams.zh.md: 0a8822bc8d1f0296ac6698b59fda60666faaf54a +capability-seams.md: a84a6d6e0836524e1f39f2fd067ae1f6570741a5 +capability-seams.zh.md: b6b0aba72729f9b9b752347ab00cd76a45c248d9 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 23fc498cc2..a84a6d6e08 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -160,9 +160,9 @@ flowchart LR pkg_subagent_dsh_sdk["subagent-dsh-sdk"] pkg_tool_subagent_control["tool-subagent-control"] pkg_tool_ralph["tool-ralph"] - pkg_team["team"] - svc_teams["ctx.teams
Agent Teams coordination domain"] - pkg_tool_team["tool-team"] + pkg_agent_team["agent-team"] + svc_agentTeams["ctx.agentTeams
Agent Teams coordination domain"] + pkg_tool_agent_team["tool-agent-team"] pkg_jobs["jobs"] svc_jobs["ctx.jobs
Background job registry"] pkg_jobs_local["jobs-local"] @@ -204,6 +204,7 @@ flowchart LR pkg_agent_default_model --> svc_agentDefaultModel pkg_agent_loop --> svc_agentLoop pkg_agent_presets --> svc_agentPresets + pkg_agent_team --> svc_agentTeams pkg_api_gateway --> svc_typertGateway pkg_apiproxy --> svc_apiProxy pkg_approval --> svc_approval @@ -287,7 +288,6 @@ flowchart LR pkg_subprocess_e2b --> svc_subprocess pkg_subprocess_local --> svc_subprocess pkg_system_prompt --> svc_systemPrompt - pkg_team --> svc_teams pkg_terminal --> svc_terminals pkg_terminal_bash --> svc_terminals pkg_token_meter --> svc_tokenMeter @@ -306,6 +306,7 @@ flowchart LR svc_agentDefaultModel --> pkg_headless svc_agentDefaultModel --> pkg_host_apiproxy svc_agentLoop --> pkg_agent_spine_demo + svc_agentTeams --> pkg_tool_agent_team svc_agents --> pkg_acp svc_agents --> pkg_agent_loop svc_agents --> pkg_subagent_inprocess @@ -392,7 +393,6 @@ flowchart LR svc_systemPrompt --> pkg_tool_terminal svc_systemPrompt --> pkg_tool_web svc_systemPrompt --> pkg_tools - svc_teams --> pkg_tool_team svc_terminals --> pkg_tool_terminal svc_tokenMeter --> pkg_compaction_basic svc_toolResultPruner --> pkg_compaction_basic @@ -467,7 +467,7 @@ flowchart LR | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-e2b`](../packages/e2b/fs-e2b) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate. | | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. | -| `ctx.teams` | `core` | [`team`](../packages/experimental/team) | - | [`tool-team`](../packages/experimental/tool-team) | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-team contributes the scoped model policy and controls. | +| `ctx.agentTeams` | `core` | `agent-team` | - | `tool-agent-team` | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes the scoped model policy and controls. | | `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | - | Producers (background bash, PTY sends, and subagent delegations) register running work; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. | | `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. | | `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | The backend saves oversized tool text and returns a model-facing locator plus retrieval hint; spill-policy is the tools/post-execute consumer that decides when to spill. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 0a8822bc8d..b6b0aba727 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -162,9 +162,9 @@ flowchart LR pkg_subagent_dsh_sdk["subagent-dsh-sdk"] pkg_tool_subagent_control["tool-subagent-control"] pkg_tool_ralph["tool-ralph"] - pkg_team["team"] - svc_teams["ctx.teams
Agent Teams coordination domain"] - pkg_tool_team["tool-team"] + pkg_agent_team["agent-team"] + svc_agentTeams["ctx.agentTeams
Agent Teams coordination domain"] + pkg_tool_agent_team["tool-agent-team"] pkg_jobs["jobs"] svc_jobs["ctx.jobs
Background job registry"] pkg_jobs_local["jobs-local"] @@ -206,6 +206,7 @@ flowchart LR pkg_agent_default_model --> svc_agentDefaultModel pkg_agent_loop --> svc_agentLoop pkg_agent_presets --> svc_agentPresets + pkg_agent_team --> svc_agentTeams pkg_api_gateway --> svc_typertGateway pkg_apiproxy --> svc_apiProxy pkg_approval --> svc_approval @@ -289,7 +290,6 @@ flowchart LR pkg_subprocess_e2b --> svc_subprocess pkg_subprocess_local --> svc_subprocess pkg_system_prompt --> svc_systemPrompt - pkg_team --> svc_teams pkg_terminal --> svc_terminals pkg_terminal_bash --> svc_terminals pkg_token_meter --> svc_tokenMeter @@ -308,6 +308,7 @@ flowchart LR svc_agentDefaultModel --> pkg_headless svc_agentDefaultModel --> pkg_host_apiproxy svc_agentLoop --> pkg_agent_spine_demo + svc_agentTeams --> pkg_tool_agent_team svc_agents --> pkg_acp svc_agents --> pkg_agent_loop svc_agents --> pkg_subagent_inprocess @@ -394,7 +395,6 @@ flowchart LR svc_systemPrompt --> pkg_tool_terminal svc_systemPrompt --> pkg_tool_web svc_systemPrompt --> pkg_tools - svc_teams --> pkg_tool_team svc_terminals --> pkg_tool_terminal svc_tokenMeter --> pkg_compaction_basic svc_toolResultPruner --> pkg_compaction_basic @@ -469,7 +469,7 @@ flowchart LR | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-e2b`](../packages/e2b/fs-e2b) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs 通过 ctx.fs 执行读取/写入/编辑;fs-sandbox 按共享沙箱模式限制变更;fs-observation-policy 通过 fs/* 事件门禁贡献基于观测状态的检查。 | | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 提供方实现传输;该服务还负责可选的、基于 Activation 的延续编排,tool-subagent 选择一次性或可延续委派,tool-subagent-control 传递后续消息,而 tool-ralph 要求一条全新的结构化输出路由。 | -| `ctx.teams` | `core` | [`team`](../packages/experimental/team) | - | [`tool-team`](../packages/experimental/tool-team) | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG 与 continuable child 生命周期;tool-team 提供作用域化模型策略和控制工具。 | +| `ctx.agentTeams` | `core` | `agent-team` | - | `tool-agent-team` | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供作用域化模型策略和控制工具。 | | `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | - | 生产方(后台 bash、PTY 发送和 subagent 委派)登记正在运行的工作;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 | | `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | 搜索和抓取提供方注册到同一个 ctx.web seam;tool-web 负责稳定的面向模型名称。 | | `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | 后端保存过大的工具文本,并返回面向模型的定位信息和取回提示;spill-policy 是 tools/post-execute 消费方,负责决定何时 spill。 | diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index d71dc82630..8e25d362b2 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.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 docs/config-catalog.md -config-catalog.md: 3e4d4c733ded0bb170cd1e34161f66e87c82f690 -config-catalog.zh.md: 04c54fb6bad9c14d94652e76405e42756291ed1e +config-catalog.md: 183f2ea683f553313ed04f06866d33034167d539 +config-catalog.zh.md: 68641ea8c0dcc3e2d58ad66e7e76805c18426926 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 3e4d4c733d..183f2ea683 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -587,6 +587,48 @@ export interface Config { Source: [`packages/e2b/e2b/src/index.ts:43`](../packages/e2b/e2b/src/index.ts) + + +## `@deepseek-ai/dsh-experimental-agent-team` + +Requires: `agents` · `sessions` · `sessionPersistence` · `subagents` + +```ts config-catalog +/** Team-service deployment limits. */ +export interface Config { + /** Maximum immutable teammate names retained by one Team. */ + readonly maxMembers?: number + /** Maximum non-deleted tasks retained by one Team. */ + readonly maxTasks?: number + /** Maximum queued-minus-delivered messages for one target member. */ + readonly maxPendingMessagesPerMember?: number + /** Maximum UTF-8 bytes in one complete sender-framed delivery. */ + readonly maxMessageBytes?: number + /** Maximum milliseconds allowed for Team-owned runtime disposal. */ + readonly disposalTimeoutMs?: number +} +``` + +Source: [`packages/experimental/agent-team/src/types.ts:125`](../packages/experimental/agent-team/src/types.ts) + + + +## `@deepseek-ai/dsh-experimental-tool-agent-team` + +Requires: `agents` · `agentTeams` · `tools` · `systemPrompt` + +```ts config-catalog +/** Tool routing configuration. */ +export interface Config { + /** Continuable-subagent provider used for fresh teammates. */ + readonly freshProvider?: string + /** Continuable-subagent provider used for completed-prefix fork teammates. */ + readonly forkProvider?: string +} +``` + +Source: [`packages/experimental/tool-agent-team/src/index.ts:17`](../packages/experimental/tool-agent-team/src/index.ts) + ## `@deepseek-ai/dsh-file-reference-local` @@ -1395,8 +1437,9 @@ export interface Config { */ presets?: Record /** - * Default for new sessions. When omitted, the preset matching the composed - * sandbox and approval defaults is used. + * Default for fresh sessions and eligible confirmed blank reuse. When + * omitted, the preset matching the composed sandbox and approval defaults + * is used. */ defaultPreset?: string } @@ -1416,7 +1459,7 @@ export interface PresetSpec { Depends on: [`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md) -Source: [`packages/interaction/permission-presets/src/index.ts:140`](../packages/interaction/permission-presets/src/index.ts) +Source: [`packages/interaction/permission-presets/src/index.ts:168`](../packages/interaction/permission-presets/src/index.ts) @@ -1456,7 +1499,7 @@ export interface PlanModeConfig { } ``` -Source: [`packages/plan/plan-mode/src/index.ts:71`](../packages/plan/plan-mode/src/index.ts) +Source: [`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts) @@ -2334,30 +2377,6 @@ export interface Config { Source: [`packages/core/system-prompt/src/index.ts:186`](../packages/core/system-prompt/src/index.ts) - - -## `@deepseek-ai/dsh-team` - -Requires: `agents` · `sessions` · `sessionPersistence` · `subagents` - -```ts config-catalog -/** Team-service deployment limits. */ -export interface Config { - /** Maximum immutable teammate names retained by one Team. */ - readonly maxMembers?: number - /** Maximum non-deleted tasks retained by one Team. */ - readonly maxTasks?: number - /** Maximum queued-minus-delivered messages for one target member. */ - readonly maxPendingMessagesPerMember?: number - /** Maximum UTF-8 bytes in one complete sender-framed delivery. */ - readonly maxMessageBytes?: number - /** Maximum milliseconds allowed for Team-owned runtime disposal. */ - readonly disposalTimeoutMs?: number -} -``` - -Source: [`packages/experimental/team/src/types.ts:125`](../packages/experimental/team/src/types.ts) - ## `@deepseek-ai/dsh-terminal-bash` @@ -2817,24 +2836,6 @@ Depends on: [`SubagentReportDelivery`](subsystems/subagent.md) Source: [`packages/subagent/tool-subagent-report/src/index.ts:27`](../packages/subagent/tool-subagent-report/src/index.ts) - - -## `@deepseek-ai/dsh-tool-team` - -Requires: `agents` · `teams` · `tools` · `systemPrompt` - -```ts config-catalog -/** Tool routing configuration. */ -export interface Config { - /** Continuable-subagent provider used for fresh teammates. */ - readonly freshProvider?: string - /** Continuable-subagent provider used for completed-prefix fork teammates. */ - readonly forkProvider?: string -} -``` - -Source: [`packages/experimental/tool-team/src/index.ts:17`](../packages/experimental/tool-team/src/index.ts) - ## `@deepseek-ai/dsh-tool-terminal` diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 04c54fb6ba..68641ea8c0 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -589,6 +589,48 @@ export interface Config { 来源:[`packages/e2b/e2b/src/index.ts:43`](../packages/e2b/e2b/src/index.ts) + + +## `@deepseek-ai/dsh-experimental-agent-team` + +需要:`agents` · `sessions` · `sessionPersistence` · `subagents` + +```ts config-catalog +/** Team-service deployment limits. */ +export interface Config { + /** Maximum immutable teammate names retained by one Team. */ + readonly maxMembers?: number + /** Maximum non-deleted tasks retained by one Team. */ + readonly maxTasks?: number + /** Maximum queued-minus-delivered messages for one target member. */ + readonly maxPendingMessagesPerMember?: number + /** Maximum UTF-8 bytes in one complete sender-framed delivery. */ + readonly maxMessageBytes?: number + /** Maximum milliseconds allowed for Team-owned runtime disposal. */ + readonly disposalTimeoutMs?: number +} +``` + +来源:[`packages/experimental/agent-team/src/types.ts:125`](../packages/experimental/agent-team/src/types.ts) + + + +## `@deepseek-ai/dsh-experimental-tool-agent-team` + +需要:`agents` · `agentTeams` · `tools` · `systemPrompt` + +```ts config-catalog +/** Tool routing configuration. */ +export interface Config { + /** Continuable-subagent provider used for fresh teammates. */ + readonly freshProvider?: string + /** Continuable-subagent provider used for completed-prefix fork teammates. */ + readonly forkProvider?: string +} +``` + +来源:[`packages/experimental/tool-agent-team/src/index.ts:17`](../packages/experimental/tool-agent-team/src/index.ts) + ## `@deepseek-ai/dsh-file-reference-local` @@ -1397,8 +1439,9 @@ export interface Config { */ presets?: Record /** - * Default for new sessions. When omitted, the preset matching the composed - * sandbox and approval defaults is used. + * Default for fresh sessions and eligible confirmed blank reuse. When + * omitted, the preset matching the composed sandbox and approval defaults + * is used. */ defaultPreset?: string } @@ -1418,7 +1461,8 @@ export interface PresetSpec { 依赖:[`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md) -来源:[`packages/interaction/permission-presets/src/index.ts:140`](../packages/interaction/permission-presets/src/index.ts) +来源:[`packages/interaction/permission-presets/src/index.ts:168`](../packages/interaction/permission-presets/src/index.ts) + @@ -1458,7 +1502,7 @@ export interface PlanModeConfig { } ``` -来源:[`packages/plan/plan-mode/src/index.ts:71`](../packages/plan/plan-mode/src/index.ts) +来源:[`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts) @@ -2336,31 +2380,6 @@ export interface Config { 来源:[`packages/core/system-prompt/src/index.ts:186`](../packages/core/system-prompt/src/index.ts) - - -## `@deepseek-ai/dsh-team` - -需要:`agents` · `sessions` · `sessionPersistence` · `subagents` - -```ts config-catalog -/** Team-service deployment limits. */ -export interface Config { - /** Maximum immutable teammate names retained by one Team. */ - readonly maxMembers?: number - /** Maximum non-deleted tasks retained by one Team. */ - readonly maxTasks?: number - /** Maximum queued-minus-delivered messages for one target member. */ - readonly maxPendingMessagesPerMember?: number - /** Maximum UTF-8 bytes in one complete sender-framed delivery. */ - readonly maxMessageBytes?: number - /** Maximum milliseconds allowed for Team-owned runtime disposal. */ - readonly disposalTimeoutMs?: number -} -``` - -来源:[`packages/experimental/team/src/types.ts:125`](../packages/experimental/team/src/types.ts) - - ## `@deepseek-ai/dsh-terminal-bash` @@ -2820,25 +2839,6 @@ export interface Config { 来源:[`packages/subagent/tool-subagent-report/src/index.ts:27`](../packages/subagent/tool-subagent-report/src/index.ts) - - -## `@deepseek-ai/dsh-tool-team` - -需要:`agents` · `teams` · `tools` · `systemPrompt` - -```ts config-catalog -/** Tool routing configuration. */ -export interface Config { - /** Continuable-subagent provider used for fresh teammates. */ - readonly freshProvider?: string - /** Continuable-subagent provider used for completed-prefix fork teammates. */ - readonly forkProvider?: string -} -``` - -来源:[`packages/experimental/tool-team/src/index.ts:17`](../packages/experimental/tool-team/src/index.ts) - - ## `@deepseek-ai/dsh-tool-terminal` diff --git a/docs/event-producer-consumer.i18n.yaml b/docs/event-producer-consumer.i18n.yaml index dfd6001cf3..5fdbf36ed6 100644 --- a/docs/event-producer-consumer.i18n.yaml +++ b/docs/event-producer-consumer.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 docs/event-producer-consumer.md -event-producer-consumer.md: 2e4bec93f5b9f68ab885d50bab27e9a9a6036028 -event-producer-consumer.zh.md: e3f5602287d7b688a2191937871e1639dc271e65 +event-producer-consumer.md: d75d64b58b041c117bf2eaa1612c1b4683378a00 +event-producer-consumer.zh.md: adfc11782e179077b17199fc4ea6e8f52b3c465b diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 2e4bec93f5..d75d64b58b 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -9,8 +9,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:183`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:13`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `apiproxy` | -| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), [`tool-team`](../packages/experimental/tool-team) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-team`](../packages/experimental/tool-team) | +| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `tool-agent-team` | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), `tool-agent-team` | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `apiproxy`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`session-telemetry`](../packages/session/session-telemetry) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:197`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | @@ -18,8 +18,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:244`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`team`](../packages/experimental/team) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:178`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `apiproxy`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server`, [`team`](../packages/experimental/team) | +| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `agent-team`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:178`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, `apiproxy`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:278`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/index.ts:30`](../packages/interaction/user-approval/src/index.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `apiproxy` | | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:80`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `apiproxy` | @@ -39,8 +39,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:64`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | | `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`time-context`](../packages/context/time-context), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:64`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`team`](../packages/experimental/team) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `apiproxy`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`team`](../packages/experimental/team), [`token-meter`](../packages/llm/token-meter), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:64`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, `apiproxy`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:85`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:48`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `apiproxy` | | `settings/updated` | `emit` | [`packages/settings/settings/src/types.ts:35`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) | @@ -68,7 +68,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | Event string | Dispatchers | Listeners | | --- | --- | --- | -| `internal/dispatch` | - | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`team`](../packages/experimental/team), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workflow`](../packages/workflow/workflow) | +| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workflow`](../packages/workflow/workflow) | | `internal/plugin` | - | `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), `webserver` | | `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` | | `internal/status` | - | [`agent`](../packages/core/agent) | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index e3f5602287..adfc11782e 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -11,8 +11,8 @@ | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:183`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:13`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `apiproxy` | -| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), [`tool-team`](../packages/experimental/tool-team) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-team`](../packages/experimental/tool-team) | +| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `tool-agent-team` | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), `tool-agent-team` | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `apiproxy`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`session-telemetry`](../packages/session/session-telemetry) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:197`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:205`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | @@ -20,8 +20,8 @@ | `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:231`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:244`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:260`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`team`](../packages/experimental/team) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:178`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `apiproxy`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server`, [`team`](../packages/experimental/team) | +| `agent/session-start` | `emit` | [`packages/core/agent/src/runtime-types.ts:217`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `agent-team`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:178`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, `apiproxy`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:278`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/index.ts:30`](../packages/interaction/user-approval/src/index.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `apiproxy` | | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:80`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `apiproxy` | @@ -41,8 +41,8 @@ | `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:64`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | | `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`time-context`](../packages/context/time-context), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:64`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`team`](../packages/experimental/team) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `apiproxy`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`team`](../packages/experimental/team), [`token-meter`](../packages/llm/token-meter), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:64`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:76`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, `apiproxy`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | `session/flush` | `parallel` | [`packages/core/session/src/index.ts:85`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:48`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `apiproxy` | | `settings/updated` | `emit` | [`packages/settings/settings/src/types.ts:35`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) | @@ -70,7 +70,7 @@ | 事件字符串 | 派发方 | 监听方 | | --- | --- | --- | -| `internal/dispatch` | - | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`team`](../packages/experimental/team), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workflow`](../packages/workflow/workflow) | +| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workflow`](../packages/workflow/workflow) | | `internal/plugin` | - | `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), `webserver` | | `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` | | `internal/status` | - | [`agent`](../packages/core/agent) | diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index 7f40689322..e95923c056 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/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 docs/i18n/README.md -README.md: 3acddd310a423b6a19014063418d81350562b188 -README.zh.md: acfb80d78c8891fddcbe97499372139055d3d2c1 +README.md: d3a53e26d94f3f323f328bd5205455933962044b +README.zh.md: fd11bea8d5b2c636c9fcc5a4026c5157d83097b6 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index 3acddd310a..d3a53e26d9 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -41,7 +41,7 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co ## Scope and exclusions -**Scope**: the root CONTRIBUTING document, every non-vendor README, and every active document under `.agents/notes/**`, `docs/**`, and `python/**`. README matching is case-insensitive on the basename and covers future directories without another manifest edit. Dependency and ignored build-output trees and the frozen `.agents/notes/archived/` tree are discovery exclusions, not evolving translation source. +**Scope**: the root CONTRIBUTING and BRAND_GUIDELINES documents, every non-vendor README, and every active document under `.agents/notes/**`, `docs/**`, and `python/**`. README matching is case-insensitive on the basename and covers future directories without another manifest edit. Dependency and ignored build-output trees and the frozen `.agents/notes/archived/` tree are discovery exclusions, not evolving translation source. Generated English references and graphs participate in pairing when a reviewed Chinese counterpart is available. Their generators remain the English source of truth, and freshness and pairing gates enforce their respective invariants independently; regeneration that changes English leaves the pair out of sync until the reviewed Chinese counterpart is updated and re-recorded. Generated English sources omit the language switcher that ordinary authored sources carry, because adding it would make the generator stale; their Chinese counterparts still link back to the English source. A generated page's Chinese counterpart may rewrite only self-referential generation and maintenance statements that would otherwise be false for the reviewed translation; all technical content remains subject to the ordinary faithfulness rules. diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index acfb80d78c..fd11bea8d5 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -41,7 +41,7 @@ ## 范围与排除 -**范围**:根目录 CONTRIBUTING 文档、除 vendor 源码外的全部 README,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部活跃文档。匹配 README 时只看文件名且不区分大小写,因此今后新增的目录无需再修改 manifest。依赖目录、被忽略的构建产物目录以及冻结的 `.agents/notes/archived/` 目录树只在发现阶段排除,不属于持续演进的翻译源文档。 +**范围**:根目录 CONTRIBUTING 与 BRAND_GUIDELINES 文档、除 vendor 源码外的全部 README,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部活跃文档。匹配 README 时只看文件名且不区分大小写,因此今后新增的目录无需再修改 manifest。依赖目录、被忽略的构建产物目录以及冻结的 `.agents/notes/archived/` 目录树只在发现阶段排除,不属于持续演进的翻译源文档。 有经评审的中文对侧的生成英文参考文档和图文档遵循配对规则。生成器仍是英文真源,新鲜度门禁与配对门禁各自独立强制其约束;重新生成导致英文变化后,配对会保持失去同步状态,直至经评审的中文对侧完成更新并重新记录。生成的英文源文件不含普通撰写文档所带的语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。生成页的中文对侧只能改写若直译便不再符合经评审译文事实的自指生成与维护说明;所有技术内容仍受普通忠实性规则约束。 diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index bb9428ff0d..2a2be35a97 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.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 docs/module-graph.md -module-graph.md: bf1fdd900e5b9db38b7762134c56284748720beb -module-graph.zh.md: 4e1195adbdcb2c32eb89bde5cb5bab913943b584 +module-graph.md: 3d819a66748242ded418c0cd7574857881f70c7a +module-graph.zh.md: e7509c0a81aba32138b58d14e4e144e6b42d95b3 diff --git a/docs/module-graph.md b/docs/module-graph.md index bf1fdd900e..3d819a6674 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -193,8 +193,8 @@ flowchart TD pkg_sdk_jsonrpc_demo["sdk-jsonrpc-demo"] end subgraph group_experimental["packages/experimental"] - pkg_team["team"] - pkg_tool_team["tool-team"] + pkg_experimental_agent_team["experimental-agent-team"] + pkg_experimental_tool_agent_team["experimental-tool-agent-team"] end subgraph group_extensions["packages/extensions"] pkg_client_ui_cordis["client-ui-cordis"] @@ -1034,13 +1034,13 @@ flowchart TD pkg_compaction_tool_result_pruner --> pkg_llm pkg_compaction_tool_result_pruner --> pkg_session pkg_compaction_tool_result_pruner --> pkg_token_meter - pkg_team --> pkg_agent - pkg_team --> pkg_brand - pkg_team --> pkg_invariants - pkg_team --> pkg_llm - pkg_team --> pkg_session - pkg_team --> pkg_session_persistence - pkg_team --> pkg_subagent + pkg_experimental_agent_team --> pkg_agent + pkg_experimental_agent_team --> pkg_brand + pkg_experimental_agent_team --> pkg_invariants + pkg_experimental_agent_team --> pkg_llm + pkg_experimental_agent_team --> pkg_session + pkg_experimental_agent_team --> pkg_session_persistence + pkg_experimental_agent_team --> pkg_subagent pkg_tool_cordis --> pkg_agent pkg_tool_cordis --> pkg_cordis_host_runner pkg_tool_cordis --> pkg_invariants @@ -1052,6 +1052,7 @@ flowchart TD pkg_host_apiproxy --> pkg_agent_presets pkg_host_apiproxy --> pkg_cordis_host_runner pkg_host_apiproxy --> pkg_invariants + pkg_host_apiproxy --> pkg_permission_presets pkg_sdk_protocol --> pkg_invariants pkg_sdk_protocol --> pkg_llm pkg_sdk_protocol --> pkg_session @@ -1139,12 +1140,12 @@ flowchart TD pkg_agent_spine_demo --> pkg_tool_jobs pkg_agent_spine_demo --> pkg_tool_skill pkg_agent_spine_demo --> pkg_tools - pkg_tool_team --> pkg_agent - pkg_tool_team --> pkg_invariants - pkg_tool_team --> pkg_session - pkg_tool_team --> pkg_system_prompt - pkg_tool_team --> pkg_team - pkg_tool_team --> pkg_tools + pkg_experimental_tool_agent_team --> pkg_agent + pkg_experimental_tool_agent_team --> pkg_experimental_agent_team + pkg_experimental_tool_agent_team --> pkg_invariants + pkg_experimental_tool_agent_team --> pkg_session + pkg_experimental_tool_agent_team --> pkg_system_prompt + pkg_experimental_tool_agent_team --> pkg_tools pkg_sdk_client --> pkg_invariants pkg_sdk_client --> pkg_llm pkg_sdk_client --> pkg_sdk_protocol @@ -1624,9 +1625,9 @@ flowchart TD | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) | | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | -| [`team`](../packages/experimental/team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent) | +| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent) | | [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`permission-presets`](../packages/interaction/permission-presets) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | @@ -1637,7 +1638,7 @@ flowchart TD | [`client-connection`](../packages/client/connection) | `client` | [`attachment`](../packages/attachment/attachment), [`commands`](../packages/interaction/commands), [`host-apiproxy`](../packages/host/apiproxy), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs-local`](../packages/jobs/jobs-local), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`shell-env`](../packages/shell/shell-env), [`skill`](../packages/skill/skill), [`skill-filesystem`](../packages/skill/skill-filesystem), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/shell/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) | -| [`tool-team`](../packages/experimental/tool-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`team`](../packages/experimental/team), [`tools`](../packages/core/tools) | +| [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | | [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 4e1195adbd..e7509c0a81 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -195,8 +195,8 @@ flowchart TD pkg_sdk_jsonrpc_demo["sdk-jsonrpc-demo"] end subgraph group_experimental["packages/experimental"] - pkg_team["team"] - pkg_tool_team["tool-team"] + pkg_experimental_agent_team["experimental-agent-team"] + pkg_experimental_tool_agent_team["experimental-tool-agent-team"] end subgraph group_extensions["packages/extensions"] pkg_client_ui_cordis["client-ui-cordis"] @@ -1036,13 +1036,13 @@ flowchart TD pkg_compaction_tool_result_pruner --> pkg_llm pkg_compaction_tool_result_pruner --> pkg_session pkg_compaction_tool_result_pruner --> pkg_token_meter - pkg_team --> pkg_agent - pkg_team --> pkg_brand - pkg_team --> pkg_invariants - pkg_team --> pkg_llm - pkg_team --> pkg_session - pkg_team --> pkg_session_persistence - pkg_team --> pkg_subagent + pkg_experimental_agent_team --> pkg_agent + pkg_experimental_agent_team --> pkg_brand + pkg_experimental_agent_team --> pkg_invariants + pkg_experimental_agent_team --> pkg_llm + pkg_experimental_agent_team --> pkg_session + pkg_experimental_agent_team --> pkg_session_persistence + pkg_experimental_agent_team --> pkg_subagent pkg_tool_cordis --> pkg_agent pkg_tool_cordis --> pkg_cordis_host_runner pkg_tool_cordis --> pkg_invariants @@ -1054,6 +1054,7 @@ flowchart TD pkg_host_apiproxy --> pkg_agent_presets pkg_host_apiproxy --> pkg_cordis_host_runner pkg_host_apiproxy --> pkg_invariants + pkg_host_apiproxy --> pkg_permission_presets pkg_sdk_protocol --> pkg_invariants pkg_sdk_protocol --> pkg_llm pkg_sdk_protocol --> pkg_session @@ -1141,12 +1142,12 @@ flowchart TD pkg_agent_spine_demo --> pkg_tool_jobs pkg_agent_spine_demo --> pkg_tool_skill pkg_agent_spine_demo --> pkg_tools - pkg_tool_team --> pkg_agent - pkg_tool_team --> pkg_invariants - pkg_tool_team --> pkg_session - pkg_tool_team --> pkg_system_prompt - pkg_tool_team --> pkg_team - pkg_tool_team --> pkg_tools + pkg_experimental_tool_agent_team --> pkg_agent + pkg_experimental_tool_agent_team --> pkg_experimental_agent_team + pkg_experimental_tool_agent_team --> pkg_invariants + pkg_experimental_tool_agent_team --> pkg_session + pkg_experimental_tool_agent_team --> pkg_system_prompt + pkg_experimental_tool_agent_team --> pkg_tools pkg_sdk_client --> pkg_invariants pkg_sdk_client --> pkg_llm pkg_sdk_client --> pkg_sdk_protocol @@ -1626,9 +1627,9 @@ flowchart TD | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`invariants`](../packages/runtime-diagnostics/invariants), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) | | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | -| [`team`](../packages/experimental/team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent) | +| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent) | | [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`invariants`](../packages/runtime-diagnostics/invariants), [`permission-presets`](../packages/interaction/permission-presets) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | @@ -1639,7 +1640,7 @@ flowchart TD | [`client-connection`](../packages/client/connection) | `client` | [`attachment`](../packages/attachment/attachment), [`commands`](../packages/interaction/commands), [`host-apiproxy`](../packages/host/apiproxy), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs-local`](../packages/jobs/jobs-local), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`shell-env`](../packages/shell/shell-env), [`skill`](../packages/skill/skill), [`skill-filesystem`](../packages/skill/skill-filesystem), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/shell/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-skill`](../packages/skill/tool-skill), [`tools`](../packages/core/tools) | -| [`tool-team`](../packages/experimental/tool-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`team`](../packages/experimental/team), [`tools`](../packages/core/tools) | +| [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | | [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index e6e7363a9f..c05366c81d 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.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 docs/persistence-catalog.md -persistence-catalog.md: cbe9cec195ad899ba27dd08743d64090ae7277f2 -persistence-catalog.zh.md: 8057a0910e505b95872c341283d1acdbf033faaf +persistence-catalog.md: f8c10821e4b6daa8f10b0cff63f838ecb381125c +persistence-catalog.zh.md: 14b9df246afecd583bbaa4b2971ae289ad5d066d diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index cbe9cec195..f8c10821e4 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -506,15 +506,18 @@ Source: [`packages/llm/llm-retry/src/types.ts:11`](../packages/llm/llm-retry/src ```ts persistence-catalog /** - * Records the selected preset as durable, log-only user intent. The knob + * Records the selected preset and whether it came from the session + * default, an explicit selection, or legacy-knob inference. The knob * events follow in the same turn and control execution; this event stays * out of the model transcript and lets {@link effectivePermissionPreset} - * preserve a selection when bundles match. + * preserve a selection when bundles match. `origin` is optional so logs + * written before origin tracking remain readable but are never mistaken + * for refreshable defaults. */ -'permission/preset': { preset: string } +'permission/preset': { preset: string; origin?: 'default' | 'selection' | 'inferred' } ``` -Source: [`packages/interaction/permission-presets/src/index.ts:50`](../packages/interaction/permission-presets/src/index.ts) +Source: [`packages/interaction/permission-presets/src/index.ts:53`](../packages/interaction/permission-presets/src/index.ts) ### `plan/*` @@ -531,7 +534,7 @@ Source: [`packages/interaction/permission-presets/src/index.ts:50`](../packages/ 'plan/mode': { active: boolean } ``` -Source: [`packages/plan/plan-mode/src/index.ts:54`](../packages/plan/plan-mode/src/index.ts) +Source: [`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts) ### `request/*` @@ -721,9 +724,9 @@ Source: [`packages/subagent/subagent/src/descriptor.ts:37`](../packages/subagent 'team/member': { version: 1; teamId: TeamId; member: TeamMemberSnapshot } ``` -Types: [TeamId](subsystems/team.md) · [TeamMemberSnapshot](subsystems/team.md) +Types: [TeamId](subsystems/agent-team.md) · [TeamMemberSnapshot](subsystems/agent-team.md) -Source: [`packages/experimental/team/src/types.ts:206`](../packages/experimental/team/src/types.ts) +Source: [`packages/experimental/agent-team/src/types.ts:206`](../packages/experimental/agent-team/src/types.ts) @@ -739,9 +742,9 @@ Source: [`packages/experimental/team/src/types.ts:206`](../packages/experimental } ``` -Types: [TeamId](subsystems/team.md) · [TeamMessageId](subsystems/team.md) +Types: [TeamId](subsystems/agent-team.md) · [TeamMessageId](subsystems/agent-team.md) -Source: [`packages/experimental/team/src/types.ts:212`](../packages/experimental/team/src/types.ts) +Source: [`packages/experimental/agent-team/src/types.ts:212`](../packages/experimental/agent-team/src/types.ts) @@ -752,9 +755,9 @@ Source: [`packages/experimental/team/src/types.ts:212`](../packages/experimental 'team/message/queued': { version: 1; teamId: TeamId; message: TeamMessageSnapshot } ``` -Types: [TeamId](subsystems/team.md) · [TeamMessageSnapshot](subsystems/team.md) +Types: [TeamId](subsystems/agent-team.md) · [TeamMessageSnapshot](subsystems/agent-team.md) -Source: [`packages/experimental/team/src/types.ts:210`](../packages/experimental/team/src/types.ts) +Source: [`packages/experimental/agent-team/src/types.ts:210`](../packages/experimental/agent-team/src/types.ts) @@ -765,9 +768,9 @@ Source: [`packages/experimental/team/src/types.ts:210`](../packages/experimental 'team/task': { version: 1; teamId: TeamId; task: TeamTaskSnapshot } ``` -Types: [TeamId](subsystems/team.md) · [TeamTaskSnapshot](subsystems/team.md) +Types: [TeamId](subsystems/agent-team.md) · [TeamTaskSnapshot](subsystems/agent-team.md) -Source: [`packages/experimental/team/src/types.ts:208`](../packages/experimental/team/src/types.ts) +Source: [`packages/experimental/agent-team/src/types.ts:208`](../packages/experimental/agent-team/src/types.ts) ### `todo/*` diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 8057a0910e..14b9df246a 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -508,15 +508,18 @@ export type SessionEvent = { ```ts persistence-catalog /** - * Records the selected preset as durable, log-only user intent. The knob + * Records the selected preset and whether it came from the session + * default, an explicit selection, or legacy-knob inference. The knob * events follow in the same turn and control execution; this event stays * out of the model transcript and lets {@link effectivePermissionPreset} - * preserve a selection when bundles match. + * preserve a selection when bundles match. `origin` is optional so logs + * written before origin tracking remain readable but are never mistaken + * for refreshable defaults. */ -'permission/preset': { preset: string } +'permission/preset': { preset: string; origin?: 'default' | 'selection' | 'inferred' } ``` -来源:[`packages/interaction/permission-presets/src/index.ts:50`](../packages/interaction/permission-presets/src/index.ts) +来源:[`packages/interaction/permission-presets/src/index.ts:53`](../packages/interaction/permission-presets/src/index.ts) ### `plan/*` @@ -533,7 +536,7 @@ export type SessionEvent = { 'plan/mode': { active: boolean } ``` -来源:[`packages/plan/plan-mode/src/index.ts:54`](../packages/plan/plan-mode/src/index.ts) +来源:[`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts) ### `request/*` @@ -723,9 +726,9 @@ export type SessionEvent = { 'team/member': { version: 1; teamId: TeamId; member: TeamMemberSnapshot } ``` -类型:[TeamId](subsystems/team.md) · [TeamMemberSnapshot](subsystems/team.md) +类型:[TeamId](subsystems/agent-team.md) · [TeamMemberSnapshot](subsystems/agent-team.md) -来源:[`packages/experimental/team/src/types.ts:206`](../packages/experimental/team/src/types.ts) +来源:[`packages/experimental/agent-team/src/types.ts:206`](../packages/experimental/agent-team/src/types.ts) @@ -741,9 +744,9 @@ export type SessionEvent = { } ``` -类型:[TeamId](subsystems/team.md) · [TeamMessageId](subsystems/team.md) +类型:[TeamId](subsystems/agent-team.md) · [TeamMessageId](subsystems/agent-team.md) -来源:[`packages/experimental/team/src/types.ts:212`](../packages/experimental/team/src/types.ts) +来源:[`packages/experimental/agent-team/src/types.ts:212`](../packages/experimental/agent-team/src/types.ts) @@ -754,9 +757,9 @@ export type SessionEvent = { 'team/message/queued': { version: 1; teamId: TeamId; message: TeamMessageSnapshot } ``` -类型:[TeamId](subsystems/team.md) · [TeamMessageSnapshot](subsystems/team.md) +类型:[TeamId](subsystems/agent-team.md) · [TeamMessageSnapshot](subsystems/agent-team.md) -来源:[`packages/experimental/team/src/types.ts:210`](../packages/experimental/team/src/types.ts) +来源:[`packages/experimental/agent-team/src/types.ts:210`](../packages/experimental/agent-team/src/types.ts) @@ -767,9 +770,9 @@ export type SessionEvent = { 'team/task': { version: 1; teamId: TeamId; task: TeamTaskSnapshot } ``` -类型:[TeamId](subsystems/team.md) · [TeamTaskSnapshot](subsystems/team.md) +类型:[TeamId](subsystems/agent-team.md) · [TeamTaskSnapshot](subsystems/agent-team.md) -来源:[`packages/experimental/team/src/types.ts:208`](../packages/experimental/team/src/types.ts) +来源:[`packages/experimental/agent-team/src/types.ts:208`](../packages/experimental/agent-team/src/types.ts) ### `todo/*` diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index 55e453c3f2..dc8fe4d5c1 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/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 docs/subsystems/README.md -README.md: b3832e44d4fc2150efc4e81aab00d1006cb816b6 -README.zh.md: 5f5a44e9f21956921472c23a141eabac236704e9 +README.md: d926abac718ba14b0d50f27d7c00b421c8460352 +README.zh.md: f1b70aabe9ebbbe20274f5e9522ddead00a1f75f diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index b3832e44d4..d926abac71 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -38,7 +38,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [skills.md](skills.md) | the skill service: discovery priority, `SkillSummary`/`SkillDefinition`, session-prefix catalog, model-facing `skill` loading | | [compaction.md](compaction.md) | the compaction seam: the `compaction/*` session events, `CompactionResult`, the `CompactionEngine` interface | | [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split | -| [team.md](team.md) | Agent Teams: implicit Lead identity, named continuable teammates, durable peer mailbox, and shared task DAG | +| [agent-team.md](agent-team.md) | Agent Teams: implicit Lead identity, named continuable teammates, durable peer mailbox, and shared task DAG | | [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` | | [spill.md](spill.md) | the spill storage seam: `SaveTextSpill`, `SpillOwner`/`SpillSource`, `SpillRef`, the branded `SpillLocator` | | [workflow.md](workflow.md) | the workflow seam: `WorkflowStartRequest`, `WorkflowMeta`, `WorkflowRun`/`Result`, the `workflow/*` event payloads, `WorkflowError` fatality | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 5f5a44e9f2..f1b70aabe9 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -38,7 +38,7 @@ | [skills.md](skills.md) | skill(技能)服务:发现优先级、`SkillSummary`/`SkillDefinition`、会话前缀目录、面向模型的 `skill` 加载 | | [compaction.md](compaction.md) | 压缩(compaction)seam:`compaction/*` 会话事件、`CompactionResult`、`CompactionEngine` 接口 | | [subagent.md](subagent.md) | subagent seam:命名提供方注册表、`SubagentStartRequest`/`Result`/`Run`、启动时与运行时能力拆分 | -| [team.md](team.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、持久 peer mailbox 与共享任务 DAG | +| [agent-team.md](agent-team.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、持久 peer mailbox 与共享任务 DAG | | [web.md](web.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` | | [spill.md](spill.md) | spill 存储 seam:`SaveTextSpill`、`SpillOwner`/`SpillSource`、`SpillRef`、品牌类型 `SpillLocator` | | [workflow.md](workflow.md) | 工作流 seam:`WorkflowStartRequest`、`WorkflowMeta`、`WorkflowRun`/`Result`、`workflow/*` 事件载荷、`WorkflowError` 致命性 | diff --git a/docs/subsystems/team.i18n.yaml b/docs/subsystems/agent-team.i18n.yaml similarity index 70% rename from docs/subsystems/team.i18n.yaml rename to docs/subsystems/agent-team.i18n.yaml index b58b85085c..e0686a8403 100644 --- a/docs/subsystems/team.i18n.yaml +++ b/docs/subsystems/agent-team.i18n.yaml @@ -1,6 +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 docs/subsystems/team.md -team.md: 926434bb85d8cb3739e66fdf559c1c51d807613d -team.zh.md: 7eeb4ef5aeeeee119ea931912390d4ff3d42bc85 +# pnpm run verify-translation-pairing --write docs/subsystems/agent-team.md +agent-team.md: 1e1c787cc6c4e1489ab3b6fc0599d0519b7a52f4 +agent-team.zh.md: 36f6685d55059c0d24b9ca3a5427c01e73ea9ab0 diff --git a/docs/subsystems/team.md b/docs/subsystems/agent-team.md similarity index 95% rename from docs/subsystems/team.md rename to docs/subsystems/agent-team.md index 926434bb85..1e1c787cc6 100644 --- a/docs/subsystems/team.md +++ b/docs/subsystems/agent-team.md @@ -1,8 +1,8 @@ # Agent Teams -English | [中文](team.zh.md) +English | [中文](agent-team.zh.md) -Types shared by the experimental implicit-root Team domain, model tools, and host adapters. The [Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.md) owns identity, mailbox, task, and shared-checkout decisions; this page records the literal durable forms from [`packages/experimental/team/src/types.ts`](../../packages/experimental/team/src/types.ts). +Types shared by the experimental implicit-root Team domain, model tools, and host adapters. The [Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.md) owns identity, mailbox, task, and shared-checkout decisions; this page records the literal durable forms from [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts). ## Identity and roster @@ -74,7 +74,7 @@ interface TeamTaskSnapshot { ## Replay -`foldTeam()` replays one root Session into the roster, task board, and queued-minus-delivered mailbox that every Team operation reads. It selects records by `TeamId`, so events inherited by an ordinary fork retain the ancestor id and never enter the new root's state. Session event `seq` and `time` remain the ordering and timing record; Team snapshots do not duplicate them. Roster and task reads reach callers as views that add owner name, readiness, and write-scope warnings, while pending mail stays internal to delivery and recovery. The package [README](../../packages/experimental/team/README.md) owns operation, authorization, recovery, and limit behavior. +`foldTeam()` replays one root Session into the roster, task board, and queued-minus-delivered mailbox that every Team operation reads. It selects records by `TeamId`, so events inherited by an ordinary fork retain the ancestor id and never enter the new root's state. Session event `seq` and `time` remain the ordering and timing record; Team snapshots do not duplicate them. Roster and task reads reach callers as views that add owner name, readiness, and write-scope warnings, while pending mail stays internal to delivery and recovery. The package [README](../../packages/experimental/agent-team/README.md) owns operation, authorization, recovery, and limit behavior. @@ -84,9 +84,9 @@ interface TeamTaskSnapshot { Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - + -### `ctx.teams` — `TeamService` +### `ctx.agentTeams` — `TeamService` Agent Teams service backed by the exact live Lead Session log. @@ -179,5 +179,5 @@ tryMembership(agent: Agent): TeamMembership | undefined Types: [Agent](core.md) -Source: [`packages/experimental/team/src/index.ts:56`](../../packages/experimental/team/src/index.ts) +Source: [`packages/experimental/agent-team/src/index.ts:56`](../../packages/experimental/agent-team/src/index.ts) diff --git a/docs/subsystems/team.zh.md b/docs/subsystems/agent-team.zh.md similarity index 94% rename from docs/subsystems/team.zh.md rename to docs/subsystems/agent-team.zh.md index 7eeb4ef5ae..36f6685d55 100644 --- a/docs/subsystems/team.zh.md +++ b/docs/subsystems/agent-team.zh.md @@ -1,8 +1,8 @@ # Agent Teams -[English](team.md) | 中文 +[English](agent-team.md) | 中文 -实验性隐式 Root Team 领域、模型工具与宿主适配器共享的类型。[Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.md)负责身份、mailbox、task 与共享 checkout 决策;本页记录 [`packages/experimental/team/src/types.ts`](../../packages/experimental/team/src/types.ts) 中的字面持久形式。 +实验性隐式 Root Team 领域、模型工具与宿主适配器共享的类型。[Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.md)负责身份、mailbox、task 与共享 checkout 决策;本页记录 [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts) 中的字面持久形式。 ## 身份与 roster @@ -74,7 +74,7 @@ interface TeamTaskSnapshot { ## 回放 -`foldTeam()` 把一个 Root Session 回放成每个 Team 操作所读取的 roster、任务板与 queued-minus-delivered mailbox。它按 `TeamId` 选取记录,因此普通 fork 继承的 event 保留 ancestor id,绝不会进入新 Root 的状态。Session event 的 `seq` 与 `time` 继续负责顺序和时间记录,Team snapshot 不再重复保存它们。roster 与 task 读取以 view 形式到达调用方,附带 owner name、readiness 与 write-scope 警告,而 pending 邮件仅供投递与恢复内部使用。包 [README](../../packages/experimental/team/README.md)负责 operation、authorization、recovery 和限制行为。 +`foldTeam()` 把一个 Root Session 回放成每个 Team 操作所读取的 roster、任务板与 queued-minus-delivered mailbox。它按 `TeamId` 选取记录,因此普通 fork 继承的 event 保留 ancestor id,绝不会进入新 Root 的状态。Session event 的 `seq` 与 `time` 继续负责顺序和时间记录,Team snapshot 不再重复保存它们。roster 与 task 读取以 view 形式到达调用方,附带 owner name、readiness 与 write-scope 警告,而 pending 邮件仅供投递与恢复内部使用。包 [README](../../packages/experimental/agent-team/README.md)负责 operation、authorization、recovery 和限制行为。 @@ -84,9 +84,9 @@ interface TeamTaskSnapshot { Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - + -### `ctx.teams` — `TeamService` +### `ctx.agentTeams` — `TeamService` Agent Teams service backed by the exact live Lead Session log. @@ -179,5 +179,5 @@ tryMembership(agent: Agent): TeamMembership | undefined Types: [Agent](core.md) -Source: [`packages/experimental/team/src/index.ts:56`](../../packages/experimental/team/src/index.ts) +Source: [`packages/experimental/agent-team/src/index.ts:56`](../../packages/experimental/agent-team/src/index.ts) diff --git a/docs/subsystems/permission-presets.i18n.yaml b/docs/subsystems/permission-presets.i18n.yaml index 127988f5c1..3b20358dd5 100644 --- a/docs/subsystems/permission-presets.i18n.yaml +++ b/docs/subsystems/permission-presets.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 docs/subsystems/permission-presets.md -permission-presets.md: 16ce29a4c3b00fece089ebcdc959e57f419d35c9 -permission-presets.zh.md: d2e5eff6696d5dc6f925032d7eaafb7b19774f4e +permission-presets.md: 6a237d8b3289d53d6c92a62318bc7f3041d5c81a +permission-presets.zh.md: 9c82ba073573f4a09ea4f51a8844be6c6df2dc62 diff --git a/docs/subsystems/permission-presets.md b/docs/subsystems/permission-presets.md index 16ce29a4c3..6a237d8b32 100644 --- a/docs/subsystems/permission-presets.md +++ b/docs/subsystems/permission-presets.md @@ -34,8 +34,9 @@ interface Config { */ presets?: Record /** - * Default for new sessions. When omitted, the preset matching the composed - * sandbox and approval defaults is used. + * Default for fresh sessions and eligible confirmed blank reuse. When + * omitted, the preset matching the composed sandbox and approval defaults + * is used. */ defaultPreset?: string } @@ -91,6 +92,17 @@ Owns the deployment's permission presets and their write path. Requires a confin */ current(events: readonly SessionEvent[]): string +/** + * Advance one blank session after the host has confirmed it as the exact + * Web New Session reuse target. Only a still-effective + * default-origin selection advances; a started session, an explicit pick, + * legacy origin-less data, or independently changed knobs remain pinned. + * This is the permission-side half of the Web candidate selection and the + * host's blankness, membership, cwd, and archive verification. + * @param session - the live session selected for Workspace blank reuse. + */ +refreshDefaultForReuse(session: Session): void + /** * Build the whole select value for one folded knob state: every table * option in declaration order, `custom` appended exactly while derived. @@ -127,5 +139,5 @@ set(session: Session, name: string): void Types: [Session](session.md) · [SessionEvent](session.md) -Source: [`packages/interaction/permission-presets/src/index.ts:159`](../../packages/interaction/permission-presets/src/index.ts) +Source: [`packages/interaction/permission-presets/src/index.ts:188`](../../packages/interaction/permission-presets/src/index.ts) diff --git a/docs/subsystems/permission-presets.zh.md b/docs/subsystems/permission-presets.zh.md index d2e5eff669..9c82ba0735 100644 --- a/docs/subsystems/permission-presets.zh.md +++ b/docs/subsystems/permission-presets.zh.md @@ -34,8 +34,9 @@ interface Config { */ presets?: Record /** - * Default for new sessions. When omitted, the preset matching the composed - * sandbox and approval defaults is used. + * Default for fresh sessions and eligible confirmed blank reuse. When + * omitted, the preset matching the composed sandbox and approval defaults + * is used. */ defaultPreset?: string } @@ -91,6 +92,17 @@ Owns the deployment's permission presets and their write path. Requires a confin */ current(events: readonly SessionEvent[]): string +/** + * Advance one blank session after the host has confirmed it as the exact + * Web New Session reuse target. Only a still-effective + * default-origin selection advances; a started session, an explicit pick, + * legacy origin-less data, or independently changed knobs remain pinned. + * This is the permission-side half of the Web candidate selection and the + * host's blankness, membership, cwd, and archive verification. + * @param session - the live session selected for Workspace blank reuse. + */ +refreshDefaultForReuse(session: Session): void + /** * Build the whole select value for one folded knob state: every table * option in declaration order, `custom` appended exactly while derived. @@ -127,5 +139,5 @@ set(session: Session, name: string): void Types: [Session](session.md) · [SessionEvent](session.md) -Source: [`packages/interaction/permission-presets/src/index.ts:159`](../../packages/interaction/permission-presets/src/index.ts) +Source: [`packages/interaction/permission-presets/src/index.ts:188`](../../packages/interaction/permission-presets/src/index.ts) diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml index 4623b14448..a75f951316 100644 --- a/docs/subsystems/plan.i18n.yaml +++ b/docs/subsystems/plan.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 docs/subsystems/plan.md -plan.md: 1f6863a24aa56773430be904e5a27c27384c9bff -plan.zh.md: 056bce946b608876ac958f2d33d871e9622c7187 +plan.md: 9de3566e9f065ed537e9ad97285a22d0bf8fac14 +plan.zh.md: 825065c79070398daa2f42216d1537010b45e31f diff --git a/docs/subsystems/plan.md b/docs/subsystems/plan.md index 1f6863a24a..9de3566e9f 100644 --- a/docs/subsystems/plan.md +++ b/docs/subsystems/plan.md @@ -83,5 +83,5 @@ set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop Types: [Agent](core.md) -Source: [`packages/plan/plan-mode/src/index.ts:188`](../../packages/plan/plan-mode/src/index.ts) +Source: [`packages/plan/plan-mode/src/index.ts:202`](../../packages/plan/plan-mode/src/index.ts) diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md index 056bce946b..825065c790 100644 --- a/docs/subsystems/plan.zh.md +++ b/docs/subsystems/plan.zh.md @@ -83,5 +83,5 @@ set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop Types: [Agent](core.md) -Source: [`packages/plan/plan-mode/src/index.ts:188`](../../packages/plan/plan-mode/src/index.ts) +Source: [`packages/plan/plan-mode/src/index.ts:202`](../../packages/plan/plan-mode/src/index.ts) diff --git a/docs/subsystems/session-projection.i18n.yaml b/docs/subsystems/session-projection.i18n.yaml index 2b0653542d..33db374c42 100644 --- a/docs/subsystems/session-projection.i18n.yaml +++ b/docs/subsystems/session-projection.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 docs/subsystems/session-projection.md -session-projection.md: 281e7630eed6480de07a66bf7050798c83396f77 -session-projection.zh.md: bc1448e8baa0f38b3a9ffed1c6005e8905c93bcd +session-projection.md: 2ba14c0ea12a88ae356fb99aabbe72516e0a6602 +session-projection.zh.md: 3defe8fa174647160e9a2b5da7732050e34afe47 diff --git a/docs/subsystems/session-projection.md b/docs/subsystems/session-projection.md index 281e7630ee..2ba14c0ea1 100644 --- a/docs/subsystems/session-projection.md +++ b/docs/subsystems/session-projection.md @@ -8,27 +8,30 @@ Source: [`packages/session/session-projection/src/index.ts`](../../packages/sess ## The unit -`SessionProjectionMap` is the merge-extensible type table for the whole chain (host unit, wire block, client hook); values are wire-JSON whole values, and rendering belongs to the slot system, never this layer. A domain contributes one `ProjectionDefinition` per key: +`SessionProjectionStateMap` is the merge-extensible table of host fold states, while `SessionProjectionMap` retains the client-visible whole values. A domain contributes one `ProjectionDefinition` per state key; a `wire` block makes that key client-visible, and rendering belongs to the slot system, never this layer: ```ts type-equiv /** - * One domain's state-driven computation unit: three pure synchronous - * functions plus declarations — never an opaque getter. The framework drives + * One domain's state-driven computation unit: a pure synchronous fold plus + * declarations and an optional client view — never an opaque getter. The framework drives * `apply` on every committed session event; the domain holds no - * subscriptions and owns only the mathematics. All three functions MUST be - * synchronous (an async unit would tear the carriers' consistency cut) and + * subscriptions and owns only the computation. All functions MUST be + * synchronous (an async unit would tear the carriers' consistency cut), and * `state` MUST be plain JSON (the persisted-cache precondition). */ -interface ProjectionDefinition { - /** The projection key this unit owns (its `SessionProjectionMap` entry). */ +interface ProjectionDefinition< + K extends keyof SessionProjectionStateMap, + S extends SessionProjectionStateMap[K] = SessionProjectionStateMap[K], +> { + /** The projection key this unit owns (its `SessionProjectionStateMap` entry). */ key: K - /** Validates the wire payload (`view` output) before it leaves the host. */ - schema: ZodType + /** Validates persisted state before it seeds a fold. */ + stateSchema: ZodType /** * State for the empty log. * @returns the initial state. */ - init(): S + init(): NoInfer /** * Pure transition: previous state + one committed event → next state. A * unit uninterested in an event MUST return the same state reference — an @@ -37,13 +40,18 @@ interface ProjectionDefinition { * @param event - the next committed session event. * @returns the next state (same reference when the event is not the unit's). */ - apply(state: S, event: SessionEvent): S - /** - * State → wire payload (the read-side projection). - * @param state - the current state. - * @returns the whole current value for this unit's key. - */ - view(state: S): SessionProjectionMap[K] + apply(state: NoInfer, event: SessionEvent): NoInfer + /** Client view. Omit for host-only units. */ + wire?: K extends keyof SessionProjectionMap ? { + /** Validates the wire payload before it leaves the host. */ + viewSchema: ZodType + /** + * State → wire payload (the read-side projection). + * @param state - the current state. + * @returns the whole current value for this unit's key. + */ + view(state: NoInfer): SessionProjectionMap[K] + } : never /** * Persisted-cache invalidation version: bump whenever the serialized state fields or the * fold semantics change, so persisted `(sessionId, key, ver, seq, val)` @@ -60,14 +68,14 @@ The whole-value event rule is load-bearing: a state-carrying log event carries t ```ts type-equiv /** - * One consistent read cut over every registered unit for one session. + * One consistent read cut over every registered client-visible unit for one session. * `asOfSeq` is the shared watermark — the seq of the last event every value * reflects (`-1` for an empty log, mirroring `session/subscribed.lastSeq`). */ interface ProjectionSnapshot { /** Seq of the last event the values reflect; -1 for an empty log. */ asOfSeq: number - /** Whole current value per registered key. */ + /** Whole current client value per registered key. */ values: Partial } ``` @@ -86,7 +94,7 @@ type ProjectionChangeListener = ( ) => void ``` -`snapshot(session)` is fully synchronous: a carrier reads it in the same tick as its page slice, so `asOfSeq` covers both reads at one sequence number. Every value passes its unit's schema before return; an accidentally async `view` returns a Promise, which schema validation rejects. The change feed fires once per unit whose state *reference* changed for each committed event; `apply` must return the same reference when its state did not change. +`snapshot(session)` is fully synchronous: a carrier reads it in the same tick as its page slice, so `asOfSeq` covers both reads at one sequence number. It returns only client views, and every value passes its unit's `viewSchema` before return. `stateOf(session, key)` reads one live host state without computing unrelated views; callers must not mutate the borrowed reference. The change feed fires once per client-visible unit whose state *reference* changed for each committed event; `apply` must return the same reference when its state did not change. ## The registry: `ctx.sessionProjections` @@ -154,7 +162,7 @@ Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../pack ### `ctx.sessionProjections` — `SessionProjectionRegistry` -`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. Registrants sharing a key share one unit and are counted: the same tool package mounted in N agent presets registers N times, and the key survives until the last one unloads. +`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference in a client-visible unit notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. Registrants sharing a key share one unit and are counted: the same tool package mounted in N agent presets registers N times, and the key survives until the last one unloads. ```ts cordis-catalog /** @@ -165,28 +173,45 @@ Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../pack * @param definition - key, state schema, pure unit functions, and stateVersion. * @returns the exact disposer that unregisters this unit. */ -register(definition: ProjectionDefinition): () => void +register< K extends keyof SessionProjectionMap, S extends SessionProjectionStateMap[K], >( definition: Omit, 'wire'> & { wire: NonNullable['wire']> }, ): () => void + +/** + * Register one host-only unit. Its state is omitted from client snapshots + * and always checkpointed like every other unit. + * @param definition - key, state schema, pure unit functions, and stateVersion. + * @returns the exact disposer that unregisters this unit. + */ +register< K extends Exclude, S extends SessionProjectionStateMap[K], >( definition: Omit, 'wire'>, ): () => void /** * Subscribe to the change feed. The registration is an effect on the * calling context's fiber. - * @param listener - called once per unit whose state reference changed, per committed event. + * @param listener - called once per client-visible unit whose state reference changed, per committed event. * @returns the exact disposer that unsubscribes. */ onChanged(listener: ProjectionChangeListener): () => void /** - * One consistent cut over every registered unit for one session, read from + * Read one unit's current host state without computing unrelated views. + * The returned value is live; callers must not mutate it. + * @param session - the session whose state is read. + * @param key - the registered unit key. + * @returns current state, or `undefined` when the key is not registered. + */ +stateOf( session: Session, key: K, ): SessionProjectionStateMap[K] | undefined + +/** + * One consistent cut over every registered client-visible unit for one session, read from * the watermark cache (missing cells fold lazily over the in-memory log). * Fully synchronous — every value and `asOfSeq` reflect the same log - * position. Each value passes its unit's schema before leaving. + * position. Each value passes its unit's `viewSchema` before leaving. * @param session - the session whose projection values are read. - * @returns the snapshot; `values` is empty when no unit is registered. + * @returns the snapshot; `values` is empty when no client-visible unit is registered. */ snapshot(session: Session): ProjectionSnapshot /** - * State-level checkpoint of every registered unit for one session, read + * State-level checkpoint of every persisted unit for one session, read * from the watermark cache (missing cells fold lazily over the in-memory * log). This is the write side of the persisted projection cache: the * returned rows are the `(key → {ver, seq, val})` part of the durable @@ -197,7 +222,7 @@ snapshot(session: Session): ProjectionSnapshot * every subsequent snapshot and frame through it (plain JSON by the unit * contract, so the clone is total). * @param session - the session whose unit states are checkpointed. - * @returns one row per registered key; empty when no unit is registered. + * @returns one row per registered key. */ checkpoint(session: Session): ProjectionCheckpoint @@ -221,8 +246,8 @@ restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined /** * View a checkpoint's rows without any log read: for every registered - * unit whose row's `ver` matches, serve the schema-validated - * `view` of the stored state; mismatched or absent rows leave their key + * client-visible unit whose row's `ver` matches, serve the schema-validated + * `view` of the schema-validated stored state; mismatched, malformed, or absent rows leave their key * absent (a cold or listing consumer treats it as not-yet-available and a * fuller read path refolds it). The zero-I/O rung of the read ladder — * values are as stale as their rows, never wrong. @@ -232,7 +257,7 @@ restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial /** - * Cold read: fold every registered unit over a stored log suffix, seeding + * Cold read: fold every persisted unit over a stored log suffix, seeding * each from its checkpoint row when usable — the one read recipe (cached * state + forward tail replay + `view`) applied without a live `Session`. * Call with the events returned by a persistence @@ -253,10 +278,10 @@ viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial * supplied event's seq, `baseSeq - 1` for an empty tail) plus the * refreshed checkpoint rows at that cut, ready for a durable write-back. */ -restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } +restore( checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number, ): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } ``` Types: [Session](session.md) · [SessionEvent](session.md) -Source: [`packages/session/session-projection/src/index.ts:171`](../../packages/session/session-projection/src/index.ts) +Source: [`packages/session/session-projection/src/index.ts:180`](../../packages/session/session-projection/src/index.ts) diff --git a/docs/subsystems/session-projection.zh.md b/docs/subsystems/session-projection.zh.md index bc1448e8ba..3defe8fa17 100644 --- a/docs/subsystems/session-projection.zh.md +++ b/docs/subsystems/session-projection.zh.md @@ -8,27 +8,30 @@ ## 投影单元 -`SessionProjectionMap` 是整条链路(host 侧单元、协议块、客户端钩子)的 merge-extensible 类型表;值是协议层 JSON 全量值,渲染归 slot 体系管,永远不归本层。领域为每个 key 贡献一个 `ProjectionDefinition`: +`SessionProjectionStateMap` 是 host 侧折叠状态的 merge-extensible 类型表,`SessionProjectionMap` 则继续表示客户端可见的全量值。领域为每个状态 key 贡献一个 `ProjectionDefinition`;`wire` 块使该 key 对客户端可见,渲染归 slot 体系管,永远不归本层: ```ts type-equiv /** - * One domain's state-driven computation unit: three pure synchronous - * functions plus declarations — never an opaque getter. The framework drives + * One domain's state-driven computation unit: a pure synchronous fold plus + * declarations and an optional client view — never an opaque getter. The framework drives * `apply` on every committed session event; the domain holds no - * subscriptions and owns only the mathematics. All three functions MUST be - * synchronous (an async unit would tear the carriers' consistency cut) and + * subscriptions and owns only the computation. All functions MUST be + * synchronous (an async unit would tear the carriers' consistency cut), and * `state` MUST be plain JSON (the persisted-cache precondition). */ -interface ProjectionDefinition { - /** The projection key this unit owns (its `SessionProjectionMap` entry). */ +interface ProjectionDefinition< + K extends keyof SessionProjectionStateMap, + S extends SessionProjectionStateMap[K] = SessionProjectionStateMap[K], +> { + /** The projection key this unit owns (its `SessionProjectionStateMap` entry). */ key: K - /** Validates the wire payload (`view` output) before it leaves the host. */ - schema: ZodType + /** Validates persisted state before it seeds a fold. */ + stateSchema: ZodType /** * State for the empty log. * @returns the initial state. */ - init(): S + init(): NoInfer /** * Pure transition: previous state + one committed event → next state. A * unit uninterested in an event MUST return the same state reference — an @@ -37,13 +40,18 @@ interface ProjectionDefinition { * @param event - the next committed session event. * @returns the next state (same reference when the event is not the unit's). */ - apply(state: S, event: SessionEvent): S - /** - * State → wire payload (the read-side projection). - * @param state - the current state. - * @returns the whole current value for this unit's key. - */ - view(state: S): SessionProjectionMap[K] + apply(state: NoInfer, event: SessionEvent): NoInfer + /** Client view. Omit for host-only units. */ + wire?: K extends keyof SessionProjectionMap ? { + /** Validates the wire payload before it leaves the host. */ + viewSchema: ZodType + /** + * State → wire payload (the read-side projection). + * @param state - the current state. + * @returns the whole current value for this unit's key. + */ + view(state: NoInfer): SessionProjectionMap[K] + } : never /** * Persisted-cache invalidation version: bump whenever the serialized state fields or the * fold semantics change, so persisted `(sessionId, key, ver, seq, val)` @@ -60,14 +68,14 @@ interface ProjectionDefinition { ```ts type-equiv /** - * One consistent read cut over every registered unit for one session. + * One consistent read cut over every registered client-visible unit for one session. * `asOfSeq` is the shared watermark — the seq of the last event every value * reflects (`-1` for an empty log, mirroring `session/subscribed.lastSeq`). */ interface ProjectionSnapshot { /** Seq of the last event the values reflect; -1 for an empty log. */ asOfSeq: number - /** Whole current value per registered key. */ + /** Whole current client value per registered key. */ values: Partial } ``` @@ -86,7 +94,7 @@ type ProjectionChangeListener = ( ) => void ``` -`snapshot(session)` 完全同步:载体在切出页面切片的同一 tick 内读取它,因此 `asOfSeq` 使两次读取使用同一个序号。每个值在返回前都会通过其单元的 schema 校验;如果 `view` 被误写为异步函数,它会返回 Promise,schema 校验将拒绝该值。对于每个已提交事件,变更流会为每个状态*引用*已变化的单元触发一次;状态未变时,`apply` 必须返回同一引用。 +`snapshot(session)` 完全同步:载体在切出页面切片的同一 tick 内读取它,因此 `asOfSeq` 使两次读取使用同一个序号。它只返回客户端视图,并在返回前通过各单元的 `viewSchema` 校验。`stateOf(session, key)` 可在不计算无关视图的情况下读取一份实时 host 状态;调用方不得修改这一借用引用。对于每个已提交事件,变更流会为每个状态*引用*已变化的客户端可见单元触发一次;状态未变时,`apply` 必须返回同一引用。 ## 注册表:`ctx.sessionProjections` @@ -154,7 +162,7 @@ Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../pack ### `ctx.sessionProjections` — `SessionProjectionRegistry` -`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. Registrants sharing a key share one unit and are counted: the same tool package mounted in N agent presets registers N times, and the key survives until the last one unloads. +`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference in a client-visible unit notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. Registrants sharing a key share one unit and are counted: the same tool package mounted in N agent presets registers N times, and the key survives until the last one unloads. ```ts cordis-catalog /** @@ -165,28 +173,45 @@ Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../pack * @param definition - key, state schema, pure unit functions, and stateVersion. * @returns the exact disposer that unregisters this unit. */ -register(definition: ProjectionDefinition): () => void +register< K extends keyof SessionProjectionMap, S extends SessionProjectionStateMap[K], >( definition: Omit, 'wire'> & { wire: NonNullable['wire']> }, ): () => void + +/** + * Register one host-only unit. Its state is omitted from client snapshots + * and always checkpointed like every other unit. + * @param definition - key, state schema, pure unit functions, and stateVersion. + * @returns the exact disposer that unregisters this unit. + */ +register< K extends Exclude, S extends SessionProjectionStateMap[K], >( definition: Omit, 'wire'>, ): () => void /** * Subscribe to the change feed. The registration is an effect on the * calling context's fiber. - * @param listener - called once per unit whose state reference changed, per committed event. + * @param listener - called once per client-visible unit whose state reference changed, per committed event. * @returns the exact disposer that unsubscribes. */ onChanged(listener: ProjectionChangeListener): () => void /** - * One consistent cut over every registered unit for one session, read from + * Read one unit's current host state without computing unrelated views. + * The returned value is live; callers must not mutate it. + * @param session - the session whose state is read. + * @param key - the registered unit key. + * @returns current state, or `undefined` when the key is not registered. + */ +stateOf( session: Session, key: K, ): SessionProjectionStateMap[K] | undefined + +/** + * One consistent cut over every registered client-visible unit for one session, read from * the watermark cache (missing cells fold lazily over the in-memory log). * Fully synchronous — every value and `asOfSeq` reflect the same log - * position. Each value passes its unit's schema before leaving. + * position. Each value passes its unit's `viewSchema` before leaving. * @param session - the session whose projection values are read. - * @returns the snapshot; `values` is empty when no unit is registered. + * @returns the snapshot; `values` is empty when no client-visible unit is registered. */ snapshot(session: Session): ProjectionSnapshot /** - * State-level checkpoint of every registered unit for one session, read + * State-level checkpoint of every persisted unit for one session, read * from the watermark cache (missing cells fold lazily over the in-memory * log). This is the write side of the persisted projection cache: the * returned rows are the `(key → {ver, seq, val})` part of the durable @@ -197,7 +222,7 @@ snapshot(session: Session): ProjectionSnapshot * every subsequent snapshot and frame through it (plain JSON by the unit * contract, so the clone is total). * @param session - the session whose unit states are checkpointed. - * @returns one row per registered key; empty when no unit is registered. + * @returns one row per registered key. */ checkpoint(session: Session): ProjectionCheckpoint @@ -221,8 +246,8 @@ restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined /** * View a checkpoint's rows without any log read: for every registered - * unit whose row's `ver` matches, serve the schema-validated - * `view` of the stored state; mismatched or absent rows leave their key + * client-visible unit whose row's `ver` matches, serve the schema-validated + * `view` of the schema-validated stored state; mismatched, malformed, or absent rows leave their key * absent (a cold or listing consumer treats it as not-yet-available and a * fuller read path refolds it). The zero-I/O rung of the read ladder — * values are as stale as their rows, never wrong. @@ -232,7 +257,7 @@ restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial /** - * Cold read: fold every registered unit over a stored log suffix, seeding + * Cold read: fold every persisted unit over a stored log suffix, seeding * each from its checkpoint row when usable — the one read recipe (cached * state + forward tail replay + `view`) applied without a live `Session`. * Call with the events returned by a persistence @@ -253,10 +278,10 @@ viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial * supplied event's seq, `baseSeq - 1` for an empty tail) plus the * refreshed checkpoint rows at that cut, ready for a durable write-back. */ -restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } +restore( checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number, ): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } ``` Types: [Session](session.md) · [SessionEvent](session.md) -Source: [`packages/session/session-projection/src/index.ts:171`](../../packages/session/session-projection/src/index.ts) +Source: [`packages/session/session-projection/src/index.ts:180`](../../packages/session/session-projection/src/index.ts) diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index 02e5b57c17..c267c20d47 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.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 docs/tool-catalog.md -tool-catalog.md: 13c56ed6d21c5aaac9909ed839ff111a867805df -tool-catalog.zh.md: e1b9581f25103cf5857af6de09bfbe03b3e52993 +tool-catalog.md: 92b6d8b92050d2dc822f016c18d31a81b43ef447 +tool-catalog.zh.md: 97a202b86744a4cf63819a12586a061a5daaaed4 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 13c56ed6d2..92b6d8b920 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -37,7 +37,7 @@ This table connects model-visible tool names to the plugin package and service s | `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`, `list_agents`, `send_message` | `ctx.tools`, `ctx.subagents`, `ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` and `interrupt_agent` once, plus `list_agents` from its separately loaded `/list-agents` plugin (whose catalog rows use the sessionProjections and live Agent registries). | | `@deepseek-ai/dsh-tool-subagent-report` | `report` | `ctx.subagents`, `ctx.systemPrompt`, `a live continuable in-process child Agent` | `tool/call`, `tool/result`, `a user-role message in the direct parent session` | - | Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global `toolFilter`. The same contribution installs the child-scoped `tool:report` prompt section, which this catalog does not render. The parent-facing `send_message` tool is installed independently. | | `@deepseek-ai/dsh-tool-jobs` | `job_kill`, `job_list`, `job_output` | `ctx.tools`, `ctx.jobs`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `user/message via agent.inject() for background completion notices` | - | The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. | -| `@deepseek-ai/dsh-tool-team` | `followup_task`, `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.teams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/message/queued`, `team/message/delivered`, `team/task`, `tool/result` | - | All ten tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. | +| `@deepseek-ai/dsh-experimental-tool-agent-team` | `followup_task`, `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/message/queued`, `team/message/delivered`, `team/task`, `tool/result` | - | All ten tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. | | `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task. | | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflowEngine`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. | @@ -1708,9 +1708,9 @@ Source: [`packages/jobs/tool-jobs/src/index.ts`](../packages/jobs/tool-jobs/src/ The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. - + -## `@deepseek-ai/dsh-tool-team` +## `@deepseek-ai/dsh-experimental-tool-agent-team` ### `followup_task` @@ -1736,7 +1736,7 @@ Send a durable follow-up task to another Team member and start a turn when neede } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `interrupt_agent` @@ -1757,7 +1757,7 @@ Interrupt one teammate's current turn while preserving its pending inbox. Team L } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `list_agents` @@ -1770,7 +1770,7 @@ List the Lead and every durable teammate with current runtime status. } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `send_message` @@ -1796,7 +1796,7 @@ Send durable information to another Team member without starting an idle member. } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `spawn_teammate` @@ -1835,7 +1835,7 @@ Create one named, durable teammate. Only the Team Lead may call this tool. } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_create` @@ -1875,7 +1875,7 @@ Create one unowned pending task on the shared Team task board. } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_get` @@ -1896,7 +1896,7 @@ Read the complete latest value of one shared task before changing or executing i } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_list` @@ -1935,7 +1935,7 @@ List shared tasks, including readiness, owner, revision, blockers, and write-sco } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_update` @@ -2002,7 +2002,7 @@ Compare-and-set a shared task action using the latest revision from team_task_ge } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `wait_agent` @@ -2020,7 +2020,7 @@ Wait for the next teammate status, mailbox, or shared-task change after this cal } ``` -Source: [`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) All ten tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index e1b9581f25..97a202b867 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -39,7 +39,7 @@ | `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`、`list_agents`、`send_message` | `ctx.tools`、`ctx.subagents`、`ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`、`tool/result`、`child session events through ctx.subagents` | - | 这些是控制可继续后台 subagent 的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message` 和 `interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 | | `@deepseek-ai/dsh-tool-subagent-report` | `report` | `ctx.subagents`、`ctx.systemPrompt`、`a live continuable in-process child Agent` | `tool/call`、`tool/result`、`a user-role message in the direct parent session` | - | 按可继续的进程内子级注册,而非全局注册,因此该 schema 仅在这种子级内部可见,并且不受其全局 `toolFilter` 影响。同一份贡献还会安装子级作用域的 `tool:report` 系统提示词 section,本目录不渲染该 section。面向父级的 `send_message` 工具单独安装。 | | `@deepseek-ai/dsh-tool-jobs` | `job_kill`、`job_list`、`job_output` | `ctx.tools`、`ctx.jobs`、`ctx.systemPrompt` | `tool/call`、`tool/result`、`user/message via agent.inject() for background completion notices` | - | 与任务种类无关的后台任务控制器:后台 bash 命令、PTY 发送和 subagent 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 | -| `@deepseek-ai/dsh-tool-team` | `followup_task`、`interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.teams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/message/queued`、`team/message/delivered`、`team/task`、`tool/result` | - | 这 10 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 | +| `@deepseek-ai/dsh-experimental-tool-agent-team` | `followup_task`、`interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agentTeams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/message/queued`、`team/message/delivered`、`team/task`、`tool/result` | - | 这 10 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 | | `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`、`owning Agent session` | `tool/call`、`todo/write`、`tool/result` | - | todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。 | | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`、`ctx.workflowEngine`、`ctx.systemPrompt`、`a calling Agent (exec.agent parents the script children)` | `tool/call`、`tool/result` | - | - | | `@deepseek-ai/dsh-tool-web` | `web_fetch`、`web_search` | `ctx.tools`、`ctx.web`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | web_search 和 web_fetch 将提供方选择置于 ctx.web 之后,使模型可见 schema 在更换后端时保持稳定。 | @@ -1714,7 +1714,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, -## `@deepseek-ai/dsh-tool-team` +## `@deepseek-ai/dsh-experimental-tool-agent-team` ### `followup_task` @@ -1740,7 +1740,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `interrupt_agent` @@ -1761,7 +1761,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `list_agents` @@ -1774,7 +1774,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `send_message` @@ -1800,7 +1800,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `spawn_teammate` @@ -1839,7 +1839,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_create` @@ -1879,7 +1879,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_get` @@ -1900,7 +1900,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_list` @@ -1939,7 +1939,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `team_task_update` @@ -2006,7 +2006,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) ### `wait_agent` @@ -2024,7 +2024,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/experimental/tool-team/src/index.ts`](../packages/experimental/tool-team/src/index.ts) +来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) 这 10 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 diff --git a/examples/acp-agent/tests/escalation.e2e.ts b/examples/acp-agent/tests/escalation.e2e.ts index 9f2bbf5f96..e754dc8799 100644 --- a/examples/acp-agent/tests/escalation.e2e.ts +++ b/examples/acp-agent/tests/escalation.e2e.ts @@ -13,6 +13,7 @@ import { type AgentUnderTest, type LaunchedAcpTestAgent, } from '@deepseek-ai/dsh-acp-snapshot' +import { bwrapProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts' import { cleanupAcpExampleTest } from './cleanup.ts' /** @@ -42,7 +43,7 @@ const AGENT: AgentUnderTest = { // bwrap on Linux, Seatbelt's sandbox-exec on macOS. Without one the strict // attempt would fail closed (SANDBOX_UNAVAILABLE) instead of producing the // denial this flow starts from. -const hasBwrap = spawnSync('bwrap', ['--ro-bind', '/', '/', '--dev', '/dev', '--proc', '/proc', '--die-with-parent', '--', 'true'], { +const hasBwrap = spawnSync('bwrap', [...bwrapProfileArgs({ mode: 'read-only', workspaceRoot: '/' }), '--', 'true'], { timeout: 5_000, stdio: 'ignore', }).status === 0 diff --git a/examples/headless-agent/team.cordis.snapshot.yml b/examples/headless-agent/team.cordis.snapshot.yml index 1d0ab574f4..d9be33caab 100644 --- a/examples/headless-agent/team.cordis.snapshot.yml +++ b/examples/headless-agent/team.cordis.snapshot.yml @@ -28,9 +28,9 @@ backgroundMode: one-shot maxDepth: 1 - insert: - - id: team - name: '@deepseek-ai/dsh-team' - - id: tool-team - name: '@deepseek-ai/dsh-tool-team' + - id: agent-team + name: '@deepseek-ai/dsh-experimental-agent-team' + - id: tool-agent-team + name: '@deepseek-ai/dsh-experimental-tool-agent-team' - id: team-fixture-llm name: './tests/fixtures/team-llm.mjs' diff --git a/examples/headless-agent/tests/snapshots/headless-profile/session.expected.jsonl b/examples/headless-agent/tests/snapshots/headless-profile/session.expected.jsonl index f90f798456..ef06a668f6 100644 --- a/examples/headless-agent/tests/snapshots/headless-profile/session.expected.jsonl +++ b/examples/headless-agent/tests/snapshots/headless-profile/session.expected.jsonl @@ -1,5 +1,5 @@ {"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0} -{"type":"permission/preset","seq":0,"time":0,"data":{"preset":"danger-full-access"}} +{"type":"permission/preset","seq":0,"time":0,"data":{"preset":"danger-full-access","origin":"default"}} {"type":"sandbox/mode","seq":1,"time":0,"data":{"mode":"danger-full-access"}} {"type":"approval/policy","seq":2,"time":0,"data":{"policy":"never"}} {"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Prove the product headless profile path with one real tool round trip."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} diff --git a/examples/package.json b/examples/package.json index 23f7768686..6dcdc21e28 100644 --- a/examples/package.json +++ b/examples/package.json @@ -82,7 +82,7 @@ "@deepseek-ai/dsh-subprocess-local": "workspace:*", "@deepseek-ai/dsh-system-prompt": "workspace:*", "@deepseek-ai/dsh-jobs-local": "workspace:*", - "@deepseek-ai/dsh-team": "workspace:*", + "@deepseek-ai/dsh-experimental-agent-team": "workspace:*", "@deepseek-ai/dsh-time-context": "workspace:*", "@deepseek-ai/dsh-tool-call-timeout-policy": "workspace:*", "@deepseek-ai/dsh-token-meter": "workspace:*", @@ -105,7 +105,7 @@ "@deepseek-ai/dsh-tool-subagent-control": "workspace:*", "@deepseek-ai/dsh-tool-subagent-report": "workspace:*", "@deepseek-ai/dsh-tool-jobs": "workspace:*", - "@deepseek-ai/dsh-tool-team": "workspace:*", + "@deepseek-ai/dsh-experimental-tool-agent-team": "workspace:*", "@deepseek-ai/dsh-tool-todo": "workspace:*", "@deepseek-ai/dsh-tool-web": "workspace:*", "@deepseek-ai/dsh-tool-workflow": "workspace:*", diff --git a/knip.json b/knip.json index 8733835187..280d10a1f0 100644 --- a/knip.json +++ b/knip.json @@ -79,24 +79,6 @@ "@deepseek-ai/.+" ] }, - "packages/util/home": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, - "packages/host/webserver": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/host/directory-picker-auto": { "ignoreDependencies": [ "@deepseek-ai/dsh-client-ui-directory-picker-browse", @@ -125,24 +107,6 @@ "tests/**/*.{ts,tsx}" ] }, - "packages/client/web-ui": { - "entry": [ - "tests/**/*.spec.{ts,tsx}" - ], - "project": [ - "src/**/*.{ts,tsx}", - "tests/**/*.{ts,tsx}" - ] - }, - "packages/client/runtime": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/api/remotes": { "entry": [ "tests/**/*.e2e.ts" @@ -247,15 +211,6 @@ "tests/**/*.ts" ] }, - "packages/core/tools": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/typert/generator": { "entry": [ "tests/**/*.spec.ts", @@ -307,15 +262,6 @@ "tests/**/*.ts" ] }, - "packages/context/tmux-context": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/lsp/lsp-stdio": { "entry": [ "tests/**/*.spec.ts", @@ -360,24 +306,6 @@ "src/**/*.ts" ] }, - "packages/util/timeout": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, - "packages/util/output-retention": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/test-support/acp-snapshot": { "entry": [ "tests/**/*.spec.ts", @@ -418,24 +346,6 @@ "tests/**/*.ts" ] }, - "packages/goal/goal-round-driver": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, - "packages/goal/tool-goal": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/session-query/session-query-sqlite": { "entry": [ "tests/**/*.spec.ts", @@ -526,15 +436,6 @@ "tests/**/*.ts" ] }, - "packages/util/home-paths": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/web/web-search-exa": { "entry": [ "tests/**/*.spec.ts", @@ -673,15 +574,6 @@ "tests/**/*.ts" ] }, - "packages/fs/tool-fs-search": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/mcp/mcp-client": { "entry": [ "tests/**/*.spec.ts", @@ -713,15 +605,6 @@ "tests/**/*.tsx" ] }, - "packages/client/ui-settings": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "apps/web": { "entry": [ "tests/**/*.e2e.ts", @@ -757,24 +640,6 @@ "@deepseek-ai/.+" ] }, - "packages/client/modules": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, - "packages/client/hmr": { - "entry": [ - "tests/**/*.spec.ts" - ], - "project": [ - "src/**/*.ts", - "tests/**/*.ts" - ] - }, "packages/subagent/subagent-dsh-sdk": { "entry": [ "tests/**/*.spec.ts", diff --git a/package.json b/package.json index d42108564d..87a7dbfe24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-root", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "license": "MIT", "private": true, "type": "module", diff --git a/packages/acp/acp/package.json b/packages/acp/acp/package.json index b099fd90f3..ed61407c2d 100644 --- a/packages/acp/acp/package.json +++ b/packages/acp/acp/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-acp", "description": "Automation-only Agent Client Protocol server for driving DeepSeek Harness agents over JSON-RPC stdio", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/api/gateway/package.json b/packages/api/gateway/package.json index 99a489fc3c..4c9aca7dce 100644 --- a/packages/api/gateway/package.json +++ b/packages/api/gateway/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-gateway", "description": "Typert Remote Host dispatcher and Client API endpoint", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/api/remotes/package.json b/packages/api/remotes/package.json index b4415a6457..e73460358f 100644 --- a/packages/api/remotes/package.json +++ b/packages/api/remotes/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-remotes", "description": "Remote BFF assembly and Host Agent/Session lookup policy", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/attachment/attachment-local/package.json b/packages/attachment/attachment-local/package.json index 176a728da9..655dd29e74 100644 --- a/packages/attachment/attachment-local/package.json +++ b/packages/attachment/attachment-local/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-attachment-local", "description": "Private content-addressed DSH_HOME attachment storage", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/attachment/attachment/package.json b/packages/attachment/attachment/package.json index 87764ef5e2..982c35f9d5 100644 --- a/packages/attachment/attachment/package.json +++ b/packages/attachment/attachment/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-attachment", "description": "Durable immutable attachment storage seam for the DeepSeek Harness", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/boot/app-boot/package.json b/packages/boot/app-boot/package.json index a31983a599..cc7cb7c59a 100644 --- a/packages/boot/app-boot/package.json +++ b/packages/boot/app-boot/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-app-boot", "description": "Shared boot glue for the app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, and the Loader boot sequence", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/boot/cmdline/package.json b/packages/boot/cmdline/package.json index 6ec5f68a74..e5901f49ca 100644 --- a/packages/boot/cmdline/package.json +++ b/packages/boot/cmdline/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-cmdline", "description": "Immutable command-line handoff from a dsh launcher to any app plugin that injects cmdlineArgs", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/base/package.json b/packages/bundle/base/package.json index 62350bbc11..ce984ddbf6 100644 --- a/packages/bundle/base/package.json +++ b/packages/bundle/base/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-base", "description": "The shared dsh core as a profile bundle: every profile's first patch layer, inserting the base plugin rows over the empty profile root", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/headless/package.json b/packages/bundle/headless/package.json index 133c79bb22..f639ec8415 100644 --- a/packages/bundle/headless/package.json +++ b/packages/bundle/headless/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-headless", "description": "The dsh one-shot bundle: a direct core Agent/Session runner over dsh-base with no Host, HTTP, or browser layer", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/web-app/package.json b/packages/bundle/web-app/package.json index c753882a98..dc87136e1b 100644 --- a/packages/bundle/web-app/package.json +++ b/packages/bundle/web-app/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-web-app", "description": "The dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/connection/package.json b/packages/client/connection/package.json index 49921b4df9..436c2204a6 100644 --- a/packages/client/connection/package.json +++ b/packages/client/connection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-connection", "description": "Wire consumer layer: HTTP-up/WebSocket-down client, ConnectionController dual streams with reconnect, and fixture api", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/hmr/package.json b/packages/client/hmr/package.json index fb5aadce7f..6677badd7e 100644 --- a/packages/client/hmr/package.json +++ b/packages/client/hmr/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-hmr", "description": "Dev-only hot-reload driver for script-loaded client entries: SSE rebuilt frames → invalidate/prefetch → fiber swap through the vendored Loader entry", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/locale/package.json b/packages/client/locale/package.json index 7ec5b185e5..df4992e523 100644 --- a/packages/client/locale/package.json +++ b/packages/client/locale/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-locale", "description": "Locale plugin: Host-backed zh/en preference, browser-derived fallback, locale snapshots, and typed namespace dictionaries", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/modules/package.json b/packages/client/modules/package.json index 6fe482ceca..ccdd0ee40d 100644 --- a/packages/client/modules/package.json +++ b/packages/client/modules/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-modules", "description": "Client module system, dual-face: node half composes the __DSH_BOOT__ entry graph (incremental dsh.client scan, bundle route, index tap, webPlugins service); browser half is the lazy-CJS module table the vendored cordis Loader consumes as its internal seam", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/runtime/package.json b/packages/client/runtime/package.json index ca21359f2f..051f573010 100644 --- a/packages/client/runtime/package.json +++ b/packages/client/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-runtime", "description": "Client core services: SlotRegistry, SessionRuntime (scope tree + object layer)", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/runtime/src/client/contract/sessions-port.ts b/packages/client/runtime/src/client/contract/sessions-port.ts index 551c3a16ee..8b9bf49229 100644 --- a/packages/client/runtime/src/client/contract/sessions-port.ts +++ b/packages/client/runtime/src/client/contract/sessions-port.ts @@ -32,11 +32,15 @@ export interface SessionsPort { /** Observable list snapshot (read face only; writes stay inside the sessions domain). */ readonly list: ObservableSnapshot /** - * Create a session on the host. - * @param opts - target workspace. - * @returns the new session id. + * Create or explicitly adopt a session on the host. + * @param opts - target workspace and optional confirmed blank-reuse id. + * @returns the created or adopted session id. */ - create(opts: { workspaceId: WorkspaceId }): Promise + create(opts: { + workspaceId: WorkspaceId + sessionId?: SessionId + reuseWorkspaceBlank?: true + }): Promise /** * Select a session as current. * @param id - session id (must exist in the list store). diff --git a/packages/client/runtime/src/client/sessions/manager.ts b/packages/client/runtime/src/client/sessions/manager.ts index 13aa20d1c8..bb8ed7bd6c 100644 --- a/packages/client/runtime/src/client/sessions/manager.ts +++ b/packages/client/runtime/src/client/sessions/manager.ts @@ -534,10 +534,18 @@ export class SessionManager { * @returns the create result. */ async create( - opts: { workspaceId?: WorkspaceId; cwd?: string; sessionId?: SessionId } = {}, + opts: { + workspaceId?: WorkspaceId + cwd?: string + sessionId?: SessionId + reuseWorkspaceBlank?: true + } = {}, ): Promise> { try { - const shared = opts.sessionId === undefined ? {} : { sessionId: opts.sessionId } + const shared = { + ...(opts.sessionId === undefined ? {} : { sessionId: opts.sessionId }), + ...(opts.reuseWorkspaceBlank === undefined ? {} : { reuseWorkspaceBlank: opts.reuseWorkspaceBlank }), + } const payload = opts.workspaceId !== undefined ? { workspaceId: opts.workspaceId, ...shared } : { ...(opts.cwd === undefined ? {} : { cwd: opts.cwd }), ...shared } diff --git a/packages/client/runtime/src/client/sessions/service.ts b/packages/client/runtime/src/client/sessions/service.ts index c66da4e0d3..5e23b3a10a 100644 --- a/packages/client/runtime/src/client/sessions/service.ts +++ b/packages/client/runtime/src/client/sessions/service.ts @@ -482,7 +482,12 @@ export class SessionRuntime implements ISessions { * @returns the new session id. * @throws {SessionCreateError} with the requested id. */ - async create(opts: { workspaceId?: WorkspaceId; cwd?: string; sessionId?: SessionId } = {}): Promise { + async create(opts: { + workspaceId?: WorkspaceId + cwd?: string + sessionId?: SessionId + reuseWorkspaceBlank?: true + } = {}): Promise { const result = await this.manager.create(opts) if (!result.ok) throw new SessionCreateError(result.error, opts.sessionId) this.projectList() diff --git a/packages/client/runtime/src/client/workspaces/service.ts b/packages/client/runtime/src/client/workspaces/service.ts index c23e3b3a6c..bfce0c6e28 100644 --- a/packages/client/runtime/src/client/workspaces/service.ts +++ b/packages/client/runtime/src/client/workspaces/service.ts @@ -53,7 +53,7 @@ export class WorkspaceRuntime implements IWorkspaces { readonly list: SnapshotStore /** Workspace baseline and frame owner. */ private readonly manager: WorkspaceManager - /** In-flight blank-session creates keyed by workspace (connectWorkspace coalescing). */ + /** In-flight blank-session connects keyed by workspace (reuse or create). */ private readonly connecting = new Map>() /** Guards the runtime-owned one-shot initial-selection subscription. */ private initialSelectionStarted = false @@ -76,9 +76,11 @@ export class WorkspaceRuntime implements IWorkspaces { /** * Resolve the session a New Session flow lands in once this Workspace is - * chosen: reuse the workspace's existing blank session when one is in the - * list mirror, else create a fresh one on the host (`session.create` births - * the full Session+Agent — the client holds no intermediate state). The + * chosen: explicitly adopt the workspace's existing blank session when one + * is in the list mirror, else create a fresh one on the host + * (`session.create` births or resumes the full Session+Agent — the client + * holds no intermediate state). The adoption tells optional default owners + * that this exact session passed the reuse checks. * caller owns navigation: take the returned id to `sessions.open`. * Resolution guarantee (both arms): the returned id is already in the list * store and `sessions.binding(id)` resolves synchronously — draft hand-off @@ -107,7 +109,13 @@ export class WorkspaceRuntime implements IWorkspaces { const summary = sessions.byId[id] if (summary !== undefined && summary.blank && summary.cwd === workspace.path && workspace.sessionIds.includes(summary.id) - && !archived.includes(summary.id)) return summary.id + && !archived.includes(summary.id)) { + return this.sessions.create({ + workspaceId, + sessionId: summary.id, + reuseWorkspaceBlank: true, + }) + } } const attempt = this.sessions.create({ workspaceId }) .finally(() => { this.connecting.delete(workspaceId) }) diff --git a/packages/client/runtime/tests/workspaces-service.client.spec.ts b/packages/client/runtime/tests/workspaces-service.client.spec.ts index cfdfa98e9b..68736f0a58 100644 --- a/packages/client/runtime/tests/workspaces-service.client.spec.ts +++ b/packages/client/runtime/tests/workspaces-service.client.spec.ts @@ -242,20 +242,28 @@ describe('WorkspaceRuntime', () => { { sessionId: sid('s-stray'), updatedAt: 4, running: false, blank: true, cwd: '/w/gamma' }, ] as never[], })) + api.onCreate = payload => Promise.resolve(ok({ + sessionId: (payload as { sessionId?: SessionId }).sessionId ?? sid('s-unexpected'), + })) await Promise.all([workspaces.refresh(), sessions.refresh()]) await Promise.resolve() - // Hit: same workspace → the parked member blank comes back (the earlier - // cwd-matching non-member stray is skipped), no create RPC. + // Hit: same workspace → the parked member blank is explicitly adopted + // after the earlier cwd-matching non-member stray is skipped. await expect(workspaces.connectWorkspace(wid('alpha'))).resolves.toBe('s-blank') - expect(api.callsOf('session.create')).toEqual([]) + expect(api.callsOf('session.create')).toEqual([{ + workspaceId: 'alpha', sessionId: 's-blank', reuseWorkspaceBlank: true, + }]) // Resolution guarantee: the id is binding-resolvable synchronously. expect(sessions.binding(sid('s-blank'))).toBeDefined() // Miss: beta has only a non-blank session → host create with workspaceId. api.onCreate = () => Promise.resolve(ok({ sessionId: sid('s-fresh') })) await expect(workspaces.connectWorkspace(wid('beta'))).resolves.toBe('s-fresh') - expect(api.callsOf('session.create')).toEqual([{ workspaceId: 'beta' }]) + expect(api.callsOf('session.create')).toEqual([ + { workspaceId: 'alpha', sessionId: 's-blank', reuseWorkspaceBlank: true }, + { workspaceId: 'beta' }, + ]) // Same guarantee on the create arm (draft hand-off writes the machine pre-open). expect(sessions.binding(sid('s-fresh'))).toBeDefined() @@ -263,7 +271,11 @@ describe('WorkspaceRuntime', () => { // never reused, a fresh accounted session is created instead. api.onCreate = () => Promise.resolve(ok({ sessionId: sid('s-fresh-3') })) await expect(workspaces.connectWorkspace(wid('gamma'))).resolves.toBe('s-fresh-3') - expect(api.callsOf('session.create')).toEqual([{ workspaceId: 'beta' }, { workspaceId: 'gamma' }]) + expect(api.callsOf('session.create')).toEqual([ + { workspaceId: 'alpha', sessionId: 's-blank', reuseWorkspaceBlank: true }, + { workspaceId: 'beta' }, + { workspaceId: 'gamma' }, + ]) // Unknown workspace fails loud instead of silently creating in nowhere. await expect(workspaces.connectWorkspace(wid('ghost'))).rejects.toThrow(/unknown workspace ghost/) @@ -284,6 +296,9 @@ describe('WorkspaceRuntime', () => { api.onList = () => Promise.resolve(ok({ items: [{ sessionId: sid('s-blank'), updatedAt: 2, running: false, blank: true, cwd: '/w/alpha' }] as never[], })) + api.onCreate = payload => Promise.resolve(ok({ + sessionId: (payload as { sessionId?: SessionId }).sessionId ?? sid('s-unexpected'), + })) await Promise.all([workspaces.refresh(), sessions.refresh()]) await Promise.resolve() const session = sessions.binding(sid('s-blank'))!.session @@ -292,7 +307,9 @@ describe('WorkspaceRuntime', () => { await Promise.resolve() // Failure leaves blank intact, so the same session is still the reuse hit. await expect(workspaces.connectWorkspace(wid('alpha'))).resolves.toBe('s-blank') - expect(api.callsOf('session.create')).toEqual([]) + expect(api.callsOf('session.create')).toEqual([{ + workspaceId: 'alpha', sessionId: 's-blank', reuseWorkspaceBlank: true, + }]) }) it('returns created Workspaces and preserves Host business errors', async () => { diff --git a/packages/client/ui-agent-preset/package.json b/packages/client/ui-agent-preset/package.json index 8b4afb0fb3..81763a3e3e 100644 --- a/packages/client/ui-agent-preset/package.json +++ b/packages/client/ui-agent-preset/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-agent-preset", "description": "Agent-preset surfaces: the default for later sessions, this session's seat, and the composition editor", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-attachment/package.json b/packages/client/ui-attachment/package.json index 8e22c28e57..039e1a0d5f 100644 --- a/packages/client/ui-attachment/package.json +++ b/packages/client/ui-attachment/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-attachment", "description": "Dynamic attachment presentation plugin for conversation input and message-image slots", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-brand-official/package.json b/packages/client/ui-brand-official/package.json index 25c1c93cab..d08317fbbc 100644 --- a/packages/client/ui-brand-official/package.json +++ b/packages/client/ui-brand-official/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-brand-official", "description": "Official DeepSeek Harness brand occupants for the Web client's sidebar and conversation Hero slots", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-commands/package.json b/packages/client/ui-commands/package.json index c030024ff9..5cced81487 100644 --- a/packages/client/ui-commands/package.json +++ b/packages/client/ui-commands/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-commands", "description": "Client command surface: global directory cache, '/' source, three command UI kinds, popupSelect registry", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 8131af3495..74a73ffb4a 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/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 packages/client/ui-conversation/README.md -README.md: dae46f4398dcbb6dcd50c0d7bdfac13be14d5dc2 -README.zh.md: 0b7af0b42eee060f7c827cdc9e06bc07b7bcc4ef +README.md: 71dafd1c4d683f2d6da8fefd88aec81d56149481 +README.zh.md: e4d4ab990c74229bd2e077b575e7c87bb648ade9 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index dae46f4398..71dafd1c4d 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -14,7 +14,7 @@ The view ring is a slot: the strict session-body registration declares the sessi Chat business rows are independent registry contributions rather than a closed built-in union. A client plugin declaration-merges its typed `ChatNodeDataMap` key, registers a `ConversationNodeDefinition` on `ctx.conversationEvents`, and registers the matching keyed renderer on `conversation.chat.node`; it does not modify Session folds or a central renderer switch. The [Conversation Node cookbook](../../../docs/cookbook/adding-a-conversation-node.md) covers stable event ids, append/prepend replay, Location data, and renderer constraints. -Approvals take over the composer through the chain this package declares: `ApprovalPanel` registers as a selector-routed `'conversation.composer'` entry (the ui-user-questions pattern) and occupies the composer in place of the InputBar while an approval wait is pending (amber strip, justification headline, paired command line from the running call's args, one-shot refuse/allow). The `PendingApproval` domain face in `contract/slots.ts` owns the wire encoding — the `ApprovalResponsePayload` value with the audit correlation — over the runtime's `PendingWait` carrier; the broadcast `approval/resolved` frame settles the wait and restores the composer. The runtime manager projects every approval or question wait through `SessionSummary.pendingInteraction`, including sessions never instantiated; `ui-workspace` owns its sidebar presentation. Pending waits leave the message flow entirely: questions (ui-user-questions) and approvals (ApprovalPanel) both answer through the composer takeover, so no display-only placeholder card remains. The composer's bottom-row Access seat mounts `PermissionSelect`, fed by the host-computed `permissions` projection through the standard-kit `useProjection` (key absence hides the chip); the chip opens a Menu-primitive dropdown whose kebab-case preset names render as title-case labels. Safe preset picks submit `/permission ` immediately through the bar's injected `command` callback, while `danger-full-access` is presented as `Full access` and first opens an in-page Modal risk confirmation. The enabling action stays disabled until the user checks the acknowledgement; cancel, Escape, close, and mask click submit nothing. +Approvals take over the composer through the chain this package declares: `ApprovalPanel` registers as a selector-routed `'conversation.composer'` entry (the ui-user-questions pattern) and occupies the composer in place of the InputBar while an approval wait is pending (amber strip, justification headline, paired command line from the running call's args, one-shot refuse/allow). The `PendingApproval` domain face in `contract/slots.ts` owns the wire encoding — the `ApprovalResponsePayload` value with the audit correlation — over the runtime's `PendingWait` carrier; the broadcast `approval/resolved` frame settles the wait and restores the composer. The runtime manager projects every approval or question wait through `SessionSummary.pendingInteraction`, including sessions never instantiated; `ui-workspace` owns its sidebar presentation. Pending waits leave the message flow entirely: questions (ui-user-questions) and approvals (ApprovalPanel) both answer through the composer takeover, so no display-only placeholder card remains. The composer's bottom-row Access seat mounts `PermissionSelect`, fed by the host-computed `permissions` projection through the standard-kit `useProjection` (key absence hides the chip); the chip opens a Menu-primitive dropdown whose built-in preset ids render as localized product labels only when the host keeps their canonical names, while explicit host labels remain unchanged and unknown kebab-case names render in title case. Safe preset picks submit `/permission ` immediately through the bar's injected `command` callback, while `danger-full-access` first opens an in-page Modal risk confirmation. The enabling action stays disabled until the user checks the acknowledgement; cancel, Escape, close, and mask click submit nothing. The session header renders the session-scoped `'conversation.session.header.actions'` list beside the title and the independent `'conversation.session.header.utilities'` list at the right edge. Session context and lineage controls remain in `actions`; optional Session utilities cannot reorder or move them. The composer chain currency includes the current conversation `session`; ui-subagent selects one-shot or parent-unavailable addressed sessions for reason-specific read-only copy, while the ordinary InputBar keeps every addressed child Send-only because the continuation service exposes no public per-Activation cancellation operation and `session.cancel` would bypass its ownership. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 0b7af0b42e..e4d4ab990c 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -24,7 +24,7 @@ Think 行默认保持折叠,并在不展开思维链的情况下暴露实时 聊天流会将跨重试轮次连续出现的模型重试节点投影为一个稳定的弱化状态行,并用最新一次尝试更新该行;每个重试事件仍保留在运行时快照与会话日志中。前端倒计时以客户端收到事件的时刻为计划延迟的起点,避免 Host 与浏览器的时钟偏差;剩余时间向上取整到秒,且下限为 1 秒。最近一次尚未完成的重试会显示从左到右的文字渐变动画。后续轮次事实用于区分已开始的尝试与在退避期间取消的尝试,Host 的 running 位只控制实时动画;随后该行会显示静态的已完成或已取消标签。normal 策略行显示有限重试上限;always 策略行显示 `∞`。激活该行会显示最近一次重试的精确延迟和失败消息。客户端运行时会在相应重试节点到达前移除每个失败步骤的流式输出尾部;后续某次尝试成功后,该状态仍保持可见。未进入重试的终态失败会在其轮次边界渲染为持久的内联状态,展示适合显示的持久消息与可选错误码,但不会提供 Host 无法兑现的操作;AUTH 文案绝不会回显提供方给出的凭据片段。 -审批通过本包声明的链条接管编辑器:`ApprovalPanel` 注册为按选择器路由的 `'conversation.composer'` 配置项(ui-user-questions 模式),在审批等待未决期间取代 InputBar 占据编辑器(琥珀色条、理由标题、来自运行中调用参数的配对命令行、一次性的拒绝/允许)。`contract/slots.ts` 中的 `PendingApproval` 领域面在运行时 `PendingWait` 载体之上拥有 wire 编码——带审计关联的 `ApprovalResponsePayload` 值;广播的 `approval/resolved` 帧使等待落定并恢复编辑器。运行时 manager 会将所有审批或问题等待通过 `SessionSummary.pendingInteraction` 投影出来,未实例化的会话也不例外;`ui-workspace` 负责其侧边栏呈现。未决等待完全离开消息流:问题(ui-user-questions)与审批(ApprovalPanel)都经编辑器接管作答,不再保留只读占位卡。编辑器底行的 Access 席位挂载 `PermissionSelect`,由 host 计算的 `permissions` 投影经标准工具包 `useProjection` 供数(key 缺席即隐藏 chip);chip 打开 Menu 原语下拉,其中 kebab-case 预设名渲染为 Title Case 标签;普通安全预设会立即经输入栏注入的 `command` 回调提交 `/permission `,而 `danger-full-access` 在界面中显示为 `Full access`,选择后先打开页面内的 Modal 风险确认。用户勾选确认项前启用按钮始终不可用;取消、Escape、关闭按钮与点击遮罩都不会提交命令。 +审批通过本包声明的链条接管编辑器:`ApprovalPanel` 注册为按选择器路由的 `'conversation.composer'` 配置项(ui-user-questions 模式),在审批等待未决期间取代 InputBar 占据编辑器(琥珀色条、理由标题、来自运行中调用参数的配对命令行、一次性的拒绝/允许)。`contract/slots.ts` 中的 `PendingApproval` 领域面在运行时 `PendingWait` 载体之上拥有 wire 编码——带审计关联的 `ApprovalResponsePayload` 值;广播的 `approval/resolved` 帧使等待落定并恢复编辑器。运行时 manager 会将所有审批或问题等待通过 `SessionSummary.pendingInteraction` 投影出来,未实例化的会话也不例外;`ui-workspace` 负责其侧边栏呈现。未决等待完全离开消息流:问题(ui-user-questions)与审批(ApprovalPanel)都经编辑器接管作答,不再保留只读占位卡。编辑器底行的 Access 席位挂载 `PermissionSelect`,由 host 计算的 `permissions` 投影经标准工具包 `useProjection` 供数(key 缺席即隐藏 chip);chip 打开 Menu 原语下拉,其中内置预设 id 仅在 host 保留规范名称时渲染为本地化产品标签,显式 host 标签保持原样,未知 kebab-case 预设名仍渲染为 Title Case 标签;普通安全预设会立即经输入栏注入的 `command` 回调提交 `/permission `,而 `danger-full-access` 选择后先打开页面内的 Modal 风险确认。用户勾选确认项前启用按钮始终不可用;取消、Escape、关闭按钮与点击遮罩都不会提交命令。 `TodoDock` 以 `order: 0` 占用 `'conversation.input.dock'` 列表 slot(位于 Goal 与 Queue 之前),作为计划条读取 host 计算的 `todos` 投影(当前计划:其后没有更晚 `turn/start` 的最近一次 `todo/write`)并渲染 `TodoPanel`。面板接收纯列表,列表为空时自我隐藏;列表非空时默认折叠,表头显示标题及以 `·` 连接的各状态计数(如 `1 已完成 · 2 进行中 · 1 待处理`,省略零计数)。dock adapter 拥有 selection,因此面板保持为 props 的纯函数。输入区 composer 链隐藏的一切也会隐藏整个 dock。`todo_write` 工具行属于 [`ui-tool`](../ui-tool/README.md)。 diff --git a/packages/client/ui-conversation/package.json b/packages/client/ui-conversation/package.json index 8fd8c44d4c..a8596f1209 100644 --- a/packages/client/ui-conversation/package.json +++ b/packages/client/ui-conversation/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-ui-conversation", "description": "Conversation domain: skeleton, ordered chat flow, composer with the Host-backed busy-Enter preference, and details host", - "version": "0.1.0-rc.7", + "version": "0.1.0-rc.8", "publishConfig": { "access": "public" }, diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts index f4e7a7c59a..db4d218d21 100644 --- a/packages/client/ui-conversation/src/client/locales.ts +++ b/packages/client/ui-conversation/src/client/locales.ts @@ -66,11 +66,14 @@ export const zh = { 'settings.enter.description': '仅在智能体运行时生效;Cmd/Ctrl+Enter 使用另一行为', 'settings.enter.queue': '排队发送', 'settings.enter.steer': '插话发送', - 'access.confirm.title': '确认启用 Full access?', - 'access.confirm.description': '启用 Full access 后,agent 将减少确认步骤,并且可以直接执行更多操作,包括敏感操作、文件修改或外部命令。仅建议在你信任当前任务时使用。', + 'access.preset.readOnly': '仅可查看', + 'access.preset.workspaceWrite': '可写入工作区', + 'access.preset.fullAccess': '完全权限', + 'access.confirm.title': '确认启用完全权限?', + 'access.confirm.description': '启用完全权限后,智能体将减少确认步骤,并且可以直接执行更多操作,包括敏感操作、文件修改或外部命令。仅建议在你信任当前任务时使用。', 'access.confirm.acknowledge': '我已了解风险,并愿意继续', 'access.confirm.cancel': '取消', - 'access.confirm.enable': '启用 Full access', + 'access.confirm.enable': '启用完全权限', 'hero.headline': '探索未至之境', 'hero.preview': '预览版', 'hero.chooseWorkspace': '选择工作区', @@ -243,6 +246,9 @@ export const en = { 'settings.enter.description': 'Busy only; Cmd/Ctrl+Enter uses the other behavior', 'settings.enter.queue': 'Queue', 'settings.enter.steer': 'Steer', + 'access.preset.readOnly': 'Read Only', + 'access.preset.workspaceWrite': 'Workspace Write', + 'access.preset.fullAccess': 'Full access', 'access.confirm.title': 'Enable Full access?', 'access.confirm.description': 'Full access reduces confirmation steps and lets the agent perform more actions directly, including sensitive operations, file changes, or external commands. Only use it when you trust the current task.', 'access.confirm.acknowledge': 'I understand the risks and want to continue', diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx index 14feaca291..2d96e3bf33 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx @@ -535,10 +535,10 @@ export function InputBar({ } type Boundary = | { at: number; kind: 'chip'; chip: (typeof deco.chips)[number] } - | { at: number; kind: 'text-ref'; ref: (typeof deco.textRefs)[number] } + | { at: number; kind: 'text-ref'; ref: (typeof deco.textRefs)[number]; ordinal: number } const boundaries: Boundary[] = [ ...deco.chips.map(chip => ({ at: chip.offset, kind: 'chip' as const, chip })), - ...deco.textRefs.map(ref => ({ at: ref.start, kind: 'text-ref' as const, ref })), + ...deco.textRefs.map((ref, ordinal) => ({ at: ref.start, kind: 'text-ref' as const, ref, ordinal })), ].sort((a, b) => a.at - b.at) for (const b of boundaries) { if (b.at < cursor) continue // claim-token overlap: the leading mark wins @@ -570,9 +570,14 @@ export function InputBar({ } else { // Plain-range highlight: the glyphs stay the // textarea's (advance untouched); the mark paints the chip look. + // The key is the draft-order ordinal: a fresh scan derives these + // ranges every render, so none of them carries identity past its + // position, and a draft-offset key would unmount the mark and its + // icon for every character typed ahead of it. Structured references + // key by occurrenceId, the identity their occurrence table owns. const text = draft.slice(b.ref.start, b.ref.end) backdrop.push( - + {b.ref.appearance === 'folder' ? ( <> diff --git a/packages/client/ui-conversation/src/client/skeleton/PermissionSelect.tsx b/packages/client/ui-conversation/src/client/skeleton/PermissionSelect.tsx index 5230f59835..b7ba37a7e0 100644 --- a/packages/client/ui-conversation/src/client/skeleton/PermissionSelect.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/PermissionSelect.tsx @@ -15,14 +15,14 @@ const FULL_ACCESS = 'danger-full-access' const shieldOutline = 'M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z' -const permissionGlyphs = { - 'read-only': ( +const permissionGlyphs = new Map([ + ['read-only', ( - ), - 'workspace-write': ( + )], + ['workspace-write', ( @@ -30,35 +30,44 @@ const permissionGlyphs = { - ), - [FULL_ACCESS]: ( + )], + [FULL_ACCESS, ( - ), -} as Record + )], +]) /** Glyph for a permission option value; host-configured names outside the design set get none. */ function permissionGlyph(value: string): ReactNode | undefined { - return permissionGlyphs[value] + return permissionGlyphs.get(value) } /** - * Display transform: kebab-case machine names render as title-case labels - * (`workspace-write` → `Workspace Write`); non-kebab host-configured names - * pass through. Full access intentionally overrides the machine-name - * transform so both permission surfaces use the product label `Full access`; - * the warning body remains locale-aware. + * Display transform: built-in machine names render as locale product labels; + * non-kebab host-configured names pass through. */ function displayName(name: string): string { if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) return name return name.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') } -function optionLabel(option: PermissionSelectValue['options'][number]): string { - return option.value === FULL_ACCESS ? 'Full access' : displayName(option.name) +const BUILT_IN_PERMISSION_NAMES = new Map([ + ['read-only', 'Read Only'], + ['workspace-write', 'Workspace Write'], + [FULL_ACCESS, 'Full access'], +]) + +function permissionLabel(value: string, name: string, t: ComposerBarProps['t']): string { + const builtInName = BUILT_IN_PERMISSION_NAMES.get(value) + if (builtInName !== undefined && (name === value || name === builtInName)) { + if (value === 'read-only') return t('access.preset.readOnly') + if (value === 'workspace-write') return t('access.preset.workspaceWrite') + if (value === FULL_ACCESS) return t('access.preset.fullAccess') + } + return displayName(name) } export interface PermissionSelectProps { @@ -86,13 +95,20 @@ export function PermissionSelect({ value, locked, command, t }: PermissionSelect const currentValue = pick ?? value.currentValue const current = value.options.find(option => option.value === currentValue) + const currentLabel = current === undefined + ? permissionLabel(currentValue, currentValue, t) + : permissionLabel(current.value, current.name, t) const busy = pick !== null || confirmation !== null const items: MenuEntry[] = value.options .filter(o => o.value !== 'custom') .map((option) => { const icon = permissionGlyph(option.value) - return { id: option.value, label: optionLabel(option), ...icon === undefined ? {} : { icon } } + return { + id: option.value, + label: permissionLabel(option.value, option.name, t), + ...icon === undefined ? {} : { icon }, + } }) const submit = (id: string): void => { @@ -138,7 +154,7 @@ export function PermissionSelect({ value, locked, command, t }: PermissionSelect