docs(i18n): proofread active Chinese documentation
This commit is contained in:
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-06-16-typed-event-schemas.md
|
||||
2026-06-16-typed-event-schemas.md: 97a7d0c3787eb5556696e25a6c8b1bb75642aba9
|
||||
2026-06-16-typed-event-schemas.zh.md: c19f67c6ff058d42293ff2b6346630fe91c54dec
|
||||
2026-06-16-typed-event-schemas.zh.md: d0c9ffb3640cb76f0c656e4ac841dc3817bcb86d
|
||||
|
||||
@@ -13,13 +13,13 @@ harness 将其核心词汇——内容块、消息来源、结束原因、轮次
|
||||
1. **持久化将 `event.data` 视为不透明 JSON。** JSONL/SQLite 后端对每个事件逐字 `JSON.stringify`/`JSON.parse`;唯一的运行时守卫是 `isJsonValue`(往返可序列化性检查:拒绝 BigInt、函数、循环引用、非有限数等),而非结构校验。一个损坏但仍为合法 JSON 的事件数据(字段类型错误、字段缺失)会静默往返,只有在后续消费方的 `switch` 中才可能被捕获。
|
||||
2. **插件新增变体没有运行时契约。** 一个通过声明合并添加新 `SessionEventMap` 键的插件,在自身代码中获得了编译期类型,但没有任何机制校验它产出的值是否符合它所声明的形状——无论是在生产者处、持久化边界处还是重新加载时。
|
||||
|
||||
由此引出问题:事件词汇是否应迁移到 **Zod** 或其他运行时 schema 库,使持久化和插件边界拥有运行时 schema 而非被擦除的类型。
|
||||
由此引出问题:事件词汇是否应迁移到 **Zod** 或其他运行时 schema 库,使持久化边界和插件边界拥有运行时 schema 而非被擦除的类型。
|
||||
|
||||
本 Agent Note(agent 决策记录)界定该问题的范围,不提出具体实现。
|
||||
本 Agent Note 界定该问题的范围,不提出具体实现。
|
||||
|
||||
## 为什么这不是一个持久化层的改动
|
||||
|
||||
很容易把「用 Zod 做序列化」理解为对 `dsh-session-persistence-jsonl/src/format.ts` 的局部修改。但它不是,原因在于一个结构性事实:**插件无法对 Zod schema 进行声明合并。** 声明合并是 TypeScript 编译期机制;Zod schema 是运行时值。要用 Zod 校验事件,就需要一个**运行时注册表**,每个产出事件的包(package)向其贡献自己的 schema(如 `ctx.sessionEvents.register('compaction/marker', z.object({…}))`),每个消费方从中读取。这个注册表——而非持久化后端——将成为词汇的真源,取代 merge-extensible 接口。
|
||||
很容易把「用 Zod 做序列化」理解为对 `dsh-session-persistence-jsonl/src/format.ts` 的局部修改。但它不是,原因在于一个结构性事实:**插件无法对 Zod schema 进行声明合并。** 声明合并是 TypeScript 编译期机制;Zod schema 是运行时值。要用 Zod 校验事件,就需要一个**运行时注册表**,每个产出事件的包向其贡献自己的 schema(如 `ctx.sessionEvents.register('compaction/marker', z.object({…}))`),每个消费方从中读取。这个注册表——而非持久化后端——将成为词汇的真源,取代 merge-extensible 接口。
|
||||
|
||||
因此,真正的提案是:**用运行时 schema 注册表替换编译期的 merge-extensible-map 模式,范围覆盖整个仓库。** 这是一次核心词汇的重新设计。
|
||||
|
||||
@@ -53,7 +53,7 @@ harness 将其核心词汇——内容块、消息来源、结束原因、轮次
|
||||
### C. 为整个词汇建立运行时 schema 注册表(Zod 或 schemastery)
|
||||
用运行时注册表替换 merge-extensible map,生产者向其贡献 schema,持久化/消费路径据此校验。
|
||||
|
||||
- **优点**:持久化边界和插件 seam 处获得真正的运行时校验;单一真源;可支撑通用工具(自动生成文档、模糊测试、协议格式检查)。
|
||||
- **优点**:持久化边界和插件 seam 处获得真正的运行时校验;单一真源;可支撑通用工具(自动生成文档、模糊测试、协议格式(wire format)检查)。
|
||||
- **缺点**:上述全部影响范围;**Zod 目前不是直接依赖**(仅作为 `@earendil-works/pi-ai` 的传递依赖),仓库选定的 schema 库是 **schemastery**——广泛引入 Zod 本身就是一个依赖决策;声明合并的易用性(一行插件扩展、完整推断)被运行时注册 + 手动类型接线取代;`assertNever` 穷举保证弱化(运行时变体在静态层面不可穷举)。
|
||||
|
||||
## 提案
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-15-sdk-project-editing-architecture.md
|
||||
2026-07-15-sdk-project-editing-architecture.md: 8335af516dbaa85f4adb85286f976ce9be2c9da8
|
||||
2026-07-15-sdk-project-editing-architecture.zh.md: bec39cc896887678b2d3f74832a9d13d7b354d6e
|
||||
2026-07-15-sdk-project-editing-architecture.zh.md: 51f0c5e7f4f033d8149a7ae01aae7a8c4755b76c
|
||||
|
||||
@@ -6,11 +6,11 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
[开发者拥有的 SDK 工程](../feature/2026-07-14-sdk-developer-projects.md) 由 create 创建,可以通过 config 调整,并由 start 等命令构建和运行。初始创建、配置调整和编译运行都需要理解功能、功能选项、NPM 依赖、Cordis 配置项、环境变量、包管理器、本地插件和多个项目文件。如果读写项目的各个流程分别使用不同的解析协议,SDK 开发者流程会变得难以维护。
|
||||
[开发者拥有的 SDK 工程](../feature/2026-07-14-sdk-developer-projects.md) 由 create 创建,可以通过 config 调整,并由 start 等命令构建和运行。初始创建、配置调整和编译运行都需要理解功能、功能选项、NPM 依赖、Cordis 配置项、环境变量、包管理器、本地插件和多个项目文件。如果各个项目读写工作流分别使用不同的解析协议,SDK 开发者工作流会变得难以维护。
|
||||
|
||||
## 提案
|
||||
|
||||
SDK 使用一个共享的面向对象工程模型。`SdkProject` 是只读快照,`ProjectEditSession` 是唯一修改与提交边界;功能对象负责自身的功能选项、关系、资源贡献和现状识别;create 与 config 只编排各自的用户流程,并通过同一组领域操作修改工程。
|
||||
SDK 使用一个共享的面向对象工程模型。`SdkProject` 是只读快照,`ProjectEditSession` 是唯一修改与提交边界;功能对象负责自身的功能选项、关系、资源贡献和现状识别;create 与 config 只编排各自的用户工作流,并通过同一组领域操作修改工程。
|
||||
|
||||
结构化文件通过文档对象修改,一次性文本产物通过完整模板生成。问题由类型化对象表达,并使用 clack 交互。差异计算可以作为编辑会话的内部实现,但不成为要求调用方组装的公共执行协议。
|
||||
|
||||
@@ -18,26 +18,26 @@ SDK 使用一个共享的面向对象工程模型。`SdkProject` 是只读快照
|
||||
|
||||
| 名词 | 本文用词 | 含义 |
|
||||
|---|---|---|
|
||||
| Feature | 功能 | SDK 人工策划和管理的产品单元;一项功能可以包含多个功能选项,并贡献多个 Cordis 配置项、NPM 依赖、环境变量占位和独占文件 |
|
||||
| Feature | 功能 | 由 SDK 策划和管理的产品单元;一项功能可以包含多个功能选项,并贡献多个 Cordis 配置项、NPM 依赖、环境变量占位和独占文件 |
|
||||
| Feature option | 功能选项 | 一项功能内有限、可选择的实现或配置形状;根据功能规则可以固定、互斥或多选 |
|
||||
| Cordis plugin | Cordis 插件 | Cordis 加载的插件实现,通常由一个 NPM 包导出;它不是 `cordis.yml` 中的一项配置 |
|
||||
| Cordis config entry | Cordis 配置项 | `cordis.yml` 插件列表中的一项,通过 `id` 标识实例并通过 `name` 指向 Cordis 插件 |
|
||||
| Cordis plugin config | Cordis 插件配置 | Cordis 插件公开的配置对象或配置结构;其中由功能拥有并更新的单个字段称为“配置键” |
|
||||
| Cordis plugin config | Cordis 插件配置 | Cordis 插件公开的配置对象或配置结构;其中由功能拥有并更新的单个字段称为「配置键」 |
|
||||
| config key | 配置键 | Cordis 插件配置中的单个字段;功能只更新自己声明拥有的配置键,并保留未知配置键 |
|
||||
| npm dependency | NPM 依赖 | `package.json` 中的包关系;`dependencies`、`devDependencies` 等字段保持原样 |
|
||||
| Feature requirement | 功能依赖 | 功能或功能选项通过 `requires` 声明的关系 |
|
||||
|
||||
## Package 边界
|
||||
## 包边界
|
||||
|
||||
| Package | 责任 | 不负责 |
|
||||
| 包 | 责任 | 不负责 |
|
||||
|---|---|---|
|
||||
| `@deepseek-ai/dsh-helper` | 编辑会话、功能配置、工程模板渲染、包管理适配和 prompt 交互适配 | 启动 Cordis 应用或决定 create/config 的终端流程 |
|
||||
| `@deepseek-ai/dsh-scripts` | `dsh-sdk start/dev/build/config`、进程生命周期、项目入口加载、config 流程和所属终端文案模板 | 直接解释功能定义或修改 YAML/JSON AST |
|
||||
| `@deepseek-ai/dsh-helper` | 编辑会话、功能配置、工程模板渲染、包管理器适配和 prompt 交互适配 | 启动 Cordis 应用或决定 create/config 的终端工作流 |
|
||||
| `@deepseek-ai/dsh-scripts` | `dsh-sdk start/dev/build/config`、进程生命周期、项目入口加载、config 工作流和所属终端文案模板 | 直接解释功能定义或修改 YAML/JSON AST |
|
||||
| `@deepseek-ai/create-sdk` | `npm create @deepseek-ai/sdk` 的参数、问题顺序、首次工程创建、安装收尾和所属终端文案模板 | 成为生成工程的运行时 NPM 依赖或提供库 API |
|
||||
|
||||
`@deepseek-ai/create-sdk` 是仓库 `@deepseek-ai/dsh-*` 命名规则的唯一例外;npm scoped initializer 约定要求 `npm create @deepseek-ai/sdk` 对应这个 package 名。该例外是仓库架构事实,不增加第三个开发者产品入口。
|
||||
`@deepseek-ai/create-sdk` 是仓库 `@deepseek-ai/dsh-*` 命名规则的唯一例外;npm scoped initializer 约定要求 `npm create @deepseek-ai/sdk` 对应这个包名。该例外是仓库架构事实,不增加第三个开发者产品入口。
|
||||
|
||||
三个 package 只导出相邻层实际使用的最小入口,不提供 `src/*` 深路径。scripts 的库入口与构建配置子路径服务生成代码和项目构建配置,但开发者产品合同仍由 `dsh-sdk` 命令承担。
|
||||
三个包只导出相邻层实际使用的最小入口,不提供 `src/*` 深路径。scripts 的库入口与构建配置子路径服务生成代码和项目构建配置,但开发者产品契约仍由 `dsh-sdk` 命令承担。
|
||||
|
||||
## 工程聚合与编辑会话
|
||||
|
||||
@@ -58,35 +58,35 @@ validate feature requirements and resource ownership
|
||||
|
||||
## 功能与资源所有权
|
||||
|
||||
功能是一等行为对象。浅层基类实现 install、configure、enable、disable、required/requires 校验和共同状态识别;固定功能选项、互斥功能选项与可多选功能选项共享这些生命周期。只有资源贡献依赖项目上下文或需要自定义 round-trip 的功能才使用专用行为类,其余功能通过标准化数据声明真正不同的部分。
|
||||
功能是一等行为对象。浅层基类实现 install、configure、enable、disable、required/requires 校验和通用状态检查;固定功能选项、互斥功能选项与可多选功能选项共享这些生命周期。只有资源贡献依赖项目上下文或需要自定义 round-trip 的功能才使用专用行为类,其余功能通过标准化数据声明真正不同的部分。
|
||||
|
||||
每项功能贡献带稳定 key 的 Cordis 配置项、NPM 依赖、环境变量占位和独占文件。注册表初始化时拒绝不同功能声明同一个资源 key;同一功能的不同功能选项可以共享资源,并由该功能根据最终选项集合处理。
|
||||
|
||||
Cordis 配置项是功能安装锚点。NPM 包名判断配置项所属的功能,配置项 ID 区分同一插件包的多个实例;只有 NPM 依赖而没有功能拥有的 Cordis 配置项时,该功能仍视为未安装。Cordis 配置项存在后,缺失 NPM 依赖、无法读取的 Cordis 插件配置或资源冲突会使功能进入不一致状态,config 命令显示诊断并拒绝猜测式修改。
|
||||
Cordis 配置项是功能安装锚点。NPM 包名用于确定配置项所属的功能,配置项 ID 区分同一插件包的多个实例;只有 NPM 依赖而没有功能拥有的 Cordis 配置项时,该功能仍视为未安装。Cordis 配置项存在后,缺失 NPM 依赖、无法读取的 Cordis 插件配置或资源冲突会使功能进入不一致状态,config 命令显示诊断并拒绝猜测式修改。
|
||||
|
||||
同一功能选项只更新其声明拥有的配置键,保留未知键。替换功能选项会删除旧功能选项独占且仍可确认的资源;无法确认旧资源或发现独占文件被用户修改时,整个操作失败。
|
||||
配置同一功能选项时,只更新其声明拥有的配置键,保留未知键。替换功能选项会删除旧功能选项独占且仍可确认的资源;无法确认旧资源或发现独占文件被用户修改时,整个操作失败。
|
||||
|
||||
## 问题与 workflow
|
||||
## 问题与工作流
|
||||
|
||||
问题由 TypeScript `Question<T>` 对象表达,默认值、校验、适用条件和类型留在同一个对象中。`PromptPort` 是领域层与终端库之间的唯一接口,helper 提供一份薄 `ClackPromptPort`;create 和 config 注入各自的命令行输入输出流,并在各自流程中决定取消、返回和收尾语义。
|
||||
问题由 TypeScript `Question<T>` 对象表达,默认值、校验、适用条件和类型留在同一个对象中。`PromptPort` 是领域层与终端库之间的唯一接口,helper 提供一个轻量的 `ClackPromptPort`;create 和 config 注入各自的命令行输入输出流,并在各自工作流中决定取消、返回和收尾语义。
|
||||
|
||||
create 的有状态问题顺序留在一个向导中,config 的最终状态选择留在一个流程中。两者通过同一个功能配置器收集功能选项与专用输入,因此增加一项普通功能、功能选项或参数不要求同时修改两个入口。
|
||||
create 的有状态问题顺序留在一个向导中,config 的最终状态选择留在一个工作流中。两者通过同一个功能配置器收集功能选项与专用输入,因此增加一项普通功能、功能选项或参数不要求同时修改两个入口。
|
||||
|
||||
## 项目文档与模板
|
||||
|
||||
只有需要读取或修改的结构化文件拥有具体文档对象,包括 `package.json`、`cordis.yml`、`.env`、`.env.example`、根 `tsconfig.json` 和 pnpm workspace 文件。文档对象拥有解析、克隆、校验和序列化行为;具体类与模块分别使用 `*File` 和 `*-file.ts` 命名,业务层不直接操作 YAML/JSON AST,异常形状在所属文档边界 fail loud。
|
||||
只有需要读取或修改的结构化文件拥有具体文档对象,包括 `package.json`、`cordis.yml`、`.env`、`.env.example`、根 `tsconfig.json` 和 pnpm workspace 文件。文档对象拥有解析、克隆、校验和序列化行为;具体类与模块分别使用 `*File` 和 `*-file.ts` 命名,业务层不直接操作 YAML/JSON AST,异常结构会在所属文档边界明确报错。
|
||||
|
||||
README、入口代码、构建配置、`.gitignore` 和其他一次性文本产物使用与真实文件一一对应的完整模板。CLI usage、创建结果与恢复提示、安装与重试指导以及默认 persona 等完整产品文案也由所属 package 的本地模板提供。
|
||||
README、入口代码、构建配置、`.gitignore` 和其他一次性文本产物使用与真实文件一一对应的完整模板。CLI(命令行界面)用法、创建结果与恢复提示、安装与重试指导以及默认 persona 等完整产品文案也由所属包的本地模板提供。
|
||||
|
||||
helper 提供通用的数据类型化 `TextTemplate` 模板渲染器,调用 package 通过本地 asset URL 加载自己的模板。
|
||||
helper 提供通用的数据类型化 `TextTemplate` 模板渲染器,调用方包通过本地 asset URL 加载自己的模板。
|
||||
|
||||
模板使用 Handlebars strict mode 与 `noEscape`,不进行自定义处理。文件对象负责把类型化数据值编码成目标语言文本;如果不希望插值,则源码以 `\{{model}}` 等转义形式输出下游。
|
||||
模板使用 Handlebars strict mode 与 `noEscape`,不进行自定义处理。文件对象负责把类型化数据值编码成目标语言文本;模板源码在必须原样输出下游字面量时,将插值转义为 `\{{model}}`。
|
||||
|
||||
## 命令与运行边界
|
||||
|
||||
scripts 支持 `dsh-sdk start/dev/build/config`。start 动态加载模块 target 并调用其命名入口;dev 在同一路径前增加 TypeScript 与本地 workspace 源码解析;build 调用工程安装的 tsdown;config 打开一个编辑会话并在 Review & Apply 后提交。typecheck 由生成工程直接执行 `tsc -b`。
|
||||
scripts 支持 `dsh-sdk start/dev/build/config`。start 动态加载模块 target 并调用其命名入口;dev 在同一路径前增加 TypeScript 与本地 workspace 源码解析;build 调用工程安装的 tsdown;config 打开一个编辑会话并在 Review & Apply 后提交。类型检查由生成工程直接执行 `tsc -b`。
|
||||
|
||||
HMR 作为显式 Cordis 配置项由 dev 和 start 加载;它所需的 `node-addon-require-builtin` 由 scripts package 传递提供,不写入开发者工程的 `package.json`。
|
||||
HMR(热模块替换)作为显式 Cordis 配置项由 dev 和 start 加载;它所需的 `node-addon-require-builtin` 由 scripts 包传递提供,不写入开发者工程的 `package.json`。
|
||||
|
||||
dev/start 会执行开发者入口,在开发者代码中处理命令行参数、cwd,由开发者自行传入 `--model=<name>` 与 `--resume=<session-id>` 启动标准流程。
|
||||
|
||||
@@ -94,12 +94,12 @@ dev/start 会执行开发者入口,在开发者代码中处理命令行参数
|
||||
|
||||
create-sdk 保留隐藏的 `--link-workspace` 选项供 Harness 仓库开发和 e2e 使用。该选项可以被解析,但不出现在 help、公开 flag 清单或普通用户文档中,也不接收仓库路径参数;仓库根从正在执行的 create-sdk 模块位置向上确定。
|
||||
|
||||
链接模式保持普通工程的文件形状。`@deepseek-ai/*` 指向 `packages/`,Cordis 相关 NPM 依赖指向 `vendor/`,共享底层 package 锚定到仓库实际使用的同一物理拷贝,避免 Cordis 类型合并产生多个模块类型定义。npm 使用 `file:`,pnpm 使用 `link:` 并关闭自动 peer 安装,Yarn 使用 `portal:` 与 resolutions;仓库 package 需要先构建。
|
||||
链接模式保持普通工程的文件形状。`@deepseek-ai/*` 指向 `packages/`,Cordis 相关 NPM 依赖指向 `vendor/`,共享底层包锚定到仓库实际使用的同一物理拷贝,避免 Cordis 类型合并产生多个模块类型定义。npm 使用 `file:`,pnpm 使用 `link:` 并关闭自动 peer 安装,Yarn 使用 `portal:` 与 resolutions;仓库包需要先构建。
|
||||
|
||||
## 后续工作
|
||||
|
||||
- **可替换的 required 主干角色。** 当前 `spine` 以一个固定功能选项拥有整组实现,包含 SystemPrompt、LLMService 等。无法让开发者对其进行替换和切换,只能手工修改 Cordis 配置项。
|
||||
- **Service contract 与 package 声明。** 替换特定内建服务时,Cordis 插件目前无法通过 `provides` 元数据声明其提供的服务,因此 SDK 无法在开发阶段辅助配置,也无法在运行时检查兼容性。后续需要设计相应协议。
|
||||
- **服务契约与包声明。** 替换特定内建服务时,Cordis 插件目前无法通过 `provides` 元数据声明其提供的服务,因此 SDK 无法在开发阶段辅助配置,也无法在运行时检查兼容性。后续需要设计相应协议。
|
||||
- **功能参数描述。** 当前功能的专用输入必须手工声明;SDK 无法从任意 Cordis 插件配置或 NPM package.json 信息中自动推导可交互参数。后续可以定义有限的声明式参数元数据,但不把任意 Cordis 插件配置转换成通用表单。
|
||||
- **SDK 应用级配置。** 当前项目资源模型只描述 Cordis 配置项及单个 Cordis 插件拥有的配置键,因此所有受 SDK 管理的配置都必须归属某个插件。跨插件或面向整个 SDK 应用的设置没有独立持久化位置;后续需要定义应用级配置文档及其所有权、读取和修改边界。
|
||||
|
||||
@@ -107,23 +107,23 @@ create-sdk 保留隐藏的 `--link-workspace` 选项供 Harness 仓库开发和
|
||||
|
||||
**保留静态 Catalog 与中心 engine。** 该方案改动最小,但功能参数、round-trip、独占文件和 create/config 复用都会继续进入同一个协调中心;拆文件只能缩短单文件,不能收拢职责。
|
||||
|
||||
**使用 `wizard.json` 与通用 Questionnaire。** 静态表单无法直接表达功能依赖、选项切换、已有值回填和项目资源变化;类型、gate 和动态 option 最终仍要通过字符串 registry 与过程式 `run()` 连接,形成新的内部 DSL。
|
||||
**使用 `wizard.json` 与通用 Questionnaire。** 静态表单无法直接表达功能依赖、选项切换、已有值回填和项目资源变化;类型、门禁和动态选项最终仍要通过字符串注册表与过程式 `run()` 连接,形成新的内部 DSL。
|
||||
|
||||
**公开本地链接 flag。** 该模式依赖 Harness monorepo 布局和未发布 package,只服务仓库开发;公开后会形成无法对外兑现的项目创建合同,因此保持隐藏。
|
||||
**公开本地链接 flag。** 该模式依赖 Harness monorepo 布局和未发布包,只服务仓库开发;公开后会形成无法对外兑现的项目创建契约,因此保持隐藏。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- create 与 config 只通过 `SdkProject` 和 `ProjectEditSession` 修改工程,写入前的任何业务、文件或并发校验失败都不产生磁盘变化
|
||||
- 新增普通功能、功能选项或参数只扩展类型化 spec 或所属行为对象,create/config 流程不增加中央 switch
|
||||
- create 与 config 只通过 `SdkProject` 和 `ProjectEditSession` 修改工程,写入前的任何业务、文档或并发校验失败都不产生磁盘变化
|
||||
- 新增普通功能、功能选项或参数只扩展类型化 spec 或所属行为对象,create/config 工作流不增加中央 switch
|
||||
- 功能模型、NPM 依赖与其他资源配置、不一致检测由 helper 统一实现
|
||||
- 结构化文件通过 `*File` 文档对象修改;一次性文件和完整产品文案通过所属 package 的 Handlebars 模板生成,业务决策不进入模板 DSL
|
||||
- `dsh-sdk start/dev/build/config` 是运行产品面,typecheck 直接使用 `tsc -b`,HMR 不通过命令隐式注入,`node-addon-require-builtin` 只由 scripts package 传递提供
|
||||
- 结构化文件通过 `*File` 文档对象修改;一次性文件和完整产品文案通过所属包的 Handlebars 模板生成,业务决策不进入模板 DSL
|
||||
- `dsh-sdk start/dev/build/config` 是运行时产品接口,类型检查直接使用 `tsc -b`,HMR 不通过命令隐式注入,`node-addon-require-builtin` 只由 scripts 包传递提供
|
||||
- `--link-workspace` 只作为隐藏的仓库开发选项存在,并对 npm、pnpm 和 Yarn 保持单一模块身份
|
||||
|
||||
## 风险
|
||||
|
||||
- 行为对象与类型化 spec 并存会形成两种扩展形状;专用类必须只用于确实依赖项目上下文或自定义的功能,否则会重新产生无意义的类型层次
|
||||
- 行为对象与类型化 spec 并存会形成两种扩展形状;专用类必须只用于确实依赖项目上下文或需要自定义行为的功能,否则会重新产生无意义的类型层次
|
||||
- 乐观并发检查与写前校验不能解决写入中途的 I/O 故障,调用方仍需向开发者报告可能的部分提交
|
||||
- 隐藏链接模式依赖仓库目录与 package manager 链接语义,仓库布局或工具行为变化时必须与实现一起更新
|
||||
- Cordis loader 从自身模块路径加载 `node-addon-require-builtin`;npm、pnpm 或 Yarn 的 NPM 依赖布局变化时,scripts package 必须继续满足该可选对等依赖(optional peer dependency)
|
||||
- 隐藏链接模式依赖仓库目录与包管理器链接语义,仓库布局或工具行为变化时必须与实现一起更新
|
||||
- Cordis loader 从自身模块路径加载 `node-addon-require-builtin`;在 npm、pnpm 和 Yarn 的 NPM 依赖布局下,scripts 包必须持续满足该可选对等依赖(peer dependency)
|
||||
- Handlebars 的 `noEscape` 把目标语言编码责任交给 typed model 构造方;新增模板字段时必须在 owner 处完成正确转义,下游 Handlebars 占位符必须在模板源码中显式转义
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-19-required-cancellation-through-tool-capability-seams.md
|
||||
2026-07-19-required-cancellation-through-tool-capability-seams.md: c2cfb09f27222136965058695e9b6b706ac688a9
|
||||
2026-07-19-required-cancellation-through-tool-capability-seams.zh.md: f7a1d303212dfab6da27feba2d6e7195ea07bd50
|
||||
2026-07-19-required-cancellation-through-tool-capability-seams.zh.md: 717d6e204555ad49753e41f6f7e5dd03e5a677ef
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 工具可达能力接缝中的必填取消
|
||||
# Agent Note: 工具可达能力 seam 中的必填取消
|
||||
|
||||
Status: proposed
|
||||
|
||||
@@ -8,17 +8,17 @@ Status: proposed
|
||||
|
||||
已经实现的[工具注册表取消契约](../../implemented/architecture/2026-07-19-cooperative-tool-cancellation.md)让每个工具主体中的 `exec.signal` 成为必填值,但许多由工具主体调用的异步能力接口仍接受可选信号。因此,工具可以满足自身类型,却在下一次同进程调用时意外丢失取消。
|
||||
|
||||
这项缺口会沿调用链传递。文件系统工具可能调用路径解析和 I/O,Web 工具可能调用提供方,Bash 工具可能调用执行器,组合工具可能启动或等待任务、subagent 或工作流。只要某个控制工具所持有工作的等待操作允许省略信号,TypeScript 就无法证明取消仍能到达拥有副作用的边界。
|
||||
这项缺口会沿调用链传递。文件系统工具可能调用路径解析和 I/O,Web 工具可能调用提供方,Bash 工具可能调用执行器,组合工具可能启动或等待任务、subagent 或工作流。只要某个控制工具所持有工作的、且会被工具等待的操作允许省略信号,TypeScript 就无法证明取消仍能到达拥有副作用的边界。
|
||||
|
||||
要求仓库中所有异步函数都携带信号会过度扩张。有些操作无法从工具到达,有些同步查询不会等待或持有持续工作,而明确分离的工作在刻意交接后已经拥有新的所有者。
|
||||
要求仓库中所有异步函数都携带信号会过度扩张。有些操作无法从工具到达,有些同步查询无法等待或持有持续工作,而明确分离的工作在刻意交接后已经拥有新的所有者。
|
||||
|
||||
## 提议
|
||||
|
||||
所有能从工具主体到达、且在工具仍持有或等待该操作期间执行的异步同进程能力操作,都必须接收 `AbortSignal`。根据所属接缝的既有形态,这项要求可以表现为位置参数,也可以表现为必填的只读请求字段,但省略信号必须导致 TypeScript 编译失败。
|
||||
所有能从工具主体到达、且在工具仍持有或等待该操作期间执行的异步同进程能力操作,都必须接收 `AbortSignal`。根据所属服务边界的既有形态,这项要求可以表现为位置参数,也可以表现为必填的只读请求字段,但省略信号必须导致 TypeScript 编译失败。
|
||||
|
||||
每个直接调用方提供自己持有的信号,或从自身必填的操作上下文继续传递信号。实现可以派生子截止时间或取消作用域,但派生信号在委托期间仍须与上游信号关联。能力实现不得生成永不中止信号、使用环境式异步本地取消,也不得仅为重复类型化同进程契约而在运行时校验 `AbortSignal`。
|
||||
|
||||
迁移首先从每个第一方 `ToolDefinition.execute()` 出发,清点其等待的能力调用;随后把每个内聚的接口、实现和使用方接缝连同测试与生成的 API 文档一起修改。文件系统、Bash 与任务、Web 与提供方、工作流与 subagent、代码运行时等能力族可以通过独立 PR 迁移,以保持每项变更可审查;但根据仓库的预发布原则,已经迁移的接口不得保留可选兼容重载。
|
||||
迁移首先从每个第一方 `ToolDefinition.execute()` 出发,清点其等待的能力调用;随后按内聚的服务边界,将接口、实现和消费方连同测试与生成的 API 文档一起修改。文件系统、Bash 与任务、Web 与提供方、工作流与 subagent、代码运行时等能力族可以通过独立 PR(Pull Request)迁移,以保持每项变更可审查;但根据仓库的预发布原则,已经迁移的接口不得保留可选兼容重载。
|
||||
|
||||
### 范围边界
|
||||
|
||||
@@ -26,7 +26,7 @@ Status: proposed
|
||||
|
||||
本提议不包含同步注册表查询、可用性检查、schema 渲染、参数分类,以及其他无法保留异步工作的操作。明确交接所有权后的分离工作也不在范围内:任务、工作流、worker 或 subagent 成功发布给新的生命周期所有者后,其分离生命周期由新所有者的控制器管理。发起启动的操作在交接提交前仍须接收调用方信号;之后若另一次工具调用等待该分离工作,则必须使用该次调用自己的信号。
|
||||
|
||||
若外部协议本身允许省略取消,解析器、配置、模型与工具 JSON、持久化与文件格式、worker、进程或线协议输入仍可保留可选取消。所属边界必须先把该输入解析为必填的同进程信号,再调用已经迁移的能力接缝。
|
||||
若外部协议本身允许省略取消,解析器、配置、模型与工具 JSON、持久化与文件格式、worker、进程或协议输入仍可保留可选取消。所属边界必须先把该输入解析为必填的同进程信号,再调用已经迁移的能力 seam。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -34,19 +34,19 @@ Status: proposed
|
||||
|
||||
**通过 lint 规则或回调检查强制传递。** 不予采纳,因为语法检查无法可靠识别所有权、派生信号、抽象层或正确的完全停稳行为。必填接口参数可以在 TypeScript 能检查每个调用方的位置表达契约。
|
||||
|
||||
**把 `ToolRunContext` 传入所有能力。** 不予采纳,因为能力需要的是取消,而不是工具身份、agent 状态或上下文延后功能。传递更大的上下文会让可复用服务耦合到工具注册表,也会掩盖狭窄接缝。
|
||||
**把 `ToolRunContext` 传入所有能力。** 不予采纳,因为能力需要的是取消,而不是工具身份、agent(智能体)状态或上下文延后功能。传递更大的上下文会让可复用服务耦合到工具注册表,也会掩盖狭窄服务边界。
|
||||
|
||||
**使用环境式异步本地信号。** 不予采纳,因为隐藏传递会让所有权和分离交接难以审计,使测试复杂化,并可能让调用静默绑定到错误的生命周期。
|
||||
|
||||
**在能力实现中加入默认或永不中止信号。** 不予采纳,因为默认值会抹去缺失的所有者,而不是在编译期暴露问题。
|
||||
|
||||
**在已经实现的工具注册表变更中迁移所有能力。** 不予采纳,因为传递性的接口修改横跨独立能力族。单独保留这项提议既能维持已实现的注册表决策,也能让每个深层接缝通过聚焦测试完成迁移。
|
||||
**在已经实现的工具注册表变更中迁移所有能力。** 不予采纳,因为传递性的接口修改横跨独立能力族。单独保留这项提议既能维持已实现的注册表决策,也能让每个深层服务边界通过聚焦测试完成迁移。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 清单把每个第一方工具主体映射到所有权交接前可以到达的异步能力操作。
|
||||
- 每个范围内的能力接口都要求 `AbortSignal`,并由编译期契约测试证明省略信号会失败。
|
||||
- 接口、实现、直接使用方、测试辅助函数、示例和生成的 API 引用必须一起迁移,不保留兼容重载或生产环境永不中止哨兵。
|
||||
- 接口、实现、直接消费方、测试辅助函数、示例和生成的 API 引用必须一起迁移,不保留兼容重载或生产环境永不中止哨兵。
|
||||
- 派生截止时间和包装层作用域仍与调用方信号关联,集成测试证明取消到达副作用所有者,且等待的工作完全停稳。
|
||||
- 同步查询和明确交接后的分离工作不受这项要求约束;存在歧义时,需要记录并测试所有权转换。
|
||||
- 只有真实的无类型边界才添加运行时校验,不得重复校验 TypeScript 已要求的字段或参数。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md
|
||||
2026-07-24-domain-kv-storage-and-workspace.md: 230877628428dc88dbddeecfe5f4353cf15e151d
|
||||
2026-07-24-domain-kv-storage-and-workspace.zh.md: 050f72cd3327f83e2c3f3cefcab63c01e8f112ee
|
||||
2026-07-24-domain-kv-storage-and-workspace.zh.md: 537f526c84864555e6d0250deb3170d6facbd3d3
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
# Agent Note: Domain KV storage capability seam and the workspace entity
|
||||
# Agent Note: 领域 KV 存储能力 seam 与 workspace 实体
|
||||
|
||||
Status: proposed
|
||||
状态:提议中
|
||||
|
||||
[English](2026-07-24-domain-kv-storage-and-workspace.md) | 中文
|
||||
|
||||
## Problem
|
||||
## 问题
|
||||
|
||||
host 侧唯一的持久化面是 session 事件日志(`packages/session-persistence`:append-only、一 session 一文件)。凡是"不属于某个 session"的信息就没有落盘处,眼下有两个真实需求:
|
||||
host 侧唯一的持久化面是 session 事件日志(`packages/session-persistence`:仅追加、一 session 一文件)。凡是"不属于某个 session"的信息就没有落盘处,眼下有两个真实需求:
|
||||
|
||||
- **workspace 实体**。GUI 要把 workspace 做成真实对象:路径、标题、关联 session 清单。归属关系由 workspace 持有——"哪些 session 属于这个 workspace"不是任何单个 session 自己的事实,塞进 session log 语义不成立。此前 workspace 只是 sidebar 上按 cwd 分组的视觉概念,没有实体(该结论已被推翻)。
|
||||
- **session 动态元信息**(可预见的第二个消费者)。冷会话列表只读日志首行 header(创建时的不可变快照),title、结束状态这类随会话推进变化的信息拿不到;补齐方向是 sidecar 元数据表——正是一张按 key 高频点更新的 KV 表。
|
||||
- **session 动态元信息**(可预见的第二个消费方)。冷会话列表只读日志首行 header(创建时的不可变快照),title、结束状态这类随会话推进变化的信息拿不到;补齐方向是 sidecar 元数据表——正是一张按 key 高频点更新的 KV 表。
|
||||
|
||||
另外,Session 删除需要 `SessionPersistence` 删除原语和 `session.delete` 端点。该空白的设计随本 Note 定案,但实现仍属未来工作。
|
||||
|
||||
后续的 [Workspace 注册记录删除决策](../../implemented/feature/2026-07-27-workspace-registration-deletion.md)取代的仅是上述耦合关系:删除 Workspace 注册记录会保留相关 Session 及其日志,Session 删除仍是独立的未来工作。因此,下文的级联设计并不是 Workspace GUI 的删除语义。
|
||||
|
||||
## Proposal
|
||||
## 方案
|
||||
|
||||
新建 `packages/storage/` 组——`ctx.storage` 存储枢纽(后端注册面 + 数据形式挂载面)、两个后端、domain 领域数据形式——及 workspace 消费者包;给 `SessionPersistence` 扩删除原语。
|
||||
新建 `packages/storage/` 组——`ctx.storage` 存储枢纽(后端注册面 + 数据形式挂载面)、两个后端、domain 领域数据形式——及 workspace 消费方包;给 `SessionPersistence` 扩删除原语。
|
||||
|
||||
| 包 | 路径 | ctx 面 | 本期 |
|
||||
| --- | --- | --- | --- |
|
||||
| `@deepseek-ai/dsh-storage` | `packages/storage/storage/` | `ctx.storage`(枢纽) | ✓ |
|
||||
| `@deepseek-ai/dsh-storage-json` | `packages/storage/storage-json/` | 注册 backend `json` | ✓ |
|
||||
| `@deepseek-ai/dsh-storage-sqlite` | `packages/storage/storage-sqlite/` | 注册 backend `sqlite` | ✓ |
|
||||
| `@deepseek-ai/dsh-storage-json` | `packages/storage/storage-json/` | 注册后端 `json` | ✓ |
|
||||
| `@deepseek-ai/dsh-storage-sqlite` | `packages/storage/storage-sqlite/` | 注册后端 `sqlite` | ✓ |
|
||||
| `@deepseek-ai/dsh-storage-domain` | `packages/storage/storage-domain/` | 挂载 `ctx.storage.domain` | ✓ |
|
||||
| `@deepseek-ai/dsh-workspace` | `packages/workspace/workspace/` | `ctx.workspace` | ✓ |
|
||||
| `SessionPersistence.delete` 扩面 + 级联删编排 | `packages/session-persistence/*` | 既有 seam 新方法 | ✗ future work(本期不动 session 侧) |
|
||||
@@ -35,13 +35,13 @@ host 侧唯一的持久化面是 session 事件日志(`packages/session-persis
|
||||
|
||||
### `dsh-storage`:存储枢纽
|
||||
|
||||
纯注册枢纽,自身不做 IO,无 Config。`Storage` service 挂 `ctx.storage`,两个面:`backend`(`BackendRegistry`:`register(name, backend)` 返回 disposer、重名 throw;`get(name)` 未知名 throw `backend-not-found`)与数据形式挂载(`mount(form, facility)` 配 merge-extensible 的 `StorageForms` map,`dsh-domain` merge 进 `domain` 键;未挂载访问 throw `form-not-mounted`)。签名正文见 `packages/storage/storage/src/index.ts` 与 `src/registry.ts`。
|
||||
纯注册枢纽,自身不做 IO,无 Config。`Storage` 服务挂 `ctx.storage`,两个面:`backend`(`BackendRegistry`:`register(name, backend)` 返回 disposer、重名 throw;`get(name)` 未知名 throw `backend-not-found`)与数据形式挂载(`mount(form, facility)` 配 merge-extensible 的 `StorageForms` map,`dsh-domain` merge 进 `domain` 键;未挂载访问 throw `form-not-mounted`)。签名正文见 `packages/storage/storage/src/index.ts` 与 `src/registry.ts`。
|
||||
|
||||
**多后端同时挂载**;域→后端的选择是 `dsh-domain` 的配置(见下),不是全局二选一。disposer 语义 = 从表中摘名;后端自身的 close 由后端包的 effect 闭包负责,顺序先摘名后 close。
|
||||
|
||||
一个后端是一个**介质 owner**(一棵文件树 root / 一个 db 文件),通过**数据形状 facet** 暴露原语——本期只有 `kv`;session 迁移期加 `log`(见迁移节)。facet 是可选成员,缺席即该后端不支持该形状,解析时 fail loud。`kv` facet 的原语面:`open(descriptor)`(descriptor = 名字/版本/表名清单/有无 global,名字与表名限 `^[a-z][a-z0-9_]*$` 兼作文件名与 SQL 表名段)返回 unit,unit 提供 `loadAll` / `putRecord` / `deleteRecord`(缺 key 为 no-op)/ `setGlobal` / `close`(幂等);值对后端是不透明 JSON。规范正文(含逐方法 JSDoc)在 `packages/storage/storage/src/backend.ts`。
|
||||
|
||||
backend 契约(共享契约测试逐条断言,两后端同套件):
|
||||
后端契约(共享契约测试逐条断言,两后端同套件):
|
||||
|
||||
1. `open` 对不存在的介质创建(懒物化允许:可延迟到首写,但 `loadAll` 立即可用返回空表);对已存在介质载入。
|
||||
2. 介质上版本 ≠ descriptor.version → `StorageError('version-mismatch')`,不迁移不重建。
|
||||
@@ -93,7 +93,7 @@ CREATE TABLE IF NOT EXISTS "u_<unit>_<table>" (
|
||||
|
||||
### `dsh-domain`:领域数据形式
|
||||
|
||||
单实现不抽象;消费者只依赖这层,不直接触后端。
|
||||
单实现不抽象;消费方只依赖这层,不直接触后端。
|
||||
|
||||
```ts ignore-check
|
||||
export const Config = z.object({
|
||||
@@ -108,7 +108,7 @@ export function apply(ctx: Context, config: Config) {
|
||||
|
||||
(facility 卸载顺序:先 dispose 各域(排空写链)再从枢纽摘名——排空期间在途写仍发 `domain/changed`,事件一致性 invariant 经 facility 反查域,要求此时域名仍可解析。)
|
||||
|
||||
域声明(spec 对象由拥有该域的包定义导出,是类型与运行时的单一来源;schema 用 zod,`z.infer` 推导类型不重复声明——记录模型下期要投影成 RPC wire schema,wire 边界全是 zod;schemastery 仍只管插件 Config):
|
||||
域声明(spec 对象由拥有该域的包定义导出,是类型与运行时的唯一真源;schema 用 zod,`z.infer` 推导类型不重复声明——记录模型下期要投影成 RPC wire schema,wire 边界全是 zod;schemastery 仍只管插件 Config):
|
||||
|
||||
```ts ignore-check
|
||||
export interface DomainGlobalSpec<G> { readonly schema: ZodType<G>; readonly initial: G }
|
||||
@@ -156,7 +156,7 @@ export interface KvTable<K extends string, V> {
|
||||
规则:
|
||||
|
||||
- **一级 mapping**:key → 记录,不做嵌套表;层级需求用复合 key 或值内字段。两后端因此同构(JSON object 一层 ↔ SQLite 一行)。
|
||||
- **记录是纯数据**:可直接 JSON 序列化的不可变 POJO;`get`/`entries` 返回值不得原地改(TypeScript readonly 投影,不做运行时冻结)。带行为的领域对象属于消费者包。
|
||||
- **记录是纯数据**:可直接 JSON 序列化的不可变 POJO;`get`/`entries` 返回值不得原地改(TypeScript readonly 投影,不做运行时冻结)。带行为的领域对象属于消费方包。
|
||||
- **写串行**:域内一条 promise 链,`put`/`delete`/`update`/`global.set` 全排队;`update` 的 fn 在链上执行,并发不交错。不做 active-record(取出可变对象自动落盘——落盘时机不可控,与整域原子覆写冲突)。
|
||||
- **版本 fail loud**:盘上版本与 spec 不符直接报错,不迁移不重建(数据不可再生,pre-release 拒绝旧格式)。
|
||||
- **变更事件**:每次写落盘 resolve 后 emit `domain/changed`(`@mode emit`),逐条发、不带旧值(对齐仓库"新快照 + 操作判别"惯例,范本 `goal/changed`);payload `DomainChanged` 是 put/deleted 判别联合——域名 + 表名 + key(global 变更两者为 `''`)+ operation,put 支带新快照 value、deleted 支无 value(`packages/storage/storage-domain/src/events.ts`)。此为下期 RPC 推帧的事件源。错误词汇 `DomainError`,码表:`already-open` / `facet-unsupported` / `invalid-record`(带 `{ table, key }`)/ `missing-key` / `closed`。
|
||||
@@ -242,7 +242,7 @@ export class WorkspaceRegistry extends Service {
|
||||
|
||||
- **path 规范**:落盘值 = `fs.realpath(输入)`(尾斜杠、`..`、符号链接全解析);唯一性 = 规范化后字符串相等(符号链接指向同一目录算撞)。目录不存在时 create 直接 reject(realpath 失败——workspace 必须指向存在目录;"Create new = 建目录"是上层交互,先 mkdir 再 create)。attach 校验的 session cwd 同口径。cwd 单值 + path 唯一 ⇒ 一个 session 结构上最多归属一个 workspace,双重记账写侧不可能。
|
||||
- **title**:显示名,默认 `basename(path)`,可改,允许重复。归属不用 cwd 派生兜底——cwd 表达不了排序,归属是 workspace 侧事实;headless 直开的 session 不属于任何 workspace。
|
||||
- 消费者只见 `Workspace` 接口,`WorkspaceEntity` 不出包(单实现不预拆 seam);实体按 id 唯一(registry 缓存),记录快照写后原地换新,外部只见 getter;所有写收敛到实体内 `mutate(fn)` → `table.update`,`updatedAt` 在 mutate 内统一刷。领域对象不过 RPC,下期 wire 层把记录投影成 zod wire schema。
|
||||
- 消费方只见 `Workspace` 接口,`WorkspaceEntity` 不出包(单实现不预拆 seam);实体按 id 唯一(注册表缓存),记录快照写后原地换新,外部只见 getter;所有写收敛到实体内 `mutate(fn)` → `table.update`,`updatedAt` 在 mutate 内统一刷。领域对象不过 RPC,下期 wire 层把记录投影成 zod wire schema。
|
||||
- **Session 删除仍属未来工作。** 后续的 [Workspace 注册记录删除决策](../../implemented/feature/2026-07-27-workspace-registration-deletion.md)已将 `ctx.workspace.delete(id)` 作为仅删除元数据、保留 Session 与日志的操作交付。递归删除 Session、运行中检查和崩溃重跑收敛属于独立的 `session.delete` 能力。
|
||||
|
||||
一致性口径(账 = 归属唯一依据;实现与测试基准):
|
||||
@@ -256,7 +256,7 @@ export class WorkspaceRegistry extends Service {
|
||||
|
||||
### 复用与 session 后端迁移展望
|
||||
|
||||
**长期方向**:session-persistence 的 JSONL/SQLite 后端里"纯介质操作"下沉到 `dsh-storage` 后端(session 包不删,`SessionPersistence` seam 与 coordinator 语义不动;动的只是它们脚下的文件/db 操作层)。复用的动机:介质层全是文件系统操作、数据库调用与跨平台兼容的脏活(Windows 权限与原子发布变体、fsync 语义、独占建文件……),这些只应写一遍;业务语义(session 怎么 append、何时 append、append 什么)留在上层——而"底下这次 append 是否正常完成"(持久性/原子性/平台正确性)是底层的责任,责任界面就是 facet 原语的契约。为此后端接口按**介质 owner + 数据形状 facet** 设计:session 日志是 append-only 流,与 KV 形状不同——强行统一进 KV 原语会两头变形,所以按 facet 分开(`kv` 本期、`log` 迁移期),介质与生命周期共享。
|
||||
**长期方向**:session-persistence 的 JSONL/SQLite 后端里"纯介质操作"下沉到 `dsh-storage` 后端(session 包不删,`SessionPersistence` seam 与 coordinator 语义不动;动的只是它们脚下的文件/db 操作层)。复用的动机:介质层全是文件系统操作、数据库调用与跨平台兼容的脏活(Windows 权限与原子发布变体、fsync 语义、独占建文件……),这些只应写一遍;业务语义(session 怎么 append、何时 append、append 什么)留在上层——而"底下这次 append 是否正常完成"(持久性/原子性/平台正确性)是底层的责任,责任界面就是 facet 原语的契约。为此后端接口按**介质 owner + 数据形状 facet** 设计:session 日志是仅追加流,与 KV 形状不同——强行统一进 KV 原语会两头变形,所以按 facet 分开(`kv` 本期、`log` 迁移期),介质与生命周期共享。
|
||||
|
||||
现状复用审计(迁移前就能看清的账):
|
||||
|
||||
@@ -266,7 +266,7 @@ export class WorkspaceRegistry extends Service {
|
||||
| JSONL:逐行 append、首行 header 快读、zstd 逐帧压缩 | log 形状 | 留在原地;迁移期进 `log` facet |
|
||||
| SQLite:openDatabase(mkdir/独占建文件/PRAGMA 序列/user_version 检查) | 纯介质 | 本期 `dsh-storage-sqlite` 抄用——两处 openDatabase 已几乎逐行同构,本组是第三个使用者;先抄后提,提取放迁移期 |
|
||||
| SQLite:events/sessions 表结构、同事务物化 | log 形状 | 留在原地;迁移期进 `log` facet |
|
||||
| coordinator(per-id 写链、懒物化、崩溃修复、flush 屏障) | session 语义 | 永不下沉——事件日志的领域逻辑,对应物在 domain 层(写串行链),各归各 |
|
||||
| coordinator(per-id 写链、懒物化、崩溃修复、flush 屏障) | session 语义 | 永不下沉——事件日志的领域逻辑,在 domain 层对应的是写串行链,各归各 |
|
||||
| encodeSegment(id 进路径转义) | 介质工具 | domain 侧 key 不进路径用不到;`log` facet(一 session 一文件)迁移时随之下沉 |
|
||||
|
||||
**本期不改 session-persistence 的介质代码**(只加 delete 原语);上表是迁移期的施工清单,也是后端接口"必须装得下 log 形状"的设计依据。
|
||||
@@ -275,8 +275,8 @@ export class WorkspaceRegistry extends Service {
|
||||
|
||||
| 套件 | 覆盖 | 后端 |
|
||||
| --- | --- | --- |
|
||||
| backend 契约(共享套件,一次编写两端跑) | 七条契约 + 版本拒绝 + close 幂等 | json、sqlite(`:memory:` + 临时目录) |
|
||||
| registry/mount | 重复注册、未挂载访问、disposer 摘除 | — |
|
||||
| 后端契约(共享套件,一次编写两端跑) | 七条契约 + 版本拒绝 + close 幂等 | json、sqlite(`:memory:` + 临时目录) |
|
||||
| 注册表/mount | 重复注册、未挂载访问、disposer 摘除 | — |
|
||||
| domain 层 | open 六步语义、schema 拒绝、update 串行(并发交错压测)、`domain/changed` 逐条、global 初值懒物化、路由与 `facet-unsupported` | 任一(json) |
|
||||
| workspace | create/唯一性/realpath、attach 校验(含 sessionPersistence 缺席拒绝)、一致性口径四情形 | mock domain 或 json |
|
||||
| session delete 契约(future work,随实施并入 runPersistenceContract) | 未知 id、已删 id 复用、未物化 intent、与在途 append 串行、deleted 事件 | jsonl、sqlite |
|
||||
@@ -292,40 +292,40 @@ export class WorkspaceRegistry extends Service {
|
||||
| 多进程并发写保护 | 两 host 进程同写一介质 | JSON 后端文件锁;SQLite WAL 天然多进程 | 写全经 domain 单点串行,加锁只动后端 |
|
||||
| 跨进程变更观测 | GUI 断线重连感知 | revision 模式(抄 session-persistence) | 进程内已有 `domain/changed` |
|
||||
| 数据迁移 | 首个 tagged release 后模型再变 | 版本号驱动逐域迁移 | 版本号自第一天入介质 |
|
||||
| 大表性能 | 千级记录域挂 json | `routes` 改指 sqlite,数据手工导一次 | 路由即配置,消费者零改动 |
|
||||
| 多段 key | 两段 key 消费者出现(每 workspace 每 session 维度数据) | key 泛型换 tuple、SQLite 复合主键、JSON 嵌套层 | 一级表 = 段数 1 特例;不做任意深度嵌套;不拼字符串 key |
|
||||
| 大表性能 | 千级记录域挂 json | `routes` 改指 sqlite,数据手工导一次 | 路由即配置,消费方零改动 |
|
||||
| 多段 key | 两段 key 消费方出现(每 workspace 每 session 维度数据) | key 泛型换 tuple、SQLite 复合主键、JSON 嵌套层 | 一级表 = 段数 1 特例;不做任意深度嵌套;不拼字符串 key |
|
||||
| scope 维度 | "每 workspace 一份"的域出现且复合 key 表达不动 | DomainSpec 加 scope + 文件名 scope 段(encodeSegment) | 名字字符集已收紧,文件名不冲突 |
|
||||
| 跨表原子事务 | 同域两表一次原子操作需求 | `domain.transact(fn)`;JSON 天然原子,SQLite 包事务 | — |
|
||||
| 二级索引/条件查询 | 内存过滤不动(万级记录) | SQLite JSON1 查 value 列,加只读 query 面 | JSON 后端不陪跑 |
|
||||
| session 跨 workspace 移动 | 产品需求出现 | attach 校验放宽为"先 detach 后 attach"编排 | — |
|
||||
| Session 删除 RPC/GUI | 破坏性的 Session 删除产品流启动 | `session.delete` 端点、wire schema 与明确的确认 UI | Workspace RPC/GUI 已独立交付,不再存在级联耦合 |
|
||||
|
||||
## Alternatives considered
|
||||
## 备选方案
|
||||
|
||||
- **复用 session-persistence 的 coordinator/后端**:事件日志语义(append-only、turn 崩溃修复、懒物化)与 KV 覆写语义不匹配;只借其分层思想(协调层持写序、后端只实现最小原语)。
|
||||
- **workspace 专用存储包,后续再抽 seam**:第二个消费者(session sidecar)已可预见,届时泛化要再动一次接口。
|
||||
- **复用 session-persistence 的 coordinator/后端**:事件日志语义(仅追加、turn 崩溃修复、懒物化)与 KV 覆写语义不匹配;只借其分层思想(协调层持写序、后端只实现最小原语)。
|
||||
- **workspace 专用存储包,后续再抽 seam**:第二个消费方(session sidecar)已可预见,届时泛化要再动一次接口。
|
||||
- **domain 与 storage 合为一层**:后端会被迫接触 schema 校验、变更事件、写串行等领域关切;拆开后 storage 后端只做不透明原语(可替换面最小),domain 单实现收敛全部领域逻辑(zod/事件/串行化只写一遍,不随后端翻倍)。
|
||||
- **整库单后端二选一(学 session-persistence 单坑位模式)**:曾是初版方案;改为多后端并存 + 配置路由,因为存储枢纽要承载多种数据形式,不同形式/域对后端的偏好(肉眼可读 vs 高频点更新)注定分化,单坑位会逼出"整体换挂 + 手工导数据"的粗粒度动作。代价是按名查找多一步,fail-loud 兜底。
|
||||
- **JSON 后端 jsonl 追加 + 墓碑 + 压实**:temp+fsync+rename 的崩溃安全与 append 等价;覆写让文件永远是净值、肉眼可读,免掉折叠/压实/断行容错。域规模下整写与追加一行同量级。
|
||||
- **整库单后端二选一(学 session-persistence 单 slot 模式)**:曾是初版方案;改为多后端并存 + 配置路由,因为存储枢纽要承载多种数据形式,不同形式/域对后端的偏好(肉眼可读 vs 高频点更新)注定分化,单 slot 会逼出"整体换挂 + 手工导数据"的粗粒度动作。代价是按名查找多一步,fail-loud 兜底。
|
||||
- **JSON 后端 jsonl 追加 + 墓碑 + 压实(compaction)**:temp+fsync+rename 的崩溃安全与 append 等价;覆写让文件永远是净值、肉眼可读,免掉折叠/压实/断行容错。域规模下整写与追加一行同量级。
|
||||
- **JSON 一表一文件**:覆写下文件粒度不影响写成本,按域合并文件更少,global 单例有落点。
|
||||
- **SQLite 整域存单行 blob**:任何一条记录变更都重写整域,失去按 key 精确更新——SQLite 相对 JSON 的唯一优势归零。
|
||||
- **SQLite 按 schema 生成 typed columns**:DDL 生成器过度建设;document-per-row 足够,查询需求出现再议。
|
||||
- **每域独立 sqlite db 文件**:与仓库一库多表惯例相反。
|
||||
- **path 作为 workspace key**:规范化/符号链接解析会改写 path;引用锚点必须稳定。
|
||||
- **归属用 cwd 派生(或与账合并)**:双真相源;cwd 表达不了排序;归属本就是 workspace 侧事实。
|
||||
- **变更事件带旧值**:仓库变更事件惯例是"新快照 + 操作判别"(唯一例外 fs 的 before/after 是方法返回值而非事件,因旧值事后不可重建且有 diff 消费者);需要 diff 的消费者自己持有上次快照。
|
||||
- **变更事件带旧值**:仓库变更事件惯例是"新快照 + 操作判别"(唯一例外 fs 的 before/after 是方法返回值而非事件,因旧值事后不可重建且有 diff 消费方);需要 diff 的消费方自己持有上次快照。
|
||||
- **删除自动 cancel 运行中 session**:持久层/编排层反向牵动运行时,层次变脏;cancel 机制已存在,调用方组合即可。
|
||||
|
||||
## Acceptance criteria
|
||||
## 验收标准
|
||||
|
||||
- 测试矩阵本期四套件全绿:backend 契约共享套件在 json/sqlite 双端、registry/mount disposer 语义、domain 层(含 open 六步与路由 fail-loud)、workspace 全语义(create/attach 校验/一致性口径)。
|
||||
- 测试矩阵本期四套件全绿:后端契约共享套件在 json/sqlite 双端、注册表/mount disposer 语义、domain 层(含 open 六步与路由 fail-loud)、workspace 全语义(create/attach 校验/一致性口径)。
|
||||
- `ctx.workspace` 可在测试组装下完成 create → attach → list → 仅删除元数据的 delete 生命周期。
|
||||
- session-persistence 包零 diff(本期不动 session 侧的验收线)。
|
||||
- 本期无新快照(无模型可见面与组装面);下期 RPC 接线时补。
|
||||
|
||||
## Risks
|
||||
## 风险
|
||||
|
||||
- **仓库持久化面第一个推式变更事件**(session-persistence 靠 revision 轮询):形态虽有 `goal/changed` 范本,但"存储层发事件"是新先例,下期 RPC 消费时才能验证形态是否合适。
|
||||
- **JSON 后端整域覆写的规模前提**:若第二个消费者(session sidecar)在路由到 SQLite 前就以千级记录落在 JSON 后端,整写成本会先于预期显现;缓解即 `routes` 改指 sqlite。
|
||||
- **删除语义的编排层检查依赖 `ctx.sessions` 弱依赖**:headless 组装拿不到运行时注册表时按"无热 session"处理,存在窗口(外部进程正在跑该 session);多进程本就在不做清单内,接受。
|
||||
- **JSON 后端整域覆写的规模前提**:若第二个消费方(session sidecar)在路由到 SQLite 前就以千级记录落在 JSON 后端,整写成本会先于预期显现;缓解即 `routes` 改指 sqlite。
|
||||
- **删除编排对 `ctx.sessions` 的弱依赖**:headless 组装拿不到运行时注册表时按"无热 session"处理,存在窗口(外部进程正在跑该 session);多进程本就在不做清单内,接受。
|
||||
- **facet 泛化以未来的 `log` facet 为设计依据但本期不实现它**:存在"预留形状不合身"的风险;缓解是本期后端介质代码按复用审计表的下沉形状组织,`log` facet 真正落地时只动 facet 层。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-25-client-settings-locale-theme.md
|
||||
2026-07-25-client-settings-locale-theme.md: c86d6ac053f7bb87ce758613a5f3a0a34951e428
|
||||
2026-07-25-client-settings-locale-theme.zh.md: 05edbb3c550828832a390e3cf4fad3262b5be196
|
||||
2026-07-25-client-settings-locale-theme.zh.md: a875de1ee7a4f64bc1040a7adf599524fc3a0552
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
# Agent Note: Client Settings、Locale 与 Theme 分层
|
||||
|
||||
Status: proposed
|
||||
状态:提议中
|
||||
|
||||
[English](2026-07-25-client-settings-locale-theme.md) | 中文
|
||||
|
||||
## Problem
|
||||
## 问题
|
||||
|
||||
浏览器端已有的 Settings 直接写在 Sidebar 内,语言和主题也由组件本地状态直接改 DOM。这使 Settings 无法由独立插件扩展,偏好状态没有稳定的跨插件服务契约,主题 registry 同时承担状态与呈现职责。
|
||||
浏览器端已有的 Settings 直接写在 Sidebar 内,语言和主题也由组件本地状态直接改 DOM。这使 Settings 无法由独立插件扩展,偏好状态没有稳定的跨插件服务契约,主题注册表同时承担状态与呈现职责。
|
||||
|
||||
## Proposal
|
||||
## 提案
|
||||
|
||||
**协作导向(后续所有模块接入 Settings 的方式):功能属主自注册。** Settings 壳是纯组合面:只声明坑位、渲染 chrome 结构,零文案、不依赖 locale、不 import 也不枚举任何功能;一个功能要出现在 Settings 里,由它自己的插件向对应坑位注册——locale 注册 Language 行,ui-theme 注册 Appearance 行,ui-models 注册 Models 一级面板。不为「某功能的设置页」单开 `ui-settings-*` 包:设置面属于功能包本身(做 Theme 功能,Theme 的设置选择就随 ui-theme 一起交付)。不属于任何单一功能的内容(trigger/标题/close 的 chrome 文案、General 目录与骨架行、`settings` 字典)由 `ui-settings-general` 拥有——它是「无主文案」的属主,不是功能卫星包。
|
||||
**协作导向(后续所有模块接入 Settings 的方式):功能属主自注册。** Settings 壳是纯组合面:只声明 slot、渲染 chrome 结构,零文案、不依赖 locale、不 import 也不枚举任何功能;一个功能要出现在 Settings 里,由它自己的插件向对应 slot 注册——locale 注册 Language 行,ui-theme 注册 Appearance 行,ui-models 注册 Models 一级面板。不为「某功能的设置页」单开 `ui-settings-*` 包:设置面属于功能包本身(做 Theme 功能,Theme 的设置选择就随 ui-theme 一起交付)。不属于任何单一功能的内容(trigger/标题/close 的 chrome 文案、General 目录与骨架行、`settings` 字典)由 `ui-settings-general` 拥有——它是「无主文案」的属主,不是功能卫星包。
|
||||
|
||||
Sidebar 声明 `sidebar.settings` 单坑位,`ui-settings` 占用它并声明四个坑:`settings.trigger` / `settings.header` / `settings.close`(chrome 内容座,single)与 `settings.section`(一级页面,list)。无障碍名全部解析自坑内容:trigger 的可达名即其文本内容,dialog 经 aria-labelledby 指向 header 内容节点,close 是视觉隐藏文本座。每个 section 由功能插件贡献;壳只从 slot ledger 读取 entry metadata 生成导航,通过 `only` 渲染当前 section。General 由 `ui-settings-general` 注册(order 0)并声明 `settings.general.item` list 坑位,功能插件的偏好行按 order 排入。
|
||||
Sidebar 声明 `sidebar.settings` single slot,`ui-settings` 占用它并声明四个 slot:`settings.trigger` / `settings.header` / `settings.close`(chrome 内容座,single)与 `settings.section`(一级页面,list)。无障碍名称全部解析自 slot 内容:trigger 的无障碍名称即其文本内容,dialog 经 aria-labelledby 指向 header 内容节点,close 是视觉隐藏文本座。每个 section 由功能插件贡献;壳只从 slot ledger 读取 entry metadata 生成导航,通过 `only` 渲染当前 section。General 由 `ui-settings-general` 注册(order 0)并声明 `settings.general.item` list slot,功能插件的偏好行按 order 排入。
|
||||
|
||||
Settings 入口是 sidebar Foot 的 Settings 行,点击直接打开 1080×700 居中浮层(黑 24% 遮罩);close 按钮、点击遮罩、ESC 均关闭。无任何中间菜单形态。
|
||||
|
||||
`@deepseek-ai/dsh-client-locale` 提供 `ctx.locale`,`ui-theme` 提供 `ctx.theme`。两个 service 都以 getter 读取、setter 写入并用 typed Cordis change event 发布 immutable snapshot;service 自己持久化偏好(只存 id,坏值回退默认)。
|
||||
`@deepseek-ai/dsh-client-locale` 提供 `ctx.locale`,`ui-theme` 提供 `ctx.theme`。两个服务都以 getter 读取、setter 写入并用 typed Cordis 变更事件发布 immutable snapshot;服务自己持久化偏好(只存 id,坏值回退默认)。
|
||||
|
||||
功能行的 apply 层各自订阅自家 change event(locale 订 `locale/change`,ui-theme 订 `theme/change`),把 snapshot 投影到该行注册时声明的 slot store。React 组件只读 `useStore`、写注入的 setter callback,不读取 ctx 或 service。
|
||||
功能行的 apply 层各自订阅自家变更事件(locale 订 `locale/change`,ui-theme 订 `theme/change`),把 snapshot 投影到该行注册时声明的 slot store。React 组件只读 `useStore`、写注入的 setter callback,不读取 ctx 或服务。
|
||||
|
||||
Theme 偏好三态:`light`、`dark`、`system`,默认 `system`(无持久化偏好或坏值时)。system 的解析属主题领域:ThemeService 持有 `prefers-color-scheme` matchMedia 监听(环境感知,非 DOM 呈现),偏好为 system 且系统配色变化时重发 snapshot;snapshot 同时携带 `preference` 与解析后的 `active` 定义。
|
||||
|
||||
Theme service 不操作 DOM。`ui-layout` 初始读取 Theme getter,随后订阅 `theme/change`,由 Layout 持有的 presenter 按 `active` 更新 `body[data-ds-dark-theme]` 和主题 token;presenter 不感知 system,只消费已解析结果。
|
||||
Theme 服务不操作 DOM。`ui-layout` 初始读取 Theme getter,随后订阅 `theme/change`,由 Layout 持有的 presenter 按 `active` 更新 `body[data-ds-dark-theme]` 和主题 token;presenter 不感知 system,只消费已解析结果。
|
||||
|
||||
### 首期注册面
|
||||
|
||||
| 注册面 | 属主插件 | 首期内容 |
|
||||
|---|---|---|
|
||||
| chrome 内容(trigger/header/close)| `ui-settings-general` | 设置入口行图标+文案、面板标题、close 隐藏文本 |
|
||||
| General section(order 0)| `ui-settings-general` | Permission、Tool Call 视觉骨架(无写操作)+ `settings.general.item` 坑位声明 |
|
||||
| General section(order 0)| `ui-settings-general` | Permission、Tool Call 视觉骨架(无写操作)+ `settings.general.item` slot声明 |
|
||||
| Language 行(item order 0)| `locale` | Selector 下拉,中文/English 真实可切 |
|
||||
| Appearance 行(item order 10)| `ui-theme` | Light/Dark/System 三 cube 真实可切(选中态看 preference) |
|
||||
| Models section(order 10)| `ui-models` | 仅导航项,内容区为空;后续模型管理功能落在该包 |
|
||||
| Plugin | 无 | 首期不做,导航不出现该项(后续插件功能包注册 section 即自动出现) |
|
||||
| 插件 | 无 | 首期不做,导航不出现该项(后续插件功能包注册 section 即自动出现) |
|
||||
|
||||
首期只翻译 Settings 浮层内文案;字典就近——chrome + General 骨架归 `ui-settings-general` 的 `settings` namespace,功能行文案归各功能包(`settings.locale`、`settings.theme`、`settings.models`)。
|
||||
首期只对 Settings 浮层内文案进行本地化;字典就近——chrome + General 骨架归 `ui-settings-general` 的 `settings` namespace,功能行文案归各功能包(`settings.locale`、`settings.theme`、`settings.models`)。
|
||||
|
||||
### Slot topology
|
||||
### slot拓扑
|
||||
|
||||
```text
|
||||
root
|
||||
@@ -55,13 +55,13 @@ root
|
||||
└─ models (order 10) ui-models 注册
|
||||
```
|
||||
|
||||
section/item contribution 均使用 declaration-aware deferral(ui-slots 的 `deferRegistration()`:ledger 判在位、一键 dispose;本地化 label 走 [全量接入 Note](../../implemented/architecture/2026-07-30-client-locale-full-rollout.md) 的 label thunk,不再 `refresh()`),不依赖 client manifest 的 apply 顺序。SlotMap 类型分家:trigger/header/close/section 正家在 ui-settings contract(消费者 general/models 均依赖壳,无环);`settings.general.item` 正家在 locale 包——它是全部 item 注册方的最低公共依赖(设置行必带文案),而声明方 general 的 contract 对 locale/ui-theme 不可达(会成环);ui-theme 经 re-export seam 消费。
|
||||
section/item contribution 均使用 declaration-aware deferral(ui-slots 的 `deferRegistration()`:ledger 判在位、一键 dispose(资源释放);本地化 label 走 [全量接入 Note](../../implemented/architecture/2026-07-30-client-locale-full-rollout.md) 的 label thunk,不再 `refresh()`),不依赖 client manifest(元数据清单)的 apply 顺序。SlotMap 类型分家:trigger/header/close/section 正家在 ui-settings 契约(消费方 general/models 均依赖壳,无环);`settings.general.item` 正家在 locale 包——它是全部 item 注册方的最低公共依赖(设置行必带文案),而声明方 general 的契约对 locale/ui-theme 不可达(会成环);ui-theme 经 re-export seam 消费。
|
||||
|
||||
### Future work:坑位声明升格为可 inject 的一等等待物
|
||||
### 未来工作:slot声明升格为可 inject 的一等等待物
|
||||
|
||||
`deferRegistration()` 与 `ctx.inject` 行为同构——一个等 ledger 声明、一个等服务在场,消失/重现的生命周期语义一致;差别只在 fiber 版的 disposer 生命周期天然等于声明生命周期,stale-disposer 判在位机器可整体消失。方向(另开 PR):SlotsService 在声明落账/级联拆除处把每个坑位桥接成 `slot:<name>` 服务(value 为坑位 spec),注册方从 `deferRegistration()` 迁为嵌套 `ctx.inject(['slot:<name>'], cb)`,随后删除 `deferRegistration()` 并改写 packages/client/AGENTS.md checklist 第 4 条。待钉死的边界:嵌套 fiber 的无害等待不被 boot fail-loud 扫描点名(需测试);`slot:` 名字空间与 typo 静默等待的口径;provide 键是平面名(`slot:a.b` 是一个键,不是 `ctx.slots` 的属性路径)。本期维持 `deferRegistration()` 函数形式。
|
||||
`deferRegistration()` 与 `ctx.inject` 行为同构——一个等 ledger 声明、一个等服务在场,消失/重现的生命周期语义一致;差别只在 fiber 版的 disposer 生命周期天然等于声明生命周期,陈旧 disposer 判在位机器可整体消失。方向(另开 PR):SlotsService 在声明落账/级联拆除处把每个slot桥接成 `slot:<name>` 服务(value 为slot spec),注册方从 `deferRegistration()` 迁为嵌套 `ctx.inject(['slot:<name>'], cb)`,随后删除 `deferRegistration()` 并改写 packages/client/AGENTS.md checklist 第 4 条。待钉死的边界:嵌套 fiber 的无害等待不被 boot fail-loud 扫描点名(需测试);`slot:` 名字空间与 typo 静默等待的口径;provide 键是平面名(`slot:a.b` 是一个键,不是 `ctx.slots` 的属性路径)。本期维持 `deferRegistration()` 函数形式。
|
||||
|
||||
### Service contracts
|
||||
### 服务契约
|
||||
|
||||
```ts
|
||||
export type ThemePreference = 'light' | 'dark' | 'system'
|
||||
@@ -100,31 +100,31 @@ export interface Events {
|
||||
|
||||
Locale 内置中文和 English;`setLocale`/`setTheme` 是唯一写入口,未知 id 失败。
|
||||
|
||||
## Alternatives considered
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**由 app shell 统一订阅偏好并重渲染 root slot tree。** 语言和主题变化只需要更新实际消费者;全树刷新放大影响面,也把业务偏好接入 shell。
|
||||
**由 app shell 统一订阅偏好并重渲染 root slot tree。** 语言和主题变化只需要更新实际消费方;全树刷新放大影响面,也把业务偏好接入 shell。
|
||||
|
||||
**Theme service 直接修改 DOM。** registry service 因此依赖呈现环境,生命周期与全局样式所有权不清;Layout 已经拥有页面根呈现边界。
|
||||
**Theme 服务直接修改 DOM。**注册表服务因此依赖呈现环境,生命周期与全局样式所有权不清;Layout 已经拥有页面根呈现边界。
|
||||
|
||||
**system 由 Layout presenter 解析。** presenter 需自带 matchMedia 订阅并在 themes 列表里挑选具体定义,呈现层被迫理解偏好语义;解析放服务侧则所有消费者拿到一致的已解析 snapshot。
|
||||
**system 由 Layout presenter 解析。** presenter 需自带 matchMedia 订阅并在 themes 列表里挑选具体定义,呈现层被迫理解偏好语义;解析放服务侧则所有消费方拿到一致的已解析 snapshot。
|
||||
|
||||
**Settings import 并枚举各 section。** 新增页面必须修改壳插件,破坏「每个功能由自己的插件占坑」的组合模型。
|
||||
**Settings import 并枚举各 section。** 新增页面必须修改壳插件,破坏「每个功能由自己的插件占用 slot」的组合模型。
|
||||
|
||||
**按功能为每个 section 单开 `ui-settings-*` 卫星包。** 设置面与功能本体分家:改 Theme 行为要动两个包,包数随设置项线性膨胀,且卫星包反向依赖 locale/theme 服务,形成纯粹为拆包而生的中间层。功能属主自注册下不存在这层:preference 行随功能包交付;`ui-settings-general` 只收无主文案(chrome 与 General 骨架),不承载任何功能的设置面。
|
||||
|
||||
**把 Locale/Theme snapshot 直接注入 React。** inject 结果按 entry identity 缓存,易变值会陈旧;为每个 service 自造 React hook 也绕开 slot store 的统一绑定。
|
||||
**把 Locale/Theme snapshot 直接注入 React。** inject 结果按 entry identity 缓存,易变值会陈旧;为每个服务自造 React 钩子也绕开 slot store 的统一绑定。
|
||||
|
||||
## Acceptance criteria
|
||||
## 验收标准
|
||||
|
||||
- Settings 壳只依赖 slot ledger,不依赖任一功能实现;General 的 item 列表同样只依赖 ledger。
|
||||
- 新增一个设置项 = 功能包自己注册(section 或 general item),零壳改动。
|
||||
- Locale 与 Theme 的写入只走 setter,持续同步只走 change event。
|
||||
- 功能行 store 初始化走 getter,后续由自家 change event 更新并局部重渲染。
|
||||
- Layout 独立应用 Theme snapshot,Theme service 不访问 DOM;presenter 不出现 system 分支。
|
||||
- Locale 与 Theme 的写入只走 setter,持续同步只走变更事件。
|
||||
- 功能行 store 初始化走 getter,后续由自家变更事件更新并局部重渲染。
|
||||
- Layout 独立应用 Theme snapshot,Theme 服务不访问 DOM;presenter 不出现 system 分支。
|
||||
- 中文/English 与 Light/Dark/System 能切换并刷新后恢复;偏好为 system 时系统配色变化即时生效。
|
||||
- Models 只有导航项与空内容区;Permission、Tool Call 骨架无写操作。
|
||||
- 浮层经 close 按钮、遮罩点击、ESC 均可关闭。
|
||||
|
||||
## Risks
|
||||
## 风险
|
||||
|
||||
slot 声明与 contribution 的 apply 顺序不固定,所有 section/item 注册方必须保留 declaration-aware registration,并以 ledger(而非本地 disposer)判定在位。service event 可能早于行首次渲染,功能行 store 的 init 与 inject attach 都必须从 getter 对齐当前 snapshot。`settings.general.item` 的重复合并副本(locale、ui-theme)与 ui-settings 正家必须逐字一致,漂移即三处一起改。Layout 卸载时必须清理自己设置的全局属性,ThemeService dispose 时必须移除 matchMedia 监听,避免 HMR 后残留。
|
||||
slot 声明与 contribution 的 apply 顺序不固定,所有 section/item 注册方必须保留 declaration-aware registration,并以 ledger(而非本地 disposer)判定在位。服务事件可能早于行首次渲染,功能行 store 的 init 与 inject attach 都必须从 getter 对齐当前 snapshot。`settings.general.item` 的重复合并副本(locale、ui-theme)与 ui-settings 正家必须逐字一致,漂移即三处一起改。Layout 卸载时必须清理自己设置的全局属性,ThemeService dispose 时必须移除 matchMedia 监听,避免 HMR(热模块替换)后残留。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# 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: 6a073c956c27bbfc65cff2d4f44ca12023df0cd5
|
||||
2026-07-27-session-projection-and-command-log.zh.md: 500f07968db049e4a174ff3b7a075bfe095283db
|
||||
2026-07-27-session-projection-and-command-log.zh.md: d67319b6321deebf258f23091d32b77f9696f5ab
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Agent Note: Session projections and command lifecycle logging
|
||||
# Agent Note: 会话投影与命令生命周期日志记录
|
||||
|
||||
Status: proposed
|
||||
状态:提议中
|
||||
|
||||
[English](2026-07-27-session-projection-and-command-log.md) | 中文
|
||||
|
||||
## Problem
|
||||
## 问题
|
||||
|
||||
三个在途的 web 功能——todo(#497)、goal(#527)、plan mode(#587)——都要从会话日志推导按会话的状态并呈现到浏览器客户端,而三者各自发明了一套同样的机制:
|
||||
|
||||
@@ -14,7 +14,7 @@ Status: proposed
|
||||
|
||||
底层缺口是架构性的:客户端没有一个 seam 让插件在会话 scope 内观察会话事件并维护自己的派生状态;host 侧也没有统一的方式把日志派生状态的当前值交给客户端——而该状态的历史可能已被分页挤出客户端窗口之外。
|
||||
|
||||
## Proposal
|
||||
## 提案
|
||||
|
||||
先立四件基础设施,之后各领域都退化为纯贡献方。
|
||||
|
||||
@@ -24,7 +24,7 @@ Status: proposed
|
||||
|
||||
### host 侧投影注册表(`dsh-session-projection`,新包)
|
||||
|
||||
一个轻量的接口包(package):merge-extensible 类型表、注册表服务、边界上的 zod 校验。能力 seam 三方拆分:领域 host 插件负责贡献,载体负责消费,两侧互不相识。
|
||||
一个轻量的接口包:merge-extensible 类型表、注册表服务、边界上的 zod 校验。能力 seam 三方拆分:领域 host 插件负责贡献,载体负责消费,两侧互不相识。
|
||||
|
||||
领域注册的是一个**状态驱动计算单元(state-driven computation unit)**——三个纯函数外加若干声明——绝不是一个不透明的 getter。驱动它是框架的职责(订阅、水位线(watermark)、缓存,以及后续的检查点机制),领域只负责数学本身。投影服务于所有业务领域(会话标题、plan、goal、权限、todos);命令只是其中一条触发路径,在本契约中没有任何特殊地位。
|
||||
|
||||
@@ -50,7 +50,7 @@ declare module 'cordis' {
|
||||
```
|
||||
|
||||
- 值就是协议层的 JSON 载荷;同一张类型表经 `import type` 端到端贯通(host 侧单元、协议块、React 钩子)——没有第二张 DTO 表,也没有独立的客户端「views」表。值如何*渲染*是 slot 体系的事,永远不归投影层管。
|
||||
- **host 是投影唯一的计算地点。** 框架正向驱动(eager drive)每个已注册的单元:每个已提交的会话事件都经过 `apply`;对某事件不感兴趣的单元返回同一个状态引用,而引用未变(`Object.is`)就不产生任何下游工作。客户端从不折叠领域事件——它们收到的是成品值(基线块 + 下文的推送帧)。这消除了双重实现陷阱(plan 的双事件折叠只在 host 写一遍),也消除了一切客户端侧领域代码。
|
||||
- **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 收编的第四个手工投影)。
|
||||
- 注册是 effect(disposer 随 fiber 走):插件卸载后其 key 从后续响应中消失,客户端将其读作能力缺失——HMR(热模块替换)语义随之自动成立。key 重复直接 throw。领域插件在 `ctx.inject(['sessionProjections'], …)` 下注册,因此不带注册表的 headless 组装完全不受影响。
|
||||
@@ -125,17 +125,17 @@ host 侧命令执行器(`packages/ui/commands`)在调用处理器前追加 `
|
||||
|
||||
客户端 flow 构建器新增一个通用命令节点(run/done 按 `commandId` 配对;跨窗口截断时与工具配对同样软降级)。渲染走一个新的 keyed slot `'conversation.chat.commandview'`,key = 命令名,**兜底 = 通用命令卡片**(零注册即可用——从前的通知文本现在持久地渲染在 flow 里)。领域要升级展示,只需注册一个行组件,取材于 `command/run` 的结构化字段与自己的投影值(`useProjection`)——与 toolview 解散之后的工具行同一形状。
|
||||
|
||||
## Delivery plan
|
||||
## 交付计划
|
||||
|
||||
基础设施先行;三个在途 PR(Pull Request)原样不动,待基座落地后重新对接(它们的迁移映射即指南):
|
||||
|
||||
1. **host 基座**:`dsh-session-projection`(单元契约、正向驱动、水位线缓存)+ api-proxy 的 projections 块 + `session/projection` 推送帧。零领域注册也可合入(此时块与帧直接缺席)。
|
||||
1. **host 基座**:`dsh-session-projection`(单元契约、主动驱动、水位线缓存)+ api-proxy 的 projections 块 + `session/projection` 推送帧。零领域注册也可合入(此时块与帧直接缺席)。
|
||||
2. **客户端基座**:通用值仓 + `useProjection` 席位;下线按领域的 cell 机制,并在标题单元注册后一并下线 `session/title` 帧与标题快照表。帧的形状依赖 1(在此之前 fixture(测试前置数据)喂合成帧)。
|
||||
3. **命令通道**:两个事件、执行器落日志、通用节点 + keyed slot、通知通道下线、`{matched, commandId?}` 准入。与 1 并行。
|
||||
4. **领域重新对接**(在 1+2 之后):先 todo(单元进 `tool-todo`,删掉搭载字段),再 plan(双事件单元、RPC 下线、开关改发 `/plan`),最后 goal(`goal/change` 单元,删掉 `goals.get`,把六个 `Session` 方法移入领域插件的 inject)。
|
||||
5. **持久投影缓存**(后续阶段,待 domain-KV 存储 seam 就绪后):`(sessionId, key, ver, seq, val)` 行、带 turn/end 与 detach 强制点的节流写入,以及持久化侧供冷尾部回放用的按 seq 起读原语。
|
||||
|
||||
## Alternatives considered
|
||||
## 备选方案
|
||||
|
||||
**专设一个 `session.projections` RPC**——不予采纳:基线刷新时刻与尾页拉取精确重合,单独的一元 RPC 只会换来第二次往返、第二个待调和的 seq,以及一个客户端「何时重取」决策——而搭载设计把这个决策整个删掉了。
|
||||
|
||||
@@ -147,7 +147,7 @@ host 侧命令执行器(`packages/ui/commands`)在调用处理器前追加 `
|
||||
|
||||
**客户端侧折叠(带 `fromEvent` 的按领域投影 cell)**——曾是第二稿,后被否决:一旦 plan 的单元要折叠两种事件,客户端 cell 就必须在浏览器里复刻 host 的状态转移逻辑——同一个折叠写两遍、各自演化。推送成品值(标题帧先例的泛化)保住唯一计算地点,并把客户端简化为一个由 seq 把守的通用值仓;领域零客户端代码。
|
||||
|
||||
**对日志尾部的有界反向扫描(absorber 声明)**——暂不采纳:今天没有任何东西需要它,它只服务于「每个事件都携带完整折叠状态」的领域,而持久投影缓存以统一方式覆盖同一冷读需求(缓存行 + 正向尾部回放——与客户端的基线 + 追赶、与分页加载是同一套配方)。只有当出现检查点机制服务不了的真实冷读路径时才重议。
|
||||
**对日志尾部的有界反向扫描(absorber 声明)**——暂不采纳:今天没有任何东西支持它,它只服务于「每个事件都携带完整折叠状态」的领域,而持久投影缓存以统一方式覆盖同一冷读需求(缓存行 + 正向尾部回放——与客户端的基线 + 追赶、与分页加载是同一套配方)。只有当出现检查点机制服务不了的真实冷读路径时才重议。
|
||||
|
||||
**`invalidate` 式 cell(标脏,遇领域事件就重取)**——不予采纳:它的存在只为伺候增量事件。全量值规则让每个领域都是 last-wins;goal 的重取循环、合并逻辑、陈旧读栅栏随之全部消失。
|
||||
|
||||
@@ -163,7 +163,7 @@ host 侧命令执行器(`packages/ui/commands`)在调用处理器前追加 `
|
||||
|
||||
**让变更 RPC 的响应喂 cell 状态**——不予采纳:已提交的 mux 事件即刻到达,携带同一个全量值外加 seq;「响应喂状态」正是当初逼出 #527 写 revision 栅栏的根源。
|
||||
|
||||
## Acceptance criteria
|
||||
## 验收标准
|
||||
|
||||
- 领域插件把按会话的日志派生状态送达 React,只需写:全量值事件声明、一次 host 侧单元 `register`、自己那份 `SessionProjectionMap` merge、以及 inject 回调——零客户端侧代码,不改客户端 `Session` 类、`ConversationSnapshot`、api-proxy 或任何协议 schema 文件。
|
||||
- 历史尾页携带 `projections`,其 `asOfSeq` 等于窗口尾部 seq;loadOlder 页永不携带;未装注册表的部署照常返回不带该块的历史,客户端把所有 key 视为缺席。
|
||||
@@ -172,12 +172,12 @@ host 侧命令执行器(`packages/ui/commands`)在调用处理器前追加 `
|
||||
- `useProjection` 经标准 props 套件抵达组件;没有任何钩子穿过 inject 契约(包括 `useSelection`)。
|
||||
- 会话标题搭乘这对通用机制(基线块 + 投影帧);专设的 `session/title` 帧与客户端标题快照表彻底移除。
|
||||
|
||||
## Risks
|
||||
## 风险
|
||||
|
||||
- **全量值规则是承重结构**:未来某个领域若只记裸增量,就无法凭其最新事件服务消费方,还会让自己的单元复杂化。缓解:该规则写明在本 Note 与投影包的 README 里;单元契约让完整状态在每次转移处都是显式的。
|
||||
- **单元的同步纪律**:`init`/`apply`/`view` 一旦 await 就会撕裂一致性切面。注册表在文档中申明这条纪律,invariant 配套在可行范围内断言同步性;其余由评审把关。
|
||||
- **注册表的实时增删不做推送**:会话中途加载或卸载领域插件会改变键集,但不会触发任何会话事件、也不会推任何帧;开着的客户端持有陈旧的 key 直到下次尾页拉取(重连、缺口修补、打开)。接受为仅开发期(HMR)的陈旧时窗——日后可以在变更流上加一个注册表变更推送,契约不受影响。
|
||||
- **忙碌会话上的正向驱动开销**:每个已提交事件都要过每个已注册单元的 `apply`。按构造,单元的逐事件开销很低(全量值规则),不匹配的事件返回同一引用,且已注册领域的数量很小;若真出现热点路径,可以加按单元的事件类型预过滤,契约不变。
|
||||
- **忙碌会话上的主动驱动开销**:每个已提交事件都要过每个已注册单元的 `apply`。按构造,单元的逐事件开销很低(全量值规则),不匹配的事件返回同一引用,且已注册领域的数量很小;若真出现热点路径,可以加按单元的事件类型预过滤,契约不变。
|
||||
- **投影载荷膨胀**:每个尾页携带每个已注册的 key。载荷是 UI 量级状态的全量值(一张 todo 清单、一份 goal 快照);将来若某领域的值很大,可以在请求上加逐 key 的 opt-out 或惰性 key,模型本身不用改。
|
||||
- **命令日志体量**:每条斜杠命令两个仅日志事件;上限由人敲命令的频率决定,相对分片体量可忽略不计。
|
||||
- **重新对接的返工**:三个未合入的 PR 要变基到挪动后的地基上。这是基础设施先行的既定代价;设计台账中的迁移映射一节逐一列出每个 PR 的保留/删除清单。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md
|
||||
2026-07-28-storage-root-and-derived-medium-recovery.md: 2edb60d204da736bf14ace6278fa4ee7b9e4b8f7
|
||||
2026-07-28-storage-root-and-derived-medium-recovery.zh.md: daef4f7661df069df3438f909f2744880a93a873
|
||||
2026-07-28-storage-root-and-derived-medium-recovery.zh.md: e499f8329e63116a4741d51fbddb591ddb915bfe
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Agent Note:存储根目录落点与派生介质恢复
|
||||
# Agent Note: 存储根目录落点与派生介质恢复
|
||||
|
||||
Status: proposed
|
||||
状态:提议中
|
||||
|
||||
[English](2026-07-28-storage-root-and-derived-medium-recovery.md) | 中文
|
||||
|
||||
## Problem
|
||||
## 问题
|
||||
|
||||
持久投影缓存([RFC](2026-07-27-session-projection-and-command-log.md),已作为 `dsh-session-projection-cache` 落地)暴露了它所依托的存储基座的两个缺口。二者都是 domain-KV 栈([设计](2026-07-24-domain-kv-storage-and-workspace.md))的属性而非缓存自身的问题,且都首先咬到缓存——因为它是这条栈上第一个*派生*介质。
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: proposed
|
||||
|
||||
**现在是怎么恢复的。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit` 以 `malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc("version bumps discard the whole medium")相矛盾——后者今天描述的是愿望而非实现。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。
|
||||
|
||||
## Proposal
|
||||
## 提案
|
||||
|
||||
两个独立改动,一个缺口一个。
|
||||
|
||||
@@ -24,12 +24,12 @@ Status: proposed
|
||||
|
||||
### 声明派生介质:损坏时重置而非拒绝
|
||||
|
||||
- `DomainSpec` 增加 `recovery?: 'reject' | 'reset'`(默认 `'reject'`)。spec 对象已经是一个域的身份与布局的单一来源;其介质是权威还是派生属于同类事实,落在同一处。`session_projcache` 声明 `'reset'`;`workspace` 保持默认。
|
||||
- `DomainSpec` 增加 `recovery?: 'reject' | 'reset'`(默认 `'reject'`)。spec 对象已经是一个域的身份与布局的真源;其介质是权威还是派生属于同类事实,落在同一处。`session_projcache` 声明 `'reset'`;`workspace` 保持默认。
|
||||
- `KvFacet` 增加一个原语:`destroy(descriptor): Promise<void>`——整体移除该 unit 的介质(json:删文件;sqlite:drop 该 unit 的表)。与 `open` 一样,它是后端存储原语,不是策略。
|
||||
- `DomainFacility.open` 在 spec 声明 `'reset'` 且 open 恰以损坏类错误失败时——`StorageError('version-mismatch' | 'malformed-medium')` 或 `DomainError('invalid-record')`——记一条命名该域和被丢弃介质的警告,调用 `destroy`,再空开一次。其余一切失败(`backend-not-found`、`facet-unsupported`、`already-open`、I/O 错误)无论声明与否都保持大声:配置错误和环境故障不是介质损坏。重试单发——第二次失败原样传播,持续失败的介质不会成环。
|
||||
- 有了这个,缓存域 spec 的 version 字段才获得其本意:bump `version`(或让 zod 拒绝漂移行)真正丢弃整个介质,缓存经正常写点和冷读重建——恢复阶梯的最外一档,与已落地的行级各档对齐。
|
||||
|
||||
## Alternatives considered
|
||||
## 备选方案
|
||||
|
||||
**保持按启动目录的 `.storages`(改动前现状)**——拒绝:会话是全局的,所以每个从会话派生的介质都与自己的真源劈叉;缓存的动机场景(一次列出全部会话)结构性丢行,工作区注册表索引着从另一个启动目录看不见的会话。
|
||||
|
||||
@@ -45,15 +45,15 @@ Status: proposed
|
||||
|
||||
**所有域一律自动重置(不加 spec 字段)**——断然拒绝:`workspace.json` 是权威用户数据;版本 bump 时静默重置会毁掉工作区。权威性是域的属性,必须由其所有者声明。
|
||||
|
||||
## Acceptance criteria
|
||||
## 验收标准
|
||||
|
||||
- 从任意目录启动 `dsh` 都读写同一份 `$DSH_HOME/storages/*.json`(默认 `~/.dsh/storages`)——已由 overlay 表达式满足,按行覆盖走个人 config.yaml patch 层;后端对相对根在构造时 resolve 一次(待做)。
|
||||
- `session_projcache.json` 被截断、版本 bump 或 schema 漂移时,组装干净启动:一条警告命名被丢弃的介质,文件消失,缓存经正常运转重建,冷列表列随会话重新 checkpoint 逐步回归。
|
||||
- 同样的损坏发生在 `workspace.json` 上仍大声拒绝启动。
|
||||
- facility 测试覆盖:每个损坏类恰好重置一次 `'reset'` 域;非损坏失败在 `'reset'` 域上保持大声;`'reject'` 域传播一切失败;`destroy` 在两个出厂后端上都移除介质。
|
||||
|
||||
## Risks
|
||||
## 风险
|
||||
|
||||
- **错误分类失误导致自动删除健康文件。** 由封闭的损坏类清单缓解:重置只在三个确定性解析期代码上触发;ENOENT 本来就是「空 unit」,一切 I/O 错误(EACCES、EIO)大声传播。单发重试把爆炸半径限定为每次 open 至多一删。
|
||||
- **根迁移改变既有 checkout 的查找位置。** 在 pre-release 立场下接受(后端拒绝旧格式、无外部消费者);上文为在乎 per-cwd `workspace.json` 内容的人记录了一次性手动搬移。
|
||||
- **`destroy` 是存储 seam 上新增的破坏性原语。** 唯一调用方是 facility 的声明重置路径;后端契约将其记档为 facility 专属,任何面向模型或面向用户的路径都触不到它。
|
||||
- **错误分类失误导致自动删除健康文件。** 由封闭的损坏类清单缓解:重置只在三个确定性解析期代码上触发;ENOENT 本来就是「空 unit」,一切 I/O 错误(EACCES、EIO)大声传播。单发重试把爆炸半径限定为每次 open 至多一删。
|
||||
- **根迁移改变既有 checkout 的查找位置。** 在 pre-release 立场下接受(后端拒绝旧格式、无外部消费方);上文为在乎 per-cwd `workspace.json` 内容的人记录了一次性手动搬移。
|
||||
- **`destroy` 是存储 seam 上新增的破坏性原语。** 唯一调用方是 facility 的声明重置路径;后端契约将其记档为 facility 专属,任何面向模型或面向用户的路径都触不到它。
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md
|
||||
2026-06-30-pre-tool-input-rewrite.md: f35e6af465ce8cec5685911c43e12b8dd66f2e6a
|
||||
2026-06-30-pre-tool-input-rewrite.zh.md: c94c647bb6867199b72528bc84c58a08ae93e27e
|
||||
2026-06-30-pre-tool-input-rewrite.zh.md: a260d53edd1f27f53e567ab23d205e125dd153b8
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Agent Note: 工具执行前输入重写——一致性设计
|
||||
|
||||
Status: proposed
|
||||
状态:提议中
|
||||
|
||||
[English](2026-06-30-pre-tool-input-rewrite.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
[拦截 seam Agent Note(agent 决策记录)](../../implemented/feature/2026-06-30-interception-seams.md) 将 `tools/pre-execute` 定义为一道针对执行的允许/拒绝/询问门禁,此时执行的身份标识已受保护、参数已被深度冻结。Claude Code 的 `PreToolUse` 钩子还提供了 `updatedInput`,因此忠实的桥接需要一个显式的重写机制。重写不能是对现有执行对象的可变逃逸口:它必须保持持久化历史、审计记录、展示层与实际执行值之间的一致性。
|
||||
[拦截 seam Agent Note](../../implemented/feature/2026-06-30-interception-seams.md) 将 `tools/pre-execute` 定义为一道针对执行的允许/拒绝/询问门禁,此时执行的身份标识已受保护、参数已被深度冻结。Claude Code 的 `PreToolUse` 钩子还提供了 `updatedInput`,因此忠实的桥接需要一个显式的重写机制。重写不能是对现有执行对象的可变逃逸口:它必须保持持久化历史、审计记录、展示层与实际执行值之间的一致性。
|
||||
|
||||
## 问题本质:执行前参数的三个读取方
|
||||
|
||||
@@ -42,7 +42,7 @@ Status: proposed
|
||||
|
||||
## 风险
|
||||
|
||||
- 重写 `assistant/message` 中的工具调用块会改变模型「看到自己说了什么」;是否有提供方在回放时拒绝这种改动,是一个需要通过实验确定的开放问题,必须在决策形状冻结之前解决。
|
||||
- 重写 `assistant/message` 中的工具调用块会改变模型「看到自己说了什么」;是否有提供方在回放时拒绝这种改动,是一个需要通过实验确定的开放问题,必须在决策结构定型之前解决。
|
||||
- 更早的重写阶段改变了 `assistant/message`、`tool/call`、钩子审计事件与执行之间的顺序关系;设计必须固定这一顺序,同时不削弱轮次封闭性或调用/结果邻接性。
|
||||
|
||||
## 开放问题
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-07-06-recallable-compaction.md
|
||||
2026-07-06-recallable-compaction.md: ed5491e642ea7ac99fd9f4ba071a61e655f968d3
|
||||
2026-07-06-recallable-compaction.zh.md: 4060df2c2550f9ea3287adfb51d097c1a60baf71
|
||||
2026-07-06-recallable-compaction.zh.md: 1112d10e92678fbfbf9074b907699b845fa424c0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 可回溯压缩(compaction):索引检查点、状态检查点与会话内历史回溯
|
||||
# Agent Note: 可回溯压缩:索引检查点、状态检查点与会话内历史回溯
|
||||
|
||||
Status: proposed
|
||||
|
||||
@@ -24,7 +24,7 @@ Status: proposed
|
||||
- 用一行关键词记录低频字面锚点,例如确切的错误字符串、值和配置键,并按类别分组;
|
||||
- 由代码组装页脚:`[checkpoint c<summarySeq>: shadows conversation span #<start>–#<end>; originals retrievable via history_read]`。指针根据来源组装,绝不由模型编写。
|
||||
|
||||
已经提交的存根永不重写,也绝不再次进入之后的压缩区域。存根调用采用分层输入:固定前导内容与逐字节相同的本轮开始状态检查点(该阶段所有调用共享的前缀);随后是先前所有已提交存根的关键词行,使新条目索引其分片的独特内容,而不是重复整个目录;再加最近一两个已提交存根以维持时间连续性;最后是切片本身。同一轮中的同级存根不作为输入,因为并发阶段禁止这种依赖,而与轮次对齐的边界已经维持局部连续性。状态检查点只作为背景,绝不能成为存根需要总结的材料。完全由回溯内容构成的切片只通过代码生成存根,即只写一行指针,不调用 LLM(大语言模型)。存根调用失败时采用相同降级方式:其切片获得一个仅包含代码指针的存根,本轮继续执行,使状态重写成为一轮中唯一的强制 LLM 依赖。
|
||||
已经提交的存根永不重写,也绝不再次进入之后的压缩区域。存根调用采用分层输入:固定前导内容与逐字节相同的本轮开始状态检查点(该阶段所有调用共享的前缀);随后是先前所有已提交存根的关键词行,使新条目索引其分片的独特内容,而不是重复整个目录;再加最近一两个已提交存根以维持时间连续性;最后是切片本身。同一轮中的同级存根不作为输入,因为并发阶段禁止这种依赖,而与轮次对齐的边界已经维持局部连续性。状态检查点只作为背景,绝不能成为存根需要总结的材料。完全由回溯内容构成的切片只通过代码生成存根,即只写一行指针,不调用 LLM(大语言模型)。存根调用失败时采用相同降级方式:其切片获得一个仅由代码生成的指针存根,本轮继续执行,使状态重写成为一轮中唯一的强制 LLM 依赖。
|
||||
|
||||
### 状态检查点
|
||||
|
||||
@@ -41,12 +41,12 @@ Status: proposed
|
||||
|
||||
### 回溯工具
|
||||
|
||||
新增包(package)`@deepseek-ai/dsh-tool-recall`,它只是 `dsh-session` 与 `dsh-compact` 词汇之上的消费方,注册两个面向模型的工具:
|
||||
新增包`@deepseek-ai/dsh-tool-recall`,它只是 `dsh-session` 与 `dsh-compact` 词汇之上的消费方,注册两个面向模型的工具:
|
||||
|
||||
- `history_read(checkpoint, offset?)`:把日志中任意检查点(包括已被取代的检查点)遮蔽的区段渲染为 `User:`/`Assistant:`/`Tool result:` transcript(文本记录),并按配置预算分页,提供续传游标。
|
||||
- `history_search(query, checkpoint?, limit?)`:对每个被遮蔽区段进行不区分大小写的字面量扫描;返回带检查点 id 的片段与覆盖元数据(`scanned`/`matched`/`truncated`)。零匹配提示会说明扫描按字面量执行,并建议对可能的检查点直接使用 `history_read`。
|
||||
|
||||
两个工具都读取 `exec.agent.session.events`(沿用 tool-todo 访问模式;拒绝非 agent(智能体)调用方),只渲染表面类型的消息事件,并返回普通 `tool/result`:回溯字节会进入上下文尾部并记录到日志,因此无需特殊处理即可满足可重建性。系统不增加新存储或伴随索引:会话日志是归档,`compact/summary` 来源是索引元数据,而这些工具是两者之上的读取路径。工具 schema 与该包唯一的系统提示词章节都是静态字符串;检查点 id 只会通过页脚抵达模型。transcript 渲染器从 `compact-basic` 移入 `dsh-session`,供摘要器与工具共享。
|
||||
两个工具都读取 `exec.agent.session.events`(沿用 tool-todo 访问模式;拒绝非 agent 调用方),只渲染表面类型的消息事件,并返回普通 `tool/result`:回溯字节会进入上下文尾部并记录到日志,因此无需特殊处理即可满足可重建性。系统不增加新存储或伴随索引:会话日志是归档,`compact/summary` 来源是索引元数据,而这些工具是两者之上的读取路径。工具 schema 与该包唯一的系统提示词章节都是静态字符串;检查点 id 只会通过页脚抵达模型。transcript 渲染器从 `compact-basic` 移入 `dsh-session`,供摘要器与工具共享。
|
||||
|
||||
### 缓存与成本
|
||||
|
||||
@@ -60,7 +60,7 @@ Status: proposed
|
||||
|
||||
- **工具结果裁剪**(进行中的裁剪服务):其替换节点携带 `sourceEventSeqs`;同一注册表折叠会把经过裁剪的结果列为可回溯。它属于后续范围,两项工作互不阻塞。
|
||||
- **提供方 token 用量核算**(正在把压缩压力迁移至提供方报告用量的工作):为保护逻辑提供核算基础;本实现堆叠在它之后。
|
||||
- **「查询会话」backlog(待翻清单)条目**:它是跨会话的泛化方案;本 Agent Note(agent 决策记录)把范围限定在实时会话内,并选择工具名称与渲染方式,使该工作能够扩展本设计而不产生冲突。
|
||||
- **「查询会话」backlog(待办清单)条目**:它是跨会话的泛化方案;本 Agent Note 把范围限定在实时会话内,并选择工具名称与渲染方式,使该工作能够扩展本设计而不产生冲突。
|
||||
- **训练**:何时回溯属于学习到的行为。确定性页脚与关键词锚点为训练提供稳定目标,而回溯使用情况在会话日志中完全可见,可供轨迹导出;基准测试与 RL 设计由后训练侧推进。
|
||||
|
||||
### 后续事项
|
||||
@@ -72,19 +72,19 @@ Status: proposed
|
||||
- 定期使用分片原文刷新状态:触发条件是交接探针观察到漂移。
|
||||
- `stateFallbackThreshold`(存根数量低于阈值时使用完整细节的状态提示词):触发条件是短会话回归。
|
||||
- 延迟注册回溯工具:触发条件是在从不进行压缩的会话中测得上下文开销。
|
||||
- 在 pre-step 分摊存根起草工作:一旦已经陈旧但尚未压缩的内容积累超过 `chunkTokens`,就在下一个 pre-step 起草该分片的存根(一个仅写入日志的草稿事件,在分片周围的上下文仍然存活时写入),让压缩轮提交草稿,而不是集中执行摘要。这是后台压缩的确定性、精确回放等价形式(Claude Code 会话记忆采用这种模式;OpenClaw 证明同步语义完全相同)。触发条件是观察到一轮延迟,或近实时起草带来的存根质量收益得到验证。
|
||||
- 在 pre-step 分摊存根起草工作:一旦已经陈旧但尚未压缩的内容积累超过 `chunkTokens`,就在下一个 pre-step 起草该分片的存根(一个仅写入日志的草稿事件,在分片周围的上下文仍然存活时写入),让压缩轮提交草稿,而不是集中执行摘要。这是后台压缩的确定性、精确回放等价形式(Claude Code 会话记忆采用这种模式;OpenClaw 证明同步语义完全相同)。触发条件是观察到压缩轮次的执行延迟,或近实时起草带来的存根质量收益得到验证。
|
||||
- 拆分摘要模型;由模型选择分片边界;跨会话回溯;语义搜索回退:每项都必须由各自证据支持。
|
||||
- 更丰富的 `history_search` 查询形式:正则表达式,以及对日志 JSON 工具结果执行的结构化查询(sql/jq 风格,或由 agent 针对索引存储编写查询)。触发条件是观察到搜索漏检;首版先交付字面量匹配,使回溯路径保持为日志的纯函数。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **分阶段交付**(先在当前后端之上单独交付回溯工具;观察到回溯使用后,再决定是否拆分检查点):不予采纳。未经训练的模型会低频使用任何新工具,因此该条件测量的是训练缺失,而不是设计价值;训练侧需要完整机制来构建环境;预发布阶段修改持久化格式的成本最低;缓存经济性则属于第一方已经掌握的知识,不是等待遥测验证的假设。实现仍以堆叠 PR 方式落地,并先交付回溯工具,但这只是构建顺序,不是决策门槛。
|
||||
- **分阶段交付**(先在当前后端之上单独交付回溯工具;观察到回溯使用后,再决定是否拆分检查点):不予采纳。未经训练的模型会低频使用任何新工具,因此该条件测量的是训练缺失,而不是设计价值;训练侧需要完整机制来构建环境;预发布阶段修改持久化格式的成本最低;缓存经济性则属于第一方已经掌握的知识,不是等待遥测验证的假设。实现仍以堆叠 PR(Pull Request)方式落地,并先交付回溯工具,但这只是构建顺序,不是决策门槛。
|
||||
- **只保留冻结的全尺寸摘要,不设状态检查点**:不予采纳,因为永久前缀会无界增长、自我加速并最终发生颠簸,而且没有任何内容可以重新确定优先级。
|
||||
- **只保留纯存根,不设状态检查点**:不予采纳,因为这假定模型知道自己缺少什么,在面对未知的未知时会失败。
|
||||
- **由 LLM 老化/整合冻结分片**:不作为常规机制,因为摘要的摘要会丢失信息,并使冻结前缀频繁变化;其保留下来的形式是由代码汇总,且延后实现。
|
||||
- **把完整前缀作为分片摘要器输入**:不予采纳,因为成本为 O(N²);状态文档以 O(state) 提供相同背景。
|
||||
- **一次摘要调用输出全部结果**:不予采纳,因为摘要路径没有结构化输出约束;解析一份自由文本响应并将其拆开,正是保守失败设计要避免的脆弱 seam。
|
||||
- **由模型选择分片边界**:延后实现,因为相对于未经证明的收益,解析与校验成本过高;分片策略位于配置之后。
|
||||
- **由模型选择分片边界**:延后实现,因为相对于未经证明的收益,解析与校验成本过高;分片策略由配置控制。
|
||||
- **由模型编写指针**:不予采纳,因为指针必须精确,应由确定性代码组装。
|
||||
- **FTS/向量索引伴随存储**:在会话内不予采纳,因为实时日志已在内存中且大小有界,在预算内进行字面量扫描已经足够;只有跨会话范围才能证明索引的价值。
|
||||
- **回溯路径中的语义搜索回退/次级模型提取**:不予采纳,因为其中的 LLM 或嵌入调用会破坏无密钥回放的确定性;回溯必须保持为日志的纯函数。
|
||||
@@ -107,4 +107,4 @@ Status: proposed
|
||||
- **存根目录会占用注意力**:每次请求中包含数十张稳定的索引卡,可能稀释模型关注点;验收标准中的 bench 度量会将其与 `compact-basic` 对比。
|
||||
- **成本**:每轮摘要输入大约是当前实现的两倍;短会话的成本和质量接近当前水平,而设计收益随会话长度增长。
|
||||
- **状态漂移与职责分工泄漏** 可以通过交接探针和存根评审观察;对应措施已列为后续事项。
|
||||
- **两个后端** 会增加维护接口;seam 契约和共享回溯消费方会约束该成本,bench 对比则用于逐步决定默认实现。
|
||||
- **两个后端** 会扩大维护范围;seam 契约和共享回溯消费方会限制这一范围,bench 对比则用于逐步决定默认实现。
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md
|
||||
2026-07-07-claude-code-and-codex-subagent-backends.md: ee8576f97a9fdef8c88dcad3a73f28b63ca3ebe1
|
||||
2026-07-07-claude-code-and-codex-subagent-backends.zh.md: 14e8dde04d9526aaffc0e58be049e13858362887
|
||||
2026-07-07-claude-code-and-codex-subagent-backends.zh.md: bd76a8f34d86b704494b56331c47ea89bfc8a0aa
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: Claude Code 与 Codex subagent 后端(向外部编码 agent(智能体)的进程外委派)
|
||||
# Agent Note: Claude Code 与 Codex subagent 后端(向外部编码 agent 的进程外委派)
|
||||
|
||||
Status: proposed
|
||||
|
||||
@@ -6,21 +6,21 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
subagent seam([seam Agent Note(agent 决策记录)](../../implemented/feature/2026-06-21-subagent-capability-seam.md))在 `ctx.subagents` 上托管多个命名提供方,ACP(Agent Client Protocol)后端([ACP 后端 Agent Note](../../implemented/feature/2026-06-22-acp-subagent-backend.md))证明了该 seam 能跨越进程边界泛化;其「未来提供方」一节明确将 Codex app-server 与 Claude Code Agent SDK 列为机械上相似的兄弟。如今真正值得委派的就是这两个引擎:harness 的一个轮次应能把一个自包含任务交给真实的 Claude Code 或真实的 Codex——一个拥有自身模型、工具与沙箱的独立产品——并取回一个最终答案,同时父部署不向子进程泄漏密钥,子进程行为也不静默依赖宿主机上碰巧存在的 `~/.claude` / `~/.codex` 状态。
|
||||
subagent seam([seam Agent Note](../../implemented/feature/2026-06-21-subagent-capability-seam.md))在 `ctx.subagents` 上托管多个命名提供方,ACP(Agent Client Protocol)后端([ACP 后端 Agent Note](../../implemented/feature/2026-06-22-acp-subagent-backend.md))证明了该 seam 能跨越进程边界泛化;其「未来提供方」一节明确将 Codex app-server 与 Claude Code Agent SDK 列为实现机制相似的同类方案。如今真正值得委派的就是这两个引擎:harness 的一个轮次应能把一个自包含任务交给真实的 Claude Code 或真实的 Codex——一个拥有自身模型、工具与沙箱的独立产品——并取回一个最终答案,同时父部署不向子进程泄漏密钥,子进程行为也不静默依赖宿主机上碰巧存在的 `~/.claude` / `~/.codex` 状态。
|
||||
|
||||
## 提案
|
||||
|
||||
两个兄弟提供方包(package),作为 ACP 后端的结构变体,另加一次提取:
|
||||
两个兄弟提供方包,作为 ACP 后端的结构变体,另加一次提取:
|
||||
|
||||
- `@deepseek-ai/dsh-subagent-claude-code`:通过 `@anthropic-ai/claude-agent-sdk` 的 `query()` 驱动一个 Claude Code 子进程(SDK 在父进程中运行,并将其内置的 `claude` CLI(命令行界面)作为子进程 spawn)。提供方名称为 `claude-code`:子进程是 Claude Code 这个*产品*,而非 Anthropic 模型适配器——「claude」保留给未来的 `dsh-llm` 适配器。
|
||||
- `@deepseek-ai/dsh-subagent-codex`:spawn `codex app-server`,通过其 JSON-RPC-over-stdio 协议驱动一个 thread/turn,使用包内一个手写的换行 JSON 客户端(约 200–300 行)。
|
||||
- `@deepseek-ai/dsh-subagent-codex`:spawn `codex app-server`,通过其 JSON-RPC-over-stdio 协议驱动一个 thread 及其中的一个轮次,使用包内一个手写的换行 JSON 客户端(约 200–300 行)。
|
||||
- `@deepseek-ai/dsh-subagent-process`:纯库(沿用 `subagent-inprocess` 的先例),提取 `dsh-subagent-acp` 已有且两个新后端都需要的内容:凭证环境清洗(`buildChildEnv`)、EOF → SIGTERM → SIGKILL 的 dispose(资源释放)阶梯,以及新的隔离配置目录辅助函数(`mkdtemp` 创建、尽力删除)。ACP 后端迁移到该库上;`bash-local` 的兄弟副本保持不动以限制变更范围。
|
||||
|
||||
两个提供方逐字复制 ACP 后端的 seam 姿态:每次 `start` 创建全新子进程、恰好一次提示词往返、所有能力均为 `false`、`inheritsParentContext: false`、忽略 `request.parent`/`request.agentOptions`、`id = SessionId(randomUUID())`,且 `result` 从不 reject——子进程级失败扁平化为 stop reason,原始错误则通过 `onError` spec 回调送到 `ctx.logger`。模型暴露无需新代码:每个提供方各加载一次 `dsh-tool-subagent`,使用不同的 `toolName`(`subagent_claude_code`、`subagent_codex`)。无需新的会话事件——唯一的模型可见产物是工具结果,因此可重建性与 ACP 完全相同。明确边界:会话日志重建模型可见的 transcript(文本记录),而不是工作区变更历史——获准写入的子进程将文件作为日志之外的环境副作用进行修改,与 bash 工具和 ACP 后端现有行为完全一致;回放复现请求,而非磁盘。
|
||||
|
||||
## 已验证的接口事实(固定版本)
|
||||
|
||||
两个集成面在本提案之前均已针对固定版本进行了验证——阅读类型与打包源码、运行无需密钥的 spike——而非仅依赖厂商文档。固定版本是验证基线,不是运行时契约:后端不执行运行时版本探测(无 `codex --version` 门禁、无 SDK 版本嗅探)。兼容性在开发时强制执行——每次依赖升级都会针对真实加载路径重跑无密钥套件——在运行时则通过大声失败来保障:协议层面的意外通过 `onError` 结算为 `error`,绝不静默异常。
|
||||
两个集成面在本提案之前均已针对固定版本进行了验证——阅读类型与打包源码、运行无需密钥的 spike——而非仅依赖厂商文档。固定版本是验证基线,不是运行时契约:后端不执行运行时版本探测(无 `codex --version` 门禁、无 SDK 版本嗅探)。兼容性在开发时强制执行——每次依赖升级都会针对真实加载路径重跑无密钥套件——在运行时则通过显式失败来保障:协议层面的意外通过 `onError` 结算为 `error`,绝不静默异常。
|
||||
|
||||
**`@anthropic-ai/claude-agent-sdk` 0.3.202。** `options.env` 会替换子进程环境(不与 `process.env` 合并),恰好满足清洗需求。`settingSources` 默认加载所有文件系统设置——隔离要求显式传入 `[]`。结果子类型为 `success` | `error_during_execution` | `error_max_turns` | `error_max_budget_usd` | `error_max_structured_output_retries`。中止时 SDK 自行逐级加强对 CLI 子进程的终止措施:立即关闭 stdin,约 2 秒后若子进程未退出则发送 SIGTERM(已观察到;无残留进程)——无需自定义 kill 回退。`outputFormat: {type: 'json_schema'}` 和 `agents` 选项已存在,为 seam 的 `outputSchema` 能力和命名 subagent 类型提供了未来着陆点;两者均不在本 Agent Note 范围内。
|
||||
|
||||
@@ -28,7 +28,7 @@ subagent seam([seam Agent Note(agent 决策记录)](../../implemented/feat
|
||||
|
||||
- 生命周期:`initialize{clientInfo}` + `initialized` → `thread/start`(接受 `cwd`、`model`、`sandbox`、`approvalPolicy`、`ephemeral`;未认证即可成功)→ `turn/start{threadId, input:[{type:'text',text}]}` 立即返回一个 `inProgress` 的轮次;终止信号是携带 `Turn{status: completed|interrupted|failed|inProgress, error}` 的 `turn/completed` 通知。
|
||||
- 审批是服务端发起的请求——`item/commandExecution/requestApproval`、`item/fileChange/requestApproval`、`item/permissions/requestApproval`、`item/tool/requestUserInput`、`mcpServer/elicitation/request`——以 `accept`/`decline` 系列决策应答。
|
||||
- 认证:`account/login/start{type:'apiKey', apiKey}` 是一等 RPC,`account/read` 报告 `requiresOpenaiAuth`——且未认证的 `turn/start` 不会快速失败(它会挂在重试中),因此后端必须预检认证状态,并在失败时大声结算为 `error`,而非等待轮次。
|
||||
- 认证:`account/login/start{type:'apiKey', apiKey}` 是一等 RPC,`account/read` 报告 `requiresOpenaiAuth`——且未认证的 `turn/start` 不会快速失败(它会挂在重试中),因此后端必须预检认证状态,并在失败时明确结算为 `error`,而非等待轮次。
|
||||
- 隔离:`CODEX_HOME` 重定向被尊重(`initialize` 响应会回显它,测试可据此断言隔离),`ephemeral: true` 的 thread 不留任何会话文件。
|
||||
|
||||
## 隔离与凭证
|
||||
@@ -37,7 +37,7 @@ subagent seam([seam Agent Note(agent 决策记录)](../../implemented/feat
|
||||
|
||||
## 权限与审批策略
|
||||
|
||||
每个后端不压缩为 ACP 单一的 `permission: allow|reject` 旋钮,而把引擎原生词汇作为配置暴露,并采用保守默认值:Claude Code 获得 `permissionMode`(默认 `default`)以及 `permission: allow|reject`(默认 `reject`),后者作为所有漏过请求的 `canUseTool` 自动应答;Codex 获得 `sandboxMode`(默认 `read-only`)和 `approvalPolicy`(默认 `never`),以及同一个 `permission` 后备值,用来应答仍然到达的审批请求。默认值刻意做到不造成损害(开箱即用的子进程无法写文件);示例演示如何开放权限(`acceptEdits` / `workspace-write`)。机械规则是:每一个服务端发起的请求都由程序迅速结算——枚举出的审批/用户输入/elicitation 请求按配置策略应答,未知请求方法用 JSON-RPC method-not-found 错误响应(绝不保持 pending),未知通知被消费——因此任何子进程请求都不会因等待永远不会到来的应答而卡住轮次。这一版中提示词不会到达人类,与 ACP 一致。
|
||||
每个后端不压缩为 ACP 单一的 `permission: allow|reject` 旋钮,而把引擎原生词汇作为配置暴露,并采用保守默认值:Claude Code 获得 `permissionMode`(默认 `default`)以及 `permission: allow|reject`(默认 `reject`),后者作为所有未被前者处理的请求的 `canUseTool` 自动应答;Codex 获得 `sandboxMode`(默认 `read-only`)和 `approvalPolicy`(默认 `never`),以及同一个 `permission` 后备值,用来应答仍然到达的审批请求。默认值刻意做到不造成损害(开箱即用的子进程无法写文件);示例演示如何开放权限(`acceptEdits` / `workspace-write`)。机械规则是:每一个服务端发起的请求都由程序迅速结算——枚举出的审批/用户输入/elicitation 请求按配置策略应答,未知请求方法用 JSON-RPC method-not-found 错误响应(绝不保持 pending),未知通知被消费——因此任何子进程请求都不会因等待永远不会到来的应答而卡住轮次。这一版中提示词不会到达人类,与 ACP 一致。
|
||||
|
||||
## StopReason 映射
|
||||
|
||||
@@ -49,8 +49,8 @@ Claude Code:`success` → `completed`;`error_max_turns`、`error_during_exec
|
||||
|
||||
依照根 AGENTS.md 规则在每个层级明确命名,并预先消除风险:
|
||||
|
||||
- **无密钥单元/集成测试**:每个后端都镜像 ACP spec 清单(往返和输出累积、每种 stop 映射、两条取消路径、已中止、两种策略下的权限自动应答、未知消息容错、错误命令的 spawn 失败、HMR(热模块替换)提供方清理、导出形状、子进程环境隔离断言和临时目录删除;Codex 另加认证预检失败路径)。Claude Code harness 是通过 `pathToClaudeCodeExecutable` 接入真实 SDK 的脚本化假 `claude` 可执行文件——一个 spike 已在 24ms 内完成端到端无密钥验证(假 CLI 应答一次 `control_request/initialize`,并讲 plain stream-json,约 40 行)。Codex harness 是讲已验证协议格式的脚本化 mock app-server 子进程,沿用 `mock-acp-server.ts` 形状。
|
||||
- **有密钥 e2e 测试**:每个后端的真实引擎执行并由磁盘验证真实文件工作,固定使用开放后的配置,以免验收与不造成损害的默认值冲突——Claude Code 使用 `permissionMode: 'acceptEdits'`,Codex 使用 `sandboxMode: 'workspace-write'` + `approvalPolicy: 'never'`;自跳过会准确报告缺失的是二进制还是 key。CI 没有密钥,因此依照有密钥策略在本地运行。
|
||||
- **无密钥单元/集成测试**:每个后端都镜像 ACP spec 清单(往返和输出累积、每种 stop 映射、两条取消路径、已中止、两种策略下的权限自动应答、未知消息容错、错误命令的 spawn 失败、HMR(热模块替换)提供方清理、导出形状、对子进程环境隔离和临时目录删除的断言;Codex 另加认证预检失败路径)。Claude Code harness 是通过 `pathToClaudeCodeExecutable` 接入真实 SDK 的脚本化假 `claude` 可执行文件——一个 spike 已在 24 ms 内完成端到端无密钥验证(假 CLI 应答一次 `control_request/initialize`,并使用普通 stream-json 通信,约 40 行)。Codex harness 是讲已验证协议格式的脚本化 mock app-server 子进程,沿用 `mock-acp-server.ts` 形状。
|
||||
- **有密钥 e2e 测试**:每个后端的真实引擎执行真实文件操作,并通过磁盘状态进行验证,固定使用开放后的配置,以免验收与不造成损害的默认值冲突——Claude Code 使用 `permissionMode: 'acceptEdits'`,Codex 使用 `sandboxMode: 'workspace-write'` + `approvalPolicy: 'never'`;自跳过会准确报告缺失的是二进制还是 key。CI 没有密钥,因此依照有密钥策略在本地运行。
|
||||
- **快照测试**:以 `TODO(claude-code-subagent-replay)` / `TODO(codex-subagent-replay)` 推迟——即 ACP 后端也推迟的独立回放形状([按会话回放 Agent Note](../../implemented/testing/2026-06-22-subagent-snapshot-replay.md));在此期间由无密钥套件提供确定性覆盖。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
@@ -83,7 +83,7 @@ Claude Code 自身的 Task 工具将 subagent 类型放在模型可见的 schema
|
||||
|
||||
- `codex app-server` 被 CLI 标记为实验性,其 v1/v2 词汇共存;客户端固定 0.142.5、仅实现 v2、对未知方法/通知消费而不崩溃,但未来 codex 升级仍可能迫使返工(每次升级重新生成 schema 并重跑无密钥套件——这是上述「不做运行时版本探测」立场背后的开发时强制执行)。
|
||||
- Claude Code 假 CLI mock 依赖一个内部协议:任何 SDK 升级都必须通过无密钥套件,控制协议的破坏性变更意味着返工 mock(回退方案:上面否决的驱动注入 seam 成为逃生舱口)。
|
||||
- SDK 的 optionalDependencies 每平台约 280MB——已接受,限制在单个后端包内。
|
||||
- SDK 的 optionalDependencies 每平台约 280 MB——已接受,限制在单个后端包内。
|
||||
- SDK 的 SIGKILL 分支(EOF→SIGTERM 之后)未被观察到,信任其实现;e2e 保留无残留进程断言。
|
||||
- Codex 是部署前置条件(无 npm 内置二进制);缺失或不兼容的二进制以大声的 spawn/协议 `error` 呈现,而非版本探测。
|
||||
- 每次运行付出一个全新子进程的代价,且仅最终答案浮出——思考、工具卡片和用量被消费后丢弃;连接池、中间进度浮出、`sendMessage`/`resume`、通过 SDK 的 `outputFormat` 实现 `outputSchema`、以及通过 SDK 的 `agents` 选项实现命名 subagent 类型,均为刻意推迟。
|
||||
- Codex 是部署前置条件(无 npm 内置二进制);缺失或不兼容的二进制会明确报出 spawn/协议 `error`,而非版本探测。
|
||||
- 每次运行付出一个全新子进程的代价,且仅最终答案浮出——思考、工具卡片和用量被消费后丢弃;池化、中间进度浮出、`sendMessage`/`resume`、通过 SDK 的 `outputFormat` 实现 `outputSchema`、以及通过 SDK 的 `agents` 选项实现命名 subagent 类型,均为刻意推迟。
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.md
|
||||
2026-07-08-interactive-side-sessions.md: dfd325babe215782c9c1cbec3fd9f874783af7ab
|
||||
2026-07-08-interactive-side-sessions.zh.md: 9bc9d5c94fdef893134844551a594acc39a99d3b
|
||||
2026-07-08-interactive-side-sessions.zh.md: 3d0c0b1852b5bf93009320b20df073dce206a590
|
||||
|
||||
@@ -10,12 +10,12 @@ Status: proposed
|
||||
|
||||
## 提案
|
||||
|
||||
**侧会话(side session)** 是一个普通的活跃会话,从源会话的最后一个已完成轮次 fork 而来,绑定到自己的 agent(智能体),定位为只读顾问,并能**合并回写**一条精简笔记。
|
||||
**侧会话(side session)** 是一个普通的活跃会话,从源会话的最后一个已完成轮次 fork 而来,绑定到自己的 agent,定位为只读顾问,并能**合并回写**一条精简笔记。
|
||||
|
||||
- **Fork 并绑定:** 以父会话的平衡已完成轮次前缀创建子会话,并在其元数据中标记 `parentSession` 与 `seedLength`。这组合了 `ctx.agents.create({ seed, meta })`;不新增核心服务或会话存储方法。
|
||||
- **顾问定位:** 创建后注入一条插件来源的 `context/message`,告知子会话只做解释,不执行变更或继续任务。保持系统提示词逐字节一致,可在继承的历史上保留提供方的前缀缓存。
|
||||
- **合并回写:** 向子会话请求一条有长度上限的 handback,然后向父会话注入一条插件来源的 `context/message`。父会话的下一次请求在其日志位置看到该消息,保持回放与[请求可重建性](../../implemented/architecture/2026-07-05-reconstructable-requests.md),无需新增会话事件。
|
||||
- **呈现:** 调用方式、会话切换与 handback 渲染属于首个客户端拥有的界面。本 Agent Note(agent 决策记录)仅规定与界面无关的机制。
|
||||
- **合并回写:** 向子会话请求一条有长度上限的 handback,然后向父会话注入一条插件来源的 `context/message`。父会话的下一次请求在日志所记录的位置看到该消息,保持回放与[请求可重建性](../../implemented/architecture/2026-07-05-reconstructable-requests.md),无需新增会话事件。
|
||||
- **呈现:** 调用方式、会话切换与 handback 渲染属于首个客户端拥有的界面。本 Agent Note 仅规定与界面无关的机制。
|
||||
|
||||
回退产品化、会话树视图、面向模型的侧会话工具,以及 `forkName`/`mergedInto` 元数据均不在本 Agent Note 范围内。一次真实适配器 spike 已验证了源日志隔离、继承上下文、多轮子会话交互,以及合并回写在父会话下一轮次中的可见性。
|
||||
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.md
|
||||
2026-07-14-sdk-developer-projects.md: 65d2bf66232993222832eb0f2f4f56cfcf7afd16
|
||||
2026-07-14-sdk-developer-projects.zh.md: 8435a07d9b2545a8f41a1f96743c9c7e6d4daf3d
|
||||
2026-07-14-sdk-developer-projects.zh.md: e57340254fa474357f88c00cbd0923873f09f1a8
|
||||
|
||||
@@ -10,19 +10,19 @@ DeepSeek Harness 通过 Cordis 插件对功能进行组合,但从空目录开
|
||||
|
||||
一次性生成器只能降低首次创建成本。若生成结果隐藏在 preset 或不可编辑的 CLI(命令行界面)内部,高级开发者无法调整插件树、修改 Cordis 插件配置或增加项目特有行为;若创建后的工程完全脱离工具管理,开发者又必须重新承担所有 NPM 依赖和 Cordis 插件配置的一致性工作。
|
||||
|
||||
初始创建和后续配置面对同一组内置功能。两条流程各自维护功能列表、功能选项和 NPM 依赖时,新增 Cordis 插件、NPM 包或调整配置会使二者逐渐分叉。工程还需要一条普通的本地插件开发路径,参与开发、构建和启动流程。
|
||||
初始创建和后续配置面对同一组内置功能。两条流程各自维护功能列表、功能选项和 NPM 依赖时,新增 Cordis 插件、NPM 包或调整 Cordis 插件配置会使二者逐渐分叉。工程还需要一条普通的本地插件开发路径,参与开发、构建和启动流程。
|
||||
|
||||
## 提案
|
||||
|
||||
SDK 创建一个普通、显式且归开发者所有的 TypeScript/Cordis 工程。`cordis.yml` 是唯一的运行时插件树;开发和生产读取同一份文件。工程中的 `package.json`、`cordis.yml`、TypeScript 入口、构建配置和 `plugins/*` 均可直接编辑,SDK 不把它们封装成不可见的 preset。
|
||||
|
||||
开发者产品入口只有 `npm create @deepseek-ai/sdk` 和 `dsh-sdk` 命令。前者负责首次创建,`dsh-sdk config` 在创建后管理 SDK 能识别的内置功能,`dsh-sdk dev`、`dsh-sdk build` 与 `dsh-sdk start` 负责开发、构建和启动;本期不提供 `dsh-sdk create`。create 与 config 使用同一份人工编写的功能定义,因此一项功能的功能选项、NPM 依赖、Cordis 配置项、相关文件和识别规则只有一个来源。功能、功能选项等名词由 [SDK 工程编辑架构](../architecture/2026-07-15-sdk-project-editing-architecture.md) 的术语表定义。
|
||||
开发者产品入口只有 `npm create @deepseek-ai/sdk` 和 `dsh-sdk` 命令。前者负责首次创建,`dsh-sdk config` 在创建后管理 SDK 能识别的内置功能,`dsh-sdk dev`、`dsh-sdk build` 与 `dsh-sdk start` 负责开发、构建和启动;本期不提供 `dsh-sdk create`。create 与 config 使用同一份人工编写的功能定义,因此一项功能的功能选项、NPM 依赖、Cordis 配置项、相关文件和识别规则只有一个来源。[SDK 工程编辑架构](../architecture/2026-07-15-sdk-project-editing-architecture.md) 定义了功能、功能选项等术语。
|
||||
|
||||
SDK 只为功能选择和有限功能选项提供交互,不尝试把任意 Cordis 插件配置变成通用表单。功能选项所需的少量专用输入由所属功能收集;其余 Cordis 插件配置留在 `cordis.yml` 中,并通过注释指明常用改法,由开发者直接修改。
|
||||
|
||||
## 开发者流程
|
||||
|
||||
首次创建按会影响后续问题集合的顺序收集信息:目标目录与 package 身份、模型提供方与凭据、运行接口、内置功能与功能选项、可选本地插件、包管理器,以及是否安装 NPM 依赖并构建。命令参数已提供的答案不重复询问;本期 create 和 config 都要求交互式 TTY,取消创建时不写入目标目录。
|
||||
首次创建按会影响后续问题集合的顺序收集信息:目标目录与包身份、模型提供方与凭据、运行接口、内置功能与功能选项、可选本地插件、包管理器,以及是否安装 NPM 依赖并构建。命令参数已提供的答案不重复询问;本期 create 和 config 都要求交互式 TTY,取消创建时不写入目标目录。
|
||||
|
||||
```sh
|
||||
npm create @deepseek-ai/sdk my-agent
|
||||
@@ -39,13 +39,13 @@ create 还提供一次 `none / plugin / tool` 选择。`plugin` 固定生成 `pl
|
||||
|
||||
## 创建时支持的功能
|
||||
|
||||
下表是本期 create 面向开发者展示的支持集。`required` 始终存在但仍可切换有限功能选项;`default` 在选择树中预选;`optional` 由开发者主动选择。表格说明产品支持集,运行时注册表是实现的事实源。
|
||||
下表是本期 create 面向开发者展示的支持集。`required` 始终存在但仍可切换有限功能选项;`default` 在选择树中预选;`optional` 由开发者主动选择。表格说明产品支持集,运行时注册表是实现的真源。
|
||||
|
||||
| 功能 | create 状态 | 功能选项 | 限制与关系 |
|
||||
|---|---|---|---|
|
||||
| `provider` | required | `deepseek`(默认)/ `custom` | DeepSeek 收集 API key;custom 另收集 base URL,模型名可由 CLI 参数覆盖 |
|
||||
| `app` | required | `tui`(默认)/ `acp` / `embed` | 选择运行接口 |
|
||||
| `spine` | required | `default` | timer、LLM seam、会话存储、系统提示词、工具注册表、agent 注册表,以及 agent loop |
|
||||
| `spine` | required | `default` | timer、LLM(大语言模型)可替换点、会话存储、系统提示词、工具注册表、agent 注册表,以及 agent loop(智能体循环) |
|
||||
| `bash` | required | `local`(默认)/ `sandbox` | 两个功能选项互斥、与运行接口正交,且都安装面向模型的 bash 工具;sandbox 安装本地沙箱提供方和沙箱 bash 后端 |
|
||||
| `persistence` | required | `jsonl`(默认)/ `sqlite` | 每个工程恰好选择一个持久化后端 |
|
||||
| `hmr` | default | `default` | 加载 `@cordisjs/plugin-hmr`;dev 和 start 都启用,使用插件默认配置 |
|
||||
@@ -55,11 +55,11 @@ create 还提供一次 `none / plugin / tool` 选择。`plugin` 固定生成 `pl
|
||||
| `web` | optional | `deepseek`(默认)/ `exa` / `perplexity` / `fetch-only` | 搜索功能选项互斥;Exa/Perplexity 收集各自 API key;建议同时启用 timeout policy |
|
||||
| `subagent` | optional | `spawn`(默认)/ `fork`,可多选 | 本期只提供进程内后端 |
|
||||
| `workflow` | optional | `workerthread` | 要求 subagent 的 `spawn` 功能选项 |
|
||||
| `compact` | optional | `basic` | 使用 SDK 提供的上下文压缩参数 |
|
||||
| `compact` | optional | `basic` | 使用 SDK 提供的上下文压缩(context compaction)参数 |
|
||||
| `hooks` | optional | `claude`(默认)/ `codex`,可多选 | 各功能选项生成独立的可编辑配置文件 |
|
||||
| `guard` | optional | `repeat-tool` | 提供重复工具调用提醒 |
|
||||
| `timeout-policy` | optional | `default` | 对声明超时预算的工具执行统一策略 |
|
||||
| `ask-user` | optional | `default` | 提供 `ask_user_question` 工具;只有 `tui` 可选,因为 ACP 是自动化传输,而 embed 不提供人类交互服务 |
|
||||
| `ask-user` | optional | `default` | 提供 `ask_user_question` 工具;只有 `tui` 可选,因为 ACP(Agent Client Protocol)是自动化传输,而 embed 不提供人类交互服务 |
|
||||
|
||||
`bash` 的两个功能选项都适用于 ACP、TUI 和 embed,不由运行接口决定。sandbox 功能选项不写任何生效的配置键,因而沿用 `dsh-bash-sandbox` 的 `read-only` 默认值;生成的 `cordis.yml` 保留注释示例,开发者可以显式改为 `workspace-write`:
|
||||
|
||||
@@ -76,7 +76,7 @@ create 还提供一次 `none / plugin / tool` 选择。`plugin` 固定生成 `pl
|
||||
|
||||
## 生成工程
|
||||
|
||||
使用默认答案创建 npm 工程时,provider 为 DeepSeek,运行接口为 TUI,bash 为 local,持久化为 JSONL,hmr、fs、todo 与 skill 处于选中状态。初始目录树为:
|
||||
使用默认答案创建 npm 工程时,提供方为 DeepSeek,运行接口为 TUI,bash 为 local,持久化为 JSONL,hmr、fs、todo 与 skill 处于选中状态。初始目录树为:
|
||||
|
||||
```text
|
||||
my-agent/
|
||||
@@ -99,7 +99,7 @@ my-agent/
|
||||
| script | 行为 |
|
||||
|---|---|
|
||||
| `dev` | 运行 `dsh-sdk dev index.ts`,为 TypeScript 和本地 workspace 插件注册开发期解析 |
|
||||
| `build` | 运行 `dsh-sdk build`,调用工程安装的 tsdown 构建根入口和 `plugins/*` package |
|
||||
| `build` | 运行 `dsh-sdk build`,调用工程安装的 tsdown 构建根入口和 `plugins/*` 包 |
|
||||
| `typecheck` | 直接运行 `tsc -b` |
|
||||
| `start` | 运行 `dsh-sdk start index.js`,启动已构建入口且不隐式构建 |
|
||||
| `config` | 运行 `dsh-sdk config`,修改当前工程功能树 |
|
||||
@@ -118,39 +118,39 @@ my-agent/
|
||||
|
||||
`dsh-sdk config` 可以安装缺失功能、启停已安装功能和切换有限功能选项。required 功能不能取消。改变 NPM 依赖后只运行一次项目包管理器安装;安装失败不回滚已经提交的工程文件。
|
||||
|
||||
SDK 只修改功能明确拥有的 Cordis 配置项、配置键、NPM 依赖、`.env.example` 占位和独占文件。同一功能选项的更新保留 Cordis 配置项中的未知配置键;手写或第三方插件只支持按稳定 ID 启停。已知功能被手改成不完整、歧义或无法读取的形状时,`dsh-sdk config` 显示诊断并拒绝自动修改,直到开发者手工修复。
|
||||
SDK 只修改功能明确拥有的 Cordis 配置项、配置键、NPM 依赖、`.env.example` 占位和自有文件。同一功能选项的更新保留 Cordis 配置项中的未知配置键;手写或第三方插件只支持按稳定 ID 启停。已知功能被手改成不完整、歧义或无法读取的形状时,`dsh-sdk config` 显示诊断并拒绝自动修改,直到开发者手工修复。
|
||||
|
||||
一次 config 会话在内存工作区上累计全部修改。Apply 前完成功能关系、资源冲突和文件形状校验,并比较受影响文件与会话打开时的原文;校验失败或检测到外部修改时不写盘。实际写盘开始后不提供跨文件事务回滚。
|
||||
|
||||
## 维护模型
|
||||
|
||||
Builtin 支持集由 SDK 人工策划,不根据 NPM 依赖名称或目录约定自动暴露。一个功能可以组合多个 Cordis 配置项,功能选项可以共享资源,并声明对其他功能或特定功能选项的功能依赖;新增普通功能或功能选项不应要求同时修改 create 和 config 两个命令流程。
|
||||
Builtin 支持集由 SDK 人工策划,不根据 NPM 依赖名称或目录约定自动暴露。一个功能可以组合多个 Cordis 配置项,功能选项可以共享资源,并声明对其他功能或特定功能选项的功能依赖;新增普通功能或功能选项无需同时修改 create 和 config 两个命令流程。
|
||||
|
||||
## 后续工作
|
||||
|
||||
- `dsh-sdk add [package-spec]`:统一本地插件创建与外部 Cordis 插件接入;未指定 package 或仓库来源时创建本地 plugin/tool,指定来源时增加 NPM 依赖和 `cordis.yml` 配置项,来源模型为 GitHub 仓库等扩展保留空间
|
||||
- 非交互 create/config:本期两个流程都要求 TTY,不提供供自动化调用的完整输入合同
|
||||
- `dsh-sdk add [package-spec]`:统一本地插件创建与外部 Cordis 插件接入;未指定包或仓库来源时创建本地插件/工具,指定来源时增加 NPM 依赖和 `cordis.yml` 配置项,来源模型为 GitHub 仓库等扩展保留空间
|
||||
- 非交互 create/config:本期两个流程都要求 TTY,不提供供自动化调用的完整输入契约
|
||||
- 更多功能专用参数输入:本期产品只展示有限功能选项、secret 和少量专用值,不为 Cordis 插件配置提供通用参数界面
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**不可编辑的 preset 或生成器托管工程。** 该方案可以缩短初次创建路径,但会隐藏真实插件树和构建边界,使高级开发者无法直接组合 Cordis 插件,也让项目行为依赖 CLI 版本而不是检入的工程文件。
|
||||
|
||||
**只提供一次性生成器。** 创建后完全依赖手工维护,会让功能依赖、功能选项切换和多文件更新再次分散;共享 registry 的 config 流程为生成工程保留持续管理机制。
|
||||
**只提供一次性生成器。** 创建后完全依赖手工维护,会让功能依赖、功能选项切换和多文件更新再次分散;共享注册表的 config 流程为生成工程保留持续管理机制。
|
||||
|
||||
**为开发和生产维护两份 `cordis.yml`。** 两份插件树会使开发成功无法证明生产加载相同功能;dev 只增加 TypeScript 与本地 workspace 解析,运行配置保持唯一。
|
||||
|
||||
**为任意 Cordis 插件配置生成通用表单。** Cordis 插件配置包含嵌套结构、表达式和插件特有语义,通用表单会形成第二套不完整 schema。SDK 只管理有限功能选项和专用 secret,复杂配置继续由开发者直接编辑。
|
||||
|
||||
**使用私有协议发现本地插件。** 普通 package manager workspace、根 NPM 依赖、TypeScript references 和 Cordis 配置项已能表达完整关系;额外发现协议会创造只能由 SDK 理解的隐藏状态。
|
||||
**使用私有协议发现本地插件。** 普通包管理器 workspace、根 NPM 依赖、TypeScript references 和 Cordis 配置项已能表达完整关系;额外发现协议会创造只能由 SDK 理解的隐藏状态。
|
||||
|
||||
**在现有工程中提供 `dsh-sdk create`。** create 已能生成一种可编辑的本地插件骨架,后续插件可以沿用普通 workspace 和 Cordis 机制手工添加;再提供同构命令会增加第二条脚手架产品面,却不增加新的组合功能。
|
||||
**在现有工程中提供 `dsh-sdk create`。** create 已能生成一个可编辑的本地插件骨架,后续插件可以沿用普通 workspace 和 Cordis 机制手工添加;再提供并行命令会增加第二条脚手架产品面,却不增加新的组合功能。
|
||||
|
||||
**把每个新 Cordis 插件自动暴露为 builtin。** package 无法说明多个插件如何组合成一项产品功能,也无法推导互斥关系、功能依赖、secret、接口适用性和安全限制;支持集需要人工策划,自动化只适合检查候选是否完成分类。
|
||||
**把每个新 Cordis 插件自动暴露为 builtin。** 包无法说明多个插件如何组合成一项产品功能,也无法推导互斥关系、功能依赖、secret、接口适用性和安全限制;支持集需要人工策划,自动化只适合检查候选是否完成分类。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- `npm create @deepseek-ai/sdk` 按本文顺序收集项目身份、provider、interface、功能、可选本地插件、包管理器和安装选择,并在取消时保持目标路径不存在
|
||||
- `npm create @deepseek-ai/sdk` 按本文顺序收集项目身份、提供方、接口、功能、可选本地插件、包管理器和安装选择,并在取消时保持目标路径不存在
|
||||
- 默认 npm 工程具有本文目录树和 `dev`、`build`、`typecheck`、`start`、`config` scripts,且 dev/start 使用同一份 `cordis.yml`
|
||||
- create 展示本文功能及功能选项;`bash` 的 local/sandbox 二选一且默认 local,sandbox Cordis 配置项保留可编辑的注释配置示例;HMR 默认选中并同时由 dev/start 加载
|
||||
- create 的 `plugin` 或 `tool` 选择至多生成一个固定名称的本地插件,并原子更新插件文件与根工程关系;本期不提供 `dsh-sdk create`
|
||||
@@ -160,8 +160,8 @@ Builtin 支持集由 SDK 人工策划,不根据 NPM 依赖名称或目录约
|
||||
|
||||
## 风险
|
||||
|
||||
- 开发者可以把 builtin 手改成 registry 无法识别的形状;SDK 选择停止自动化而不是猜测并覆盖配置
|
||||
- 开发者可以把 builtin 手改成注册表无法识别的形状;SDK 选择停止自动管理该功能而不是猜测并覆盖配置
|
||||
- 多文件写入前的校验和外部修改检测不能提供写入阶段的事务回滚;I/O 中途失败可能留下需要人工修复的部分提交
|
||||
- sandbox 功能选项依赖目标平台存在可用的本地沙箱后端;后端不可用时必须 fail closed,不能退回无沙箱执行
|
||||
- HMR 在生产启动中也保持文件 watcher 和热重载行为;这是显式插件选择的结果,不是仅限开发环境的隐式服务
|
||||
- `.env` 的仅追加策略会保留已经不用的凭据,SDK 不判断这些用户数据何时可以安全删除
|
||||
- `.env` 的仅追加策略会保留已经不用的凭据,SDK 不判断这些用户拥有的密钥数据何时可以安全删除
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-06-11-api-extractor-reports.md
|
||||
2026-06-11-api-extractor-reports.md: 03f512992fe87ea3d0f8d51a1772ce1ec89a5c0d
|
||||
2026-06-11-api-extractor-reports.zh.md: a8180124c5e5402dce3c28c1bd8c54219d5b68fc
|
||||
2026-06-11-api-extractor-reports.zh.md: 78c8ad22a4b1a38d431e19c0e73d41a02f62b1cd
|
||||
|
||||
@@ -4,7 +4,7 @@ Status: proposed
|
||||
|
||||
[English](2026-06-11-api-extractor-reports.md) | 中文
|
||||
|
||||
> 从最初的「doc-sync(文档同步门禁)与 API 报告」Agent Note(agent 决策记录)中拆出(首次提出于 2026-06-11)。第 1 至第 2 部分(文档块类型检查、事件分类体系校验)已交付,见 [doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)。本文是被推迟的第 3 部分,作为独立提案保留。
|
||||
> 从最初的「doc-sync(文档同步门禁)与 API 报告」Agent Note中拆出(首次提出于 2026-06-11)。第 1 至第 2 部分(文档块类型检查、事件分类体系校验)已交付,见 [doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)。本文是被推迟的第 3 部分,作为独立提案保留。
|
||||
|
||||
## 问题
|
||||
|
||||
@@ -12,11 +12,11 @@ Status: proposed
|
||||
|
||||
## 提案
|
||||
|
||||
使用 api-extractor(或 `tsc --emitDeclarationOnly` 加一份规范化的公开接口导出)为每个包(package)生成一份签入仓库的 `etc/<pkg>.api.md`;CI 在重新生成结果与已签入报告不一致时失败。这样,每一次公开 API 变更都会成为评审者(或评审 agent(智能体))必须看到的一行 diff。
|
||||
使用 api-extractor(或 `tsc --emitDeclarationOnly` 加一份规范化的公开 API 清单)为每个包生成一份签入仓库的 `etc/<pkg>.api.md`;CI 在重新生成结果与已签入报告不一致时失败。这样,每一次公开 API 变更都会成为评审者(或评审 agent(智能体))必须看到的一行 diff。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
**`tsc --emitDeclarationOnly` 加规范化的公开接口导出**:如果 api-extractor 过于笨重,这是更轻量的机制;两者都能满足提案所需的「签入仓库、可 diff」的报告形态。
|
||||
**`tsc --emitDeclarationOnly` 加规范化的公开 API 清单**:如果 api-extractor 过于笨重,这是更轻量的机制;两者都能满足提案所需的「签入仓库、可 diff」的报告形态。
|
||||
|
||||
## 验收标准
|
||||
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-06-11-architectural-conformance.md
|
||||
2026-06-11-architectural-conformance.md: f7cb0d7397d4e03df225f68417da43b1fec8de62
|
||||
2026-06-11-architectural-conformance.zh.md: aa25ef6d2772642885ef268bd548fc6dad40d3cf
|
||||
2026-06-11-architectural-conformance.zh.md: c82297fcbe02feed4cd04bc60730a3494b7faa4d
|
||||
|
||||
@@ -6,15 +6,15 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
目前有两项架构保证仅存在于行文中:(1)没有任何组件依赖具体的 loop 包(package)([微内核承诺](../../implemented/architecture/2026-06-11-microkernel-event-taxonomy.md));(2)每个 LlmAdapter 都正确遵循分片协议。二者都应由机制强制执行([质量门禁原则](../../implemented/process/2026-06-11-quality-gates.md))。
|
||||
目前有两项架构保证仅存在于行文中:(1)没有任何组件依赖具体的 agent loop(智能体循环)包([微内核承诺](../../implemented/architecture/2026-06-11-microkernel-event-taxonomy.md));(2)每个 LlmAdapter 都正确遵循分片协议。二者都应由机制强制执行([质量门禁原则](../../implemented/process/2026-06-11-quality-gates.md))。
|
||||
|
||||
## 提案
|
||||
|
||||
**dependency-cruiser** 配合以下规则:
|
||||
|
||||
- `packages/*`(除 agent-loop(智能体循环)自身的 tests 和 examples/ 外)禁止导入 `@deepseek-ai/dsh-agent-loop`。
|
||||
- `packages/*`(除 agent-loop 自身的测试和 examples/ 外)禁止导入 `@deepseek-ai/dsh-agent-loop`。
|
||||
- 禁止跨包深层导入(`@deepseek-ai/dsh-*/src/...` 路径)——只允许使用公开入口点。
|
||||
- packages/ 内禁止导入循环。
|
||||
- packages/ 内禁止出现循环依赖。
|
||||
- `vendor/*` 禁止从 `packages/*` 导入。
|
||||
- 分层:dsh-llm 不导入其他 dsh 包;dsh-session 仅导入 dsh-llm;以此类推(packages/README.md 中的依赖表,强制执行)。
|
||||
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-06-11-supply-chain-and-vendor-drift.md
|
||||
2026-06-11-supply-chain-and-vendor-drift.md: a27ae64556dc7366279824f1480e5681b1e86bf1
|
||||
2026-06-11-supply-chain-and-vendor-drift.zh.md: 25c27650709faf1a462ce9779ee6f0a909746311
|
||||
2026-06-11-supply-chain-and-vendor-drift.zh.md: b52748e4b4c92a161c5b3ff49c079ff909d11c01
|
||||
|
||||
@@ -6,14 +6,14 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
vendor manifest(元数据清单)(见[引入 vendor 的决策](../../implemented/process/2026-06-11-vendor-cordis-as-source.md))在提交时仅在*正向*强制执行(vendor 变更 ⇒ manifest 更新),但没有任何机制验证 manifest 的*声明*:即 vendor/ 确实等于上游指定 SHA 的内容加上所记录的修改。此外,少量真正的 npm 依赖也没有安全公告监控或更新节奏。
|
||||
vendor manifest(元数据清单)(见[引入 vendor 的决策](../../implemented/process/2026-06-11-vendor-cordis-as-source.md))在提交时仅在*正向*强制执行(vendor 变更 ⇒ manifest 更新),但没有任何机制验证 manifest 的*声明*:即 vendor/ 确实等于上游指定 SHA 的内容加上所记录的修改。此外,少量真正的 NPM 依赖也没有安全公告监控或更新节奏。
|
||||
|
||||
## 提案
|
||||
|
||||
1. **Vendor 漂移检查**(夜间 CI):以 manifest 中记录的 SHA 浅克隆上游仓库,复制对应的包(package)源码,与 `vendor/*/src` 做 diff。除非 diff 与已记录的本地修改一致(每项修改以签入的 patch 文件保存——日志条目从行文描述变为可验证的产物),否则任务失败。
|
||||
1. **Vendor 漂移检查**(夜间 CI):以 manifest 中记录的 SHA 浅克隆上游仓库,复制对应的包源码,与 `vendor/*/src` 做 diff。除非 diff 与已记录的本地修改一致(每项修改以签入的 patch 文件保存——日志条目从行文描述变为可验证的产物),否则任务失败。
|
||||
2. **依赖安全公告**:对 lockfile 运行 osv-scanner(或 `pnpm audit`),按计划定期执行,并在涉及 lockfile 变更的 PR(Pull Request)上触发。
|
||||
3. **许可证清单**:一个脚本断言每个 vendor 包都携带其 LICENSE 文件,且 package.json 的 `license` 字段与 vendor/README.md 中的清单一致(我们混合了 vendor 的 MIT 与自有的 BSD-3)——作为 CI 步骤运行。
|
||||
4. **Renovate**(或定时 agent(智能体)任务)以小 PR 的形式提议 npm 依赖更新,这些 PR 走完整门禁套件;vendor 包不在其列(它们的更新遵循 manifest 同步流程,理想情况下是半自动化的 agent 工作流:拉取上游、重新应用 patch、运行门禁、以更新后的 manifest 表格开 PR)。
|
||||
4. **Renovate**(或定时 agent(智能体)任务)以小 PR 的形式提议 NPM 依赖更新,这些 PR 走完整门禁套件;vendor 包不在其列(它们的更新遵循 manifest 同步流程,理想情况下是半自动化的 agent 工作流:拉取上游、重新应用 patch、运行门禁、以更新后的 manifest 表格开 PR)。
|
||||
|
||||
## 计划
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-06-20-discover-package-inventory.md
|
||||
2026-06-20-discover-package-inventory.md: 7de865e43f87f0e41fad43b3786b9825509e9d50
|
||||
2026-06-20-discover-package-inventory.zh.md: 00195981a1f8036121e3895a0ff8ac342f54dff9
|
||||
2026-06-20-discover-package-inventory.zh.md: fed3d9dd8740b2dc22434f8af7e1e8c025502f61
|
||||
|
||||
@@ -6,15 +6,15 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
包(package)与门禁清单在 TypeScript project references、包文档、CI 描述和 Knip 覆盖项中反复出现。大多数只是重述包布局、manifest(元数据清单)数据或聚合命令内容。因此每新增一个包都会产生本可避免的同步点。
|
||||
包与门禁清单在 TypeScript project references、包文档、CI 描述和 Knip 覆盖项中反复出现。大多数只是重述包布局、manifest(元数据清单)数据或聚合命令内容。因此每新增一个包都会产生本可避免的同步点。
|
||||
|
||||
[包层级结构](../../archived/architecture/2026-06-20-package-hierarchy.md)已经手动消除了其中若干:`scripts/publint-all.ts` 现在从 `packages/<group>/<pkg>` 布局推导列表,两份 `tsconfig` 的 `paths` 映射也合并为一个 `@deepseek-ai/dsh-*` 通配符。剩下的是无法用 glob 消除的清单,主要是聚合配置(`tsconfig.host.json`、`tsconfig.client.json`)的 project `references`——TypeScript 要求它们是显式数组(没有通配符形式)。
|
||||
[包层级结构](../../archived/architecture/2026-06-20-package-hierarchy.md)已经手动消除了其中若干:`scripts/publint-all.ts` 现在从 `packages/<group>/<pkg>` 布局推导列表,两份 `tsconfig` 的 `paths` 映射也合并为一个 `@deepseek-ai/dsh-*` 通配符。剩下的是无法用 glob 消除的清单,主要是聚合配置(`tsconfig.host.json`、`tsconfig.client.json`)中的项目引用(`references`)——TypeScript 要求它们是显式数组(没有通配符形式)。
|
||||
|
||||
当静态列表编码的是策略时,它们是合理的;当它们只是重复 `package.json`、workspace glob 或包层级结构中已有的 manifest 数据或布局事实时,就是不必要的摩擦。
|
||||
|
||||
## 提案
|
||||
|
||||
让剩余的包与门禁清单可被发现。一个唯一的权威来源,即 `packages/<group>/<pkg>` 层级结构加上包 manifest,应当驱动聚合配置的 `references`、模块图以及任何全量包列表,并配合一个生成加校验步骤(沿用现有的 `gen-module-graph` / `gen-cordis-catalog` 模式:生成器写出产物,`--check` 模式在 `hygiene` / `doc-sync`(文档同步门禁)中发现已提交副本陈旧时失败)。模块图生成已经在读取包 manifest。`doc-sync` 应当成为定义并打印其子门禁的唯一命令,文档链接到该命令,而非重述第二份列表。
|
||||
让剩余的包与门禁清单可被发现。唯一真源,即 `packages/<group>/<pkg>` 层级结构加上包 manifest,应当驱动聚合配置的 `references`、模块图以及任何全量包列表,并配合一个生成加校验步骤(沿用现有的 `gen-module-graph` / `gen-cordis-catalog` 模式:生成器写出产物,`--check` 模式在 `hygiene` / `doc-sync`(文档同步门禁)中发现已提交副本陈旧时失败)。模块图生成已经在读取包 manifest。`doc-sync` 应当成为定义并打印其子门禁的唯一命令,文档链接到该命令,而非重述第二份列表。
|
||||
|
||||
层级结构不需要编码关于包的所有事实,但应当编码宽泛的维护策略:core/product 包、集成包、能力 seam 包与 support/test/example 包不应在脚本能区分它们之前先要求一份手工维护的例外列表。
|
||||
|
||||
@@ -22,7 +22,7 @@ Status: proposed
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 聚合配置的 project `references` 由层级结构生成(生成器输出它们;`--check` 门禁在提交副本陈旧时报错),而非手工维护。
|
||||
- 聚合配置的项目引用(`references`)由层级结构生成(生成器输出它们;`--check` 门禁在提交副本陈旧时报错),而非手工维护。
|
||||
- 新增一个包时,不需要为任何门禁编辑静态包列表。
|
||||
- 文档描述真源,而非重复生成的清单。
|
||||
- CI 调用聚合命令,由这些命令自行管理其子门禁列表。
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-07-13-human-review-skill-maintenance.md
|
||||
2026-07-13-human-review-skill-maintenance.md: 76391bd110b7a86b194a9340ffcf7cc4602d1d2e
|
||||
2026-07-13-human-review-skill-maintenance.zh.md: 67d68d07cc7f467a310b64d833a28da6f04bcc8e
|
||||
2026-07-13-human-review-skill-maintenance.zh.md: 4491983063c5620cc4f92812126983af7232f60a
|
||||
|
||||
@@ -34,17 +34,17 @@ flowchart TD
|
||||
|
||||
### 采纳证据
|
||||
|
||||
每条反馈都携带稳定的来源 ID 和有界的变更证据。评审人的 `commit_id` 仍属于该 PR 时(强制推送场景无法确认即排除),工具会选择 committer 时间戳严格早于反馈的最新 PR commit 作为基线,而不是采用评审人点击的 commit,因为后者可能更旧。工具绝不会直接比较该基线与落地 merge:这种 diff 会混入不断前移的目标分支中的无关变更。相反,它会向采纳评审人提供两份 PR 专用 patch 快照。令 `B` 为反馈基线,`T` 为落地 merge 的目标父级,`M` 为落地 merge。反馈时快照是从 `merge-base(B, T)` 至 `B` 的树 diff;最终快照是从 `T` 至 `M` 的树 diff。因此,目标分支专有变更不会出现在任一 PR patch 中,而反馈后加入 PR 的变更只会出现在最终快照中。遭到强制推送的评审、早于全部现存 PR commit 的反馈,以及无法重建目标父级的落地形态,会在任何评审人看到之前确定性地归类为 `unclear`。合并状态、已解决讨论串、作者回复「已修复」或同文件编辑只是上下文,不是采纳证明;PR 作者自己的评论绝不会进入适配器,因为它们不可能构成对作者自身意见的采纳。
|
||||
每条反馈都携带稳定的来源 ID 和有界的变更证据。评审人的 `commit_id` 仍属于该 PR 时(强制推送场景无法确认即排除),工具会选择 committer 时间戳严格早于反馈的最新 PR commit 作为基线,而不是采用评审人点击的 commit,因为后者可能更旧。工具绝不会直接比较该基线与落地 merge:这种 diff 会混入不断前移的目标分支中的无关变更。相反,它会向采纳评审人提供两份 PR 专用 patch 快照。令 `B` 为反馈基线,`T` 为落地 merge 的目标父级,`M` 为落地 merge。反馈时快照是从 `merge-base(B, T)` 至 `B` 的树 diff;最终快照是从 `T` 至 `M` 的树 diff。因此,目标分支专有变更不会出现在任一 PR patch 中,而反馈后加入 PR 的变更只会出现在最终快照中。关联 commit 已因强制推送而不再属于该 PR 的评审、早于全部现存 PR commit 的反馈,以及无法重建目标父级的落地形态,会在任何评审人看到之前确定性地归类为 `unclear`。合并状态、已解决讨论串、作者回复「已修复」或同文件编辑只是上下文,不是采纳证明;PR 作者自己的评论绝不会进入适配器,因为它们不可能构成对作者自身意见的采纳。
|
||||
|
||||
### 双评审人分类与起草
|
||||
|
||||
两个独立配置的评审适配器,会从来源(`human-authored`、`forwarded-automation` 或 `unclear`)和采纳情况(`adopted`、`rejected` 或 `unclear`)两个维度,对每个符合条件的条目进行分类。只有两个适配器都判定为 `human-authored` 加 `adopted` 的条目才会继续。采纳集合随后会针对当前 skill 接受第二次独立分类:候选项、已经覆盖、实现专用或并非反馈。单个条目即可符合要求,不要求重复出现。意见分歧会得到一次有界的重新评估;如果仍未解决,则继续保留在运行产物中。单个批次的适配器输出如果未通过 schema 或 id 校验,系统会在批次层按不采纳处理:其中的每条反馈都标记为 unclear 并路由到 `excluded`,而不是中止整次运行;有问题的原始输出会保存在该次运行的私有产物中,供调试使用。如果任一适配器在某项操作的任何非空批次中都没有返回有效结果,运行会以非零状态退出并发出失败记录,而不会报告「没有候选项」。
|
||||
|
||||
主适配器只根据结构化的共同指引起草,绝不接收原始评审文本。根据适配器作者的契约,它保持无工具且只读:返回完整的候选文件内容,由工具校验后写入唯一目标。两个适配器随后评审同一份完整 skill diff;阻塞性问题会进入有界修订循环,而且两者必须批准同一版修订。工具会在运行文档和 lint 检查前,以及报告成功前,再次拒绝暂存改动和目标 skill 之外的编辑,因此检查或并发进程无法通过添加其他路径混入。失败时,工具使用尽力而为的比较并交换恢复自身写入,避免覆盖维护者的并发编辑。成功时,它保存一份候选资料包,其中包含源 `origin/master` commit、源 skill blob ID、已评审 diff、完整候选文件、源反馈 ID 与 URL、落地证据范围、适配器判定和检查结果;它绝不提交、推送、打开或合并 PR。
|
||||
主适配器只根据结构化的共同指引起草,绝不接收原始评审文本。根据适配器作者的契约,它保持无工具且只读:返回完整的候选文件内容,由工具校验后写入唯一目标。两个适配器随后评审同一份完整 skill diff;阻塞性问题会进入有界修订循环,而且两者必须批准同一版修订。工具会在运行文档和 lint 检查前,以及报告成功前,再次拒绝暂存改动和目标 skill 之外的编辑,因此检查或并发进程无法通过添加其他路径混入。失败时,工具使用尽力而为的比较并交换回滚自身写入,以免覆盖维护者的并发编辑。成功时,它保存一份候选资料包,其中包含源 `origin/master` commit、源 skill blob ID、已评审 diff、完整候选文件、源反馈 ID 与 URL、落地证据范围、适配器判定和检查结果;它绝不提交、推送、打开或合并 PR。
|
||||
|
||||
### 评审适配器协议
|
||||
|
||||
每个私有可执行文件从 stdin 接收有字节上限、带版本的 JSON 请求,并在 stdout 返回有字节上限且符合 schema 的 JSON。两个评审命令解析为逐字节相同的可执行文件时,工具拒绝运行;这是机械性的最低标准,保证主适配器与次适配器由独立提供方或模型驱动,仍是部署运维方的责任。`access` 与 `tools` 字段是适配器作者承担的契约标记,不是 OS 沙箱:评审子进程在清理后的环境中 spawn,其 `cwd` 指向私有运行目录而非仓库根目录;反馈包装在带随机数的 `<untrusted-feedback nonce="…">` 块中,每个提示词都会要求模型把它视为数据;128 位随机数防止不受信任的正文伪造结束标签。每个子进程都采用有界、感知中止的进程树清理。适配器作者把每项操作实现为纯只读推理(inference);即使 `edit` 操作也只会在 JSON 中返回完整候选内容,由工具校验后写入唯一目标。每个生产 `git`/`gh`/检查命令同样在清理后的环境中 spawn,避免 pre-push 钩子的路由变量无提示地重定向维护工具。候选写入与失败回滚会针对最近一次写入内容使用尽力而为的比较并交换;回滚还会取消暂存目标,避免由适配器或检查暂存的候选项在失败运行后遗留并进入之后的 commit。
|
||||
每个私有可执行文件从 stdin 接收有字节上限、带版本的 JSON 请求,并在 stdout 返回有字节上限且符合 schema 的 JSON。两个评审命令解析为逐字节相同的可执行文件时,工具拒绝运行;这只是最低限度的机械检查;确保主适配器与次适配器由独立提供方或模型驱动,仍是部署运维方的责任。`access` 与 `tools` 字段是适配器作者承担的契约标记,不是 OS 沙箱:评审子进程在清理后的环境中 spawn,其 `cwd` 指向私有运行目录而非仓库根目录;反馈包装在带随机数的 `<untrusted-feedback nonce="…">` 块中,每个提示词都会要求模型把它视为数据;128 位随机数防止不受信任的正文伪造结束标签。每个子进程都采用有界、感知中止的进程树清理。适配器作者把每项操作实现为纯只读推理(inference);即使 `edit` 操作也只会在 JSON 中返回完整候选内容,由工具校验后写入唯一目标。每个生产 `git`/`gh`/检查命令同样在清理后的环境中 spawn,避免 pre-push 钩子的路由变量无提示地重定向维护工具。候选写入与失败回滚会针对最近一次写入内容使用尽力而为的比较并交换;回滚还会取消暂存目标,避免由适配器或检查暂存的候选项在失败运行后遗留并进入之后的 commit。
|
||||
|
||||
### 推广契约
|
||||
|
||||
@@ -52,7 +52,7 @@ flowchart TD
|
||||
|
||||
### 机制所在位置
|
||||
|
||||
工具源码、适配器二进制文件、提供方凭据和预期的每日调度器保存在维护者机器上,不会提交到本仓库。本文规定协议,参考实现属于私有基础设施。该机制只服务于由单个运维方维护的一项 skill,因此,让机制编辑持续接受仓库评审的成本高于来源可追溯性的收益。如果该机制将来移交给第二位维护者,移交工作需要一篇后续 Agent Note(agent 决策记录)来修订本决策;任何接手者都应从运维文档 [docs/cookbook/maintaining-dsh-code-review.md](../../../../docs/cookbook/maintaining-dsh-code-review.md) 入手。
|
||||
工具源码、适配器二进制文件、提供方凭据和预期的每日调度器保存在维护者机器上,不会提交到本仓库。本文规定协议,参考实现属于私有基础设施。该机制只服务于由单个运维方维护的一项 skill,因此,持续通过仓库评审审查机制改动的成本高于来源可追溯性的收益。如果该机制将来移交给第二位维护者,移交工作需要一篇后续 Agent Note来修订本决策;任何接手者都应从运维文档 [docs/cookbook/maintaining-dsh-code-review.md](../../../../docs/cookbook/maintaining-dsh-code-review.md) 入手。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md
|
||||
2026-07-26-remove-packed-session-fixture-migrator.md: 0a29ef98828ac07d291392d637b0508937c9a9a6
|
||||
2026-07-26-remove-packed-session-fixture-migrator.zh.md: 64b994855a7e92d5b0922884b6c66df1b82b6d90
|
||||
2026-07-26-remove-packed-session-fixture-migrator.zh.md: 0ad63c1dcfd8340da47a50ca7445aa11a4eb0632
|
||||
|
||||
@@ -12,11 +12,11 @@ Status: proposed
|
||||
|
||||
## 提案
|
||||
|
||||
最新清单确认不再有任何开放 PR(Pull Request)需要转换会话格式 JSONL 后,移除临时 CLI `scripts/migrate-packed-session-fixtures.ts`,以及根包(package)提供的 `migrate:packed-session-fixtures` 命令。在同一变更中,移除测试政策、ACP 快照 README 和已实现打包行 Agent Note(agent 决策记录)中指向该过渡命令的链接,并将 `scripts/session-fixture-layout.snapshot.ts` 中仅适用于该命令的修复指引替换为与具体命令无关的规范布局指引。
|
||||
在实时清单确认不再有任何开放 PR(Pull Request)需要转换会话格式 JSONL 后,移除临时 CLI `scripts/migrate-packed-session-fixtures.ts`,以及根包提供的 `migrate:packed-session-fixtures` 命令。在同一变更中,移除测试政策、ACP 快照 README 和已实现打包行 Agent Note中指向该过渡命令的链接,并将 `scripts/session-fixture-layout.snapshot.ts` 中仅适用于该命令的修复指引替换为与具体命令无关的规范布局指引。
|
||||
|
||||
保留 `scripts/session-fixture-layout.ts`、其单元测试和 `scripts/session-fixture-layout.snapshot.ts`。它们定义并强制执行永久规范布局;只有面向分支的写入器是临时机制。
|
||||
|
||||
移除命令前,每个受影响分支都要合并当前 `master`,运行一次迁移器,单独提交由此产生的仅 fixture 重写,并验证仓库级快照布局检查通过。已关闭或被取代的分支无需迁移。
|
||||
移除命令前,每个受影响分支都要合并当前 `master`,运行一次迁移器,将由此产生且仅包含 fixture 重写的改动单独提交,并验证仓库级快照布局检查通过。已关闭或被取代的分支无需迁移。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -24,15 +24,15 @@ Status: proposed
|
||||
|
||||
**随 CLI 一同移除规范布局转换模块。** 该模块不是过渡残留:快照 CI 使用它发现未来 fixture、解码混合物理记录,并与规范打包表示进行比较。移除该模块也会移除强制机制。
|
||||
|
||||
**打包行进入 `master` 后立即删除命令。** 较旧的开放分支在重新定向后,只能使用临时脚本或手动重新生成快照,这会增加冲突风险,也会让解码事件保真度更难评审。
|
||||
**打包行进入 `master` 后立即删除命令。** 较旧的开放分支在调整目标分支后,只能使用临时脚本或手动重新生成快照,这会增加冲突风险,也会让解码事件保真度更难评审。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 最新开放 PR 清单未发现任何仍依赖临时迁移命令处理会话格式 JSONL 改动的分支。
|
||||
- 实时开放 PR 清单未发现任何仍依赖临时迁移命令处理会话格式 JSONL 改动的分支。
|
||||
- 临时 CLI、根包命令、所有分支收敛链接与仅适用于该命令的门禁诊断均不存在;永久规范布局转换器、单元测试和快照检查仍然保留。
|
||||
- `pnpm run test:snapshot`、`pnpm run doc-sync`、lint 和空白校验在没有临时命令的情况下通过。
|
||||
- 当前文档仅描述打包默认值和永久规范布局强制机制。
|
||||
|
||||
## 风险
|
||||
|
||||
若开放分支清单不完整,命令消失后,贡献者可能会受困于大规模非打包 fixture 冲突。因此,移除操作取决于实时 PR 证据,而不是经过的时间。保留命令过久的运维成本较低,但会模糊哪一种机制才是永久机制。
|
||||
若开放分支清单不完整,命令消失后,贡献者可能会陷入大规模的非打包 fixture 冲突。因此,移除操作取决于实时 PR 证据,而不是经过的时间。保留命令过久的运维成本较低,但会模糊哪一种机制才是永久机制。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md
|
||||
2026-07-04-prune-dead-core-spine-surface.md: 473f655ab0944b43f9b1193413801eb7a80286d8
|
||||
2026-07-04-prune-dead-core-spine-surface.zh.md: 00c6f7acca403c937ff765b6d73d3d0fa0c3153a
|
||||
2026-07-04-prune-dead-core-spine-surface.zh.md: c9dc5ff1657c323d87bb392101efcd22af6b3527
|
||||
|
||||
@@ -6,26 +6,26 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
若干包(package)根导出、结果字段和便利方法没有生产消费方。它们之所以存活,要么是因为测试通过公开入口导入了内部实现,要么是因为某个类型预期了一个从未出现的调用者。每一项单独看都很小,但合在一起,它们扩大了 SDK 契约、生成的 catalog、文档和回归矩阵,却没有支撑任何已交付的路径。
|
||||
若干包根导出、结果字段和便利方法没有生产消费方。它们之所以存活,要么是因为测试通过公开入口导入了内部实现,要么是因为某个类型预期了一个从未出现的调用者。每一项单独看都很小,但合在一起,它们扩大了 SDK 契约、生成的 catalog、文档和回归矩阵,却没有支撑任何已交付的路径。
|
||||
|
||||
生产语料库是 `packages/*/*/src`、示例源码/配置和运行时脚本。测试、包 README 和 Agent Note(agent 决策记录)行文是发布的证据,但不是固定调用者。`cordis_inspect` 使 `packages/cordis/tool-cordis/src/api-catalog.ts` 对模型可见,`cordis_mount` 可以通过受保护的真实服务代理调用注入的服务,因此 catalog 中的服务方法和返回形状是真正的动态产品接口。下表因此区分「没有固定的仓库调用者」与「不可达」:涉及 catalog 词汇的行有意收缩模型编写的 mount 能发现和调用的内容,而包根实现辅助函数并不通过该服务门面可达。精确符号搜索得出以下清单:
|
||||
生产语料库是 `packages/*/*/src`、示例源码/配置和运行时脚本。测试、包 README 和 Agent Note行文是发布的证据,但不是固定调用者。`cordis_inspect` 使 `packages/cordis/tool-cordis/src/api-catalog.ts` 对模型可见,`cordis_mount` 可以通过受保护的真实服务代理调用注入的服务,因此 catalog 中的服务方法和返回形状是真正的动态产品接口。下表因此区分「没有固定的仓库调用者」与「不可达」:涉及 catalog 词汇的行有意收缩模型编写的 mount 能发现和调用的内容,而包根实现辅助函数并不通过该服务门面可达。精确符号搜索得出以下清单:
|
||||
|
||||
| 接口 | 生产证据 | 简化方式 |
|
||||
| --- | --- | --- |
|
||||
| `SurfaceManager.invalidate()` | 只有其单元测试调用它;seeding 在惰性创建的 manager 存在之前就已完成,且会话从不替换其日志引用。 | 删除它及其不可能触发的整体替换契约。 |
|
||||
| `ToolExecutionResult.callId` | 每个钩子已经接收不可变的 `ToolExecution`;循环和 ACP(Agent Client Protocol)通过调用/会话事件关联。没有消费方读取这个重复的结果字段。 | 移除该字段、复制/不匹配守卫,以及证明该重复不可能不一致的测试。 |
|
||||
| `ReactLoopAgent` 根导出 | 包外的命名导入都是测试;生产代码面向 `Agent` 编程,通过 `ctx.agents` 创建/恢复。 | 返回/接口类型为 `Agent`,将具体循环类改为包内部;保留有意设计的同步、仅配置的 `AgentLoop.create()` 路径。 |
|
||||
| `workflow-workerthread` 的 protocol/runtime/session 再导出与命名的 `WorkerWorkflowEngine` | 每个包名消费方都使用默认引擎;工作流 Agent Note 已将 worker 协议格式(wire format)定义为私有。 | 保留默认插件类/配置契约;移除重复的命名类导出,将协议模块保持为源码私有。 |
|
||||
| `ReactLoopAgent` 根导出 | 包外的命名导入都是测试;生产代码面向 `Agent` 编程,通过 `ctx.agents` 创建/恢复。 | 将返回类型和接口类型设为 `Agent`,将具体循环类改为包内部;保留有意设计的同步、仅配置的 `AgentLoop.create()` 路径。 |
|
||||
| `workflow-workerthread` 的 protocol/runtime/session 再导出与命名的 `WorkerWorkflowEngine` | 所有通过包名导入的消费方都使用默认引擎;工作流 Agent Note 已将 worker 协议格式(wire format)定义为私有。 | 保留默认插件类/配置契约;移除重复的命名类导出,将协议模块保持为源码私有。 |
|
||||
| `code-runtime-worker` 的 protocol/bootstrap 再导出 | 包外的生产/e2e 消费方使用 `WorkerCodeRuntime` 和配置,而非 `BootstrapPort`、`PatchableStream` 或 worker 消息/启动类型。 | 保留运行时类/配置契约,将其协议格式/bootstrap 词汇改为源码私有。 |
|
||||
| ACP 的 `agentOptions` 根导出 | 该辅助函数只有同文件和 ACP 测试消费方;唯一的包外生产消费方挂载的是插件命名空间。 | 保留 `name`、`inject`、`Config`、`AcpConfig` 和 `apply`;将 `agentOptions` 改为源码私有,通过桥接层行为测试。 |
|
||||
| `providerWording` 与 `completedTurnPrefix` 根导出 | 各有一个同包生产调用者;只有 balanced-prefix 辅助函数有一个同包白盒测试。 | 改为源码私有,测试提供方行为。 |
|
||||
| `depthOf`、`SubagentDepthError`、`waitForExit` 与 `exitsWithin` 根导出 | 生产 subagent 后端消费的是进程内 runner 和子进程构造/dispose(资源释放)辅助函数,而非这些强制/测试内部实现。`SENSITIVE_ENV_PATTERN` 不在其中,因为 SDK helper 会将它应用于调用方传入的环境。 | 保留深度与退出行为,但将剩余辅助函数和 error 改为源码私有;通过 spawn 和 dispose 测试。保持共享凭据正则公开。 |
|
||||
| `depthOf`、`SubagentDepthError`、`waitForExit` 与 `exitsWithin` 根导出 | 生产 subagent 后端消费的是进程内 runner 和子进程构造/dispose(资源释放)辅助函数,而非这些强制机制和测试内部实现。`SENSITIVE_ENV_PATTERN` 不在其中,因为 SDK helper 会将它应用于调用方传入的环境。 | 保留深度与退出行为,但将剩余辅助函数和 error 改为源码私有;通过 spawn 和 dispose 测试。保持共享凭据正则公开。 |
|
||||
| `PersistenceCoordinator.inits`、后端 `inits` 访问器、`seedCoversPrefix` 与 `assertSerializable` | 访问器为白盒测试而存在;`seedCoversPrefix` 没有包外生产导入者;`assertSerializable` 没有生产调用者,且与 coordinator append 边界的无损快照重复。 | 通过 `session/flush` 观察初始化,将 `seedCoversPrefix` 改为源码私有,删除 `assertSerializable`。保留两个后端、`SessionHeader` 和 SQLite 的版本契约。 |
|
||||
| `LlmError.status` 与回放 status | 适配器/回放填充它,但生产分支基于稳定的错误码/消息判断,从不读取原始 status。 | 移除未读字段和回放管道,保留错误分类。 |
|
||||
| `BlockAssembler.push()` 返回值 | 两个生产调用者都忽略返回的已完成块。 | 返回 `void`;保留有意公开的 `blocks()`/`message()` 契约。 |
|
||||
| `compactRegion` 的独立 `session` 参数 | 固定调用者传入的对象与 `agent.session` 上已有的是同一个;模型可见的 mount API 也能调用该方法,但接受两个身份允许挂载的插件提供不一致的配对。 | 保留手动 region seam,同时有意将其收窄为以 `agent.session` 为唯一真源。 |
|
||||
| `compactRegion` 的独立 `session` 参数 | 固定调用方传入的对象就是 `agent.session` 中已有的对象;模型可见的 mount API 也可以调用该方法,但同时接受两个独立对象,会让挂载的插件传入不一致的组合。 | 保留手动 region seam,同时有意将其收窄为以 `agent.session` 为唯一真源。 |
|
||||
| `CompactionResult.startSeq`、`summarySeq`、`endSeq` 与 `summary` | 生产消费方只读取 shadowed range/seq/token 统计;持久日志拥有 summary 和事件标识。 | 移除四个结果回显,保留两个共享的 transcript(文本记录)渲染器。 |
|
||||
| `BasicCompactService` 的 estimation/summarization 可见性 | 没有包外生产调用者调用这五个方法;已实现的 Agent Note 只将 `estimateContentTokens()` 和 `summarize()` 命名为子类钩子。 | 将这两个方法改为 `protected`,其余三个编排专用的估算器改为 private。 |
|
||||
| `BasicCompactService` 的估算/摘要方法可见性 | 没有包外生产调用者调用这五个方法;已实现的 Agent Note 只将 `estimateContentTokens()` 和 `summarize()` 命名为子类钩子。 | 将这两个方法改为 `protected`,其余三个编排专用的估算器改为 private。 |
|
||||
| `CodeLogEntry.source`/`level` 与 `RunCodeMeta.dispatches` | 每个生产消费方都将日志映射为文本;没有 presenter/模型路径读取其他字段或持久化的 dispatch 计数。 | 将 code-runtime 日志改为字符串(或纯文本条目),移除 result-meta 的 dispatch 管道;保留用于生成确定性 dispatch id 的本地计数器。 |
|
||||
| `CodeRuntime.language` 与 `CodeRuntime.isolation` | worker 后端提供唯一的生产值,而 Code Mode 及其他所有生产调用方只调用 `run()`。 | 移除未读描述符,同时保留 worker 的语言、隔离、预算、取消与资源释放行为。 |
|
||||
| `ToolNotFoundError.toolName`、`SystemPrompt.config` 与 `BashTask.command` | 每个存储的公开值都没有生产读取者。 | 移除未读字段,保留错误消息、已解析的配置行为和任务生命周期。 |
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.md
|
||||
2026-07-19-make-jsonrpc-directional.md: 910b4988aca34dec499b2e34cb2a42042c81b0cb
|
||||
2026-07-19-make-jsonrpc-directional.zh.md: acc1b433e14c65da2ed37dff7a5fe181ebee36ad
|
||||
2026-07-19-make-jsonrpc-directional.zh.md: 9810cf758faa078583955ba541e2b2fe9ad9c855
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: proposed
|
||||
|
||||
## 问题
|
||||
|
||||
JSON-RPC 桥接层把两个端点都建模为对称的对等端,但实际协议具有固定方向。共享传输层(现为 `dsh-sdk-protocol`,由服务端与 TypeScript SDK 客户端共用,后者行使出站请求/入站通知方向)仍实现着没有任何端点使用的两个半边:服务端发起的请求与客户端发起的通知。Python SDK 发送请求并接收响应或通知,却还会把未使用的服务端入站请求放入队列,并公开响应辅助方法。
|
||||
JSON-RPC 桥接层把两个端点都建模为对称的对等端,但实际协议具有固定方向。共享传输层(现为 `dsh-sdk-protocol`,由服务端与 TypeScript SDK 客户端共用,后者行使出站请求/入站通知方向)仍实现着没有任何端点使用的两个半边:服务端发起的请求与客户端发起的通知。Python SDK 发送请求并接收响应或通知,却还会把来自服务端、但未使用的入站请求放入队列,并公开响应辅助方法。
|
||||
|
||||
`session/prompt` 还会用两种协议结构报告同一个已结束轮次。服务端先发出 `session.finished`,再返回常量 `{ accepted: true }`;Python SDK 丢弃该响应,转而等待通知以取得状态。响应只有在处理函数返回后才会写入,因此在同一条有序流上,通知必然先于这个常量响应。
|
||||
|
||||
@@ -16,12 +16,12 @@ JSON-RPC 桥接层把两个端点都建模为对称的对等端,但实际协
|
||||
|
||||
按实际角色收窄两个端点。服务端保留入站请求、出站响应和出站通知;TypeScript 与 Python 客户端保留出站请求以及入站响应或通知。删除没有任何端点使用的方向——服务端发起的请求与客户端发起的通知。
|
||||
|
||||
在 `agent.whenIdle()` 完成后,由 `session/prompt` 直接返回 `{ status, reason }` 作为轮次结果。删除 `session.finished`、常量接纳响应以及 Python 中响应后的完成等待循环。`session.event` 与 subagent 通知仍在响应前流式发出,持久会话事件仍是最终响应重建的真源。
|
||||
在 `agent.whenIdle()` 完成后,由 `session/prompt` 直接返回 `{ status, reason }` 作为轮次结果。删除 `session.finished`、常量接纳响应以及 Python 中响应后的完成等待循环。`session.event` 与 subagent 通知仍在响应前流式发出,持久化会话事件仍是最终响应重建的真源。
|
||||
|
||||
## 实施计划
|
||||
|
||||
1. 在 `packages/ui/jsonrpc/src/server.ts` 中,用 `status: 'ok' | 'error' | 'aborted'` 和捕获的 `TurnEndReason` 替换 `SessionPromptResult.accepted`。`HarnessSdkServer.prompt()` 把 `completed` 映射为 `ok`,把 `aborted` 映射为 `aborted`,把其他当前或可合并扩展的原因映射为 `error`;进入空闲状态却没有 `turn/end` 仍视为不变量错误。只删除 `session.finished`,保持 `session.event`、`subagent.started` 和 `subagent.finished` 不变。
|
||||
2. 在 `packages/sdk/sdk-protocol/src/transport.ts` 中,把共享类收窄到有消费者的方向——入站请求/出站响应(服务端)与出站请求/入站响应加入站通知(TypeScript SDK 客户端)——只删除服务端发起的 `request()` 用法与客户端发起的通知分发,或把该类拆分为服务端与客户端两个传输。请求结果、方法不存在与处理器错误响应保持原有行为,并继续排在被等待处理器发出的通知之后。
|
||||
1. 在 `packages/ui/jsonrpc/src/server.ts` 中,用 `status: 'ok' | 'error' | 'aborted'` 和捕获的 `TurnEndReason` 替换 `SessionPromptResult.accepted`。`HarnessSdkServer.prompt()` 把 `completed` 映射为 `ok`,把 `aborted` 映射为 `aborted`,把其他当前已有或可通过声明合并扩展的原因映射为 `error`;进入空闲状态却没有 `turn/end` 仍视为不变量错误。只删除 `session.finished`,保持 `session.event`、`subagent.started` 和 `subagent.finished` 不变。
|
||||
2. 在 `packages/sdk/sdk-protocol/src/transport.ts` 中,把共享类收窄到有消费者的方向——入站请求/出站响应(服务端)与出站请求/入站响应及入站通知(TypeScript SDK 客户端)——只删除服务端发起的 `request()` 用法与客户端发起的通知分发,或把该类拆分为服务端与客户端两个传输。请求结果、方法不存在与处理器错误响应保持原有行为,并继续排在被等待处理器发出的通知之后。
|
||||
3. 在 `python/sdk/src/deepseek_harness/client.py`、`models.py` 和 `__init__.py` 中,删除 `IncomingRequest`、`_requests`、`notify()`、`next_request()`、`respond()` 和 `respond_error()`。新增公开且经过校验的 `SessionPromptResponse` 来携带状态与原因,由 `session_prompt()` 返回该对象,并保留明确的读取保护:忽略意外的服务端请求帧,避免它们命中响应等待器。
|
||||
4. 在 `python/sdk/src/deepseek_harness/api.py` 中,根据 `SessionPromptResponse` 构造 `TurnResult.status` 和新增的 `TurnResult.reason`,再删除 `session.finished` 分支与第二个完成循环。请求期间保持订阅打开,并保留 `_request_raw()` 最后的通知排空步骤,确保写在响应前的最后一条 `turn/end` 事件与任何 subagent 通知,都会在 `Session.run()` 重建最终助手消息之前被收集。
|
||||
5. 用按方向的覆盖替换 `packages/sdk/sdk-protocol/tests/transport.spec.ts` 中的对称传输对用例,并更新 `server.spec.ts`、`plugin-apply.spec.ts` 和 `built-scope-carrier.e2e.ts`,覆盖直接结果、顺序、重叠、关闭和收窄后的伪实现;同步更新 TypeScript SDK 客户端(`packages/sdk/sdk-client`)及其套件以采用基于响应的结束流程。更新 `python/sdk/tests/test_client.py`,覆盖基于响应的结束流程、意外请求帧处理、回调与并发行为,以及已删除的公开辅助方法。同步更新 JSON-RPC README、双语 Python SDK README、导出 JSDoc 与声明、`scripts/smoke-python-runtime.py` 和 Python 单可执行文件快照。
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/testing/2026-06-11-deterministic-and-stress-testing.md
|
||||
2026-06-11-deterministic-and-stress-testing.md: d9977be835af05f9ee303b63ec6015bc9e153170
|
||||
2026-06-11-deterministic-and-stress-testing.zh.md: eff9eecb699344dff388bafec270f6b6677f71ee
|
||||
2026-06-11-deterministic-and-stress-testing.zh.md: 008ecab6c55311cdce1b617c5a1f95de7442031a
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 确定性测试、回放不变式 fixture(测试前置数据)与竞态压力测试
|
||||
# Agent Note: 确定性测试、回放不变式 fixture与竞态压力测试
|
||||
|
||||
Status: proposed
|
||||
|
||||
@@ -14,7 +14,7 @@ Status: proposed
|
||||
|
||||
1. **测试中禁止挂钟睡眠。** 将 `setTimeout(N)` 等待替换为事件驱动等待(既有的 `waitForIdle` 模式,扩展为 `waitForStatus`、`waitForEvent(n)`),或在需要测试时间本身时使用 vitest 的 fake timer。通过 lint 规则禁止 `setTimeout`,适用范围是 `packages/*/tests`,白名单辅助模块除外。
|
||||
2. **通用回放 fixture。** 一个共享测试辅助函数包装 agent loop harness,使每个测试结束后,agent 的会话日志被回放到一个全新的 Session 中,并自动断言 `deriveMessages()` 相等。这样该不变式在每次 CI 运行中会被套件产生的所有场景检查数百次,而非仅两次。
|
||||
3. **夜间竞态压力测试。** 一个 CI job 以 `vitest --repeat=200`(加 `--shuffle`)运行 agent-loop 和 inbox 套件,以暴露调度依赖的失败;发现的任何不稳定测试都视为 bug 修复,绝不靠重试掩盖。
|
||||
3. **夜间竞态压力测试。** 一个 CI job 以 `vitest --repeat=200`(加 `--shuffle`)运行 agent-loop 和 inbox 套件,以暴露调度依赖的失败;发现的任何不稳定现象都视为需要修复的 bug,绝不靠重试掩盖。
|
||||
|
||||
## 计划
|
||||
|
||||
@@ -24,7 +24,7 @@ Status: proposed
|
||||
|
||||
- 不再使用 `setTimeout`;lint 规则在 `packages/*/tests` 中强制执行,白名单辅助模块除外。
|
||||
- 共享 harness 将每个测试的会话日志回放到全新的 `Session` 中,并自动断言 `deriveMessages()` 相等,覆盖整个套件。
|
||||
- 夜间 job 以 `--repeat` 和 `--shuffle` 运行 agent-loop 和 inbox 套件;发现的不稳定测试作为 bug 分诊,绝不靠重试掩盖。
|
||||
- 夜间 job 以 `--repeat` 和 `--shuffle` 运行 agent-loop 和 inbox 套件;发现的不稳定现象一律按 bug 分诊,绝不通过重试消除。
|
||||
|
||||
## 风险
|
||||
|
||||
|
||||
@@ -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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/testing/2026-06-11-mutation-testing.md
|
||||
2026-06-11-mutation-testing.md: 591d9012644a19ee2c67a916b63092d79f78db1f
|
||||
2026-06-11-mutation-testing.zh.md: 9c22ed2f42e5c44e6be98f132614886bbdb188fd
|
||||
2026-06-11-mutation-testing.zh.md: ebbeb561bd7bfee659882d25c808f2cb9a1aa825
|
||||
|
||||
@@ -14,23 +14,23 @@ Status: proposed
|
||||
|
||||
- **PR(Pull Request)范围的增量运行**(仅变更文件),作为一个 CI job。调优后速度足以作为合并门禁。
|
||||
- **每夜全量运行**,跟踪变异分数;先记录基线,再将阈值设为观测到的基线并只升不降(与覆盖率策略一致:阈值只收紧)。
|
||||
- 存活的变异体是待办项:agent 选取一个存活体、编写杀死它的测试、循环往复——一个形态良好的自主循环。
|
||||
- 存活的变异体是待办项:agent 选取一个存活体、编写杀死它的测试、循环往复——一个适合自主执行的闭环。
|
||||
- 等价变异体(可证明不改变行为的)加注释排除并附理由,与 `/* v8 ignore */` 策略一致。
|
||||
|
||||
## 计划
|
||||
|
||||
1. 添加 Stryker 配置,范围限定在一个包(package),即 llm(最小、最具算法性),并测量运行时间。
|
||||
1. 添加 Stryker 配置,范围限定在一个包,即 llm(最小、最具算法性),并测量运行时间。
|
||||
2. 扩展到所有包;在配置中记录基线分数。
|
||||
3. 接入每夜 job;运行时间可接受后再添加 PR 范围的增量 job。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- Stryker 配置在 `packages/*/src` 上以 vitest runner 运行;每夜 job 记录变异分数,当分数低于记录的基线时,通过只升不降的阈值使运行失败。
|
||||
- PR 范围的增量运行在运行时间可接受后作为合并门禁;或者明确保持仅每夜运行,并将该结论记录于此。
|
||||
- Stryker 配置在 `packages/*/src` 上以 vitest runner 运行;每夜 job 记录变异分数并采用只升不降的阈值,分数低于已记录的基线时任务失败。
|
||||
- PR 范围的增量运行在运行时间可接受后作为合并门禁;否则明确决定只保留每夜运行,并将该结论记录于此。
|
||||
- 等价变异体带有注释排除及理由,与 `/* v8 ignore */` 策略一致。
|
||||
|
||||
## 风险
|
||||
|
||||
运行时间:变异测试开销大;逐文件 100% 覆盖率有所帮助(每个变异体至少会被执行到)。如果 PR 范围的运行始终过慢,则保持仅每夜运行,依赖分数只升不降的机制。
|
||||
运行时间:变异测试开销大;逐文件 100% 覆盖率有所帮助(每个变异体至少会被执行到)。如果 PR 范围的运行始终过慢,则保持仅每夜运行,依靠变异分数阈值的只升不降机制。
|
||||
|
||||
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->
|
||||
|
||||
Reference in New Issue
Block a user