docs(i18n): apply reviewed Chinese proofreading updates
This commit is contained in:
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-15-code-mode.md
|
||||
2026-06-15-code-mode.md: f9bde7a85d9cf26d3d6670744c955380fa807e3b
|
||||
2026-06-15-code-mode.zh.md: dbf8d409dea152b39d215f5dd636989c3f4fa0bb
|
||||
2026-06-15-code-mode.zh.md: eeaddb9735231dd41b2723636b7e4508ee3e46a8
|
||||
|
||||
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
对于多步工具操作,这种方式 token 开销大且串行。模型无法组合工具——遍历结果集、根据中间值分支、扇出、后处理——每次调用都需要一次完整的模型往返,而每次往返都会把完整的中间结果拖回上下文,不管模型是否需要。
|
||||
|
||||
Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一种替代方案,基于一个简单的观察:LLM(大语言模型)编写代码的能力优于发出工具调用,因为它们见过数百万行真实代码,而人为构造的工具调用 trace 相对很少。模型不再每步发出一次工具调用,而是针对工具生成的 API 编写一段 TypeScript 程序,程序在沙箱运行时中执行,模型只策展返回的内容——仅限它 print 或 return 的部分——而非所有中间结果。
|
||||
Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一种替代方案,基于一个简单的观察:LLM(大语言模型)编写代码的能力优于发出工具调用,因为它们见过数百万行真实代码,而人为构造的工具调用 trace 相对很少。模型不再每步发出一次工具调用,而是针对工具生成的 API 编写一段 TypeScript 程序,程序在沙箱运行时中执行,模型只筛选返回的内容——仅限它 print 或 return 的部分——而非所有中间结果。
|
||||
|
||||
工具呈现属于掌管工具可见性的注册表:如果把第二种呈现方式实现为事后的 waterfall(瀑布式事件)变换,正确性将依赖监听器顺序,并与[可重建请求](../architecture/2026-07-05-reconstructable-requests.md)冲突。执行基底同样属于基础设施而非占位实现:Node `worker_threads` 提供独立 isolate、空环境、堆上限以及对热同步循环的终止能力,同时契合 harness 既有的信任模型(§信任姿态)。
|
||||
|
||||
@@ -64,9 +64,9 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一
|
||||
- `CodeBindingNamespace = { global: string; functions: Record<string, (args: unknown) => Promise<CodeJsonValue>>; errorClass?: { name: string; memberNameProperty: string } }`——运行时将每个命名空间作为程序内部的全局异步函数对象暴露;可选描述符要求运行时注入真正的、程序可见的 reject 类,而无需让 seam 获知消费方专用名称。`CodeJsonValue` 是这个低依赖 seam 的结构化无损 JSON 类型,因此绑定参数与返回值可以完整跨越实现的序列化边界。
|
||||
- `CodeRunResult = { value?: CodeJsonValue; logs: string[]; error?: CodeRunFailure }`——程序执行失败时,执行 promise 仍会 fulfill,并通过 `error` 字段返回失败结果。只有调用方/seam 误用(例如重复的绑定命名空间)时,`run()` 才会 reject;消费方仍在自己的错误边界处理不合规后端的拒绝。
|
||||
- `CodeRunFailure = { kind: 'exception' | 'timeout' | 'abort' | 'worker-exit' | 'invalid-output' | 'output-limit'; message: string }`——按[防御性模式](../../../../docs/defensive-patterns.md)独立报告的正交结果;超时的 run 不是异常,abort 不是超时,有损完成值不是溢出,基底退出也与上述情况相互独立。
|
||||
- 两个只读的后端描述符,仅供信息参考而非门禁判定:`language`(程序必须使用的语言——首个后端为 `'typescript'`;Python 后端声明 `'python'`,并在呈现侧配对自己的 SDK 生成器)和 `isolation`(交付的后端为 `'worker-thread'`;未来可为 `'process'`、`'container'` 等)。`dsh-tools` 接受任何注册了 SDK 渲染器与 `run_code` flavor 的 `language`(TypeScript 与 Python 已交付;见[语言分发 note](2026-07-31-code-mode-language-dispatch.md)),否则组装会大声失败,与 `toolOrder` 违规时的配置错误惯用法相同(如 `mode` 为非 native 但根本没有加载 `ctx.codeRuntime`)。
|
||||
- 两个只读的后端描述符,仅供信息参考而非门禁判定:`language`(程序必须使用的语言——首个后端为 `'typescript'`;Python 后端声明 `'python'`,并在呈现侧配对自己的 SDK 生成器)和 `isolation`(交付的后端为 `'worker-thread'`;未来可为 `'process'`、`'container'` 等)。`dsh-tools` 接受任何注册了 SDK 渲染器与 `run_code` flavor 的 `language`(TypeScript 与 Python 已交付;见[语言分发 note](2026-07-31-code-mode-language-dispatch.md)),否则组装会立即明确报错,与 `toolOrder` 违规时的配置错误惯用法相同(如 `mode` 为非 native 但根本没有加载 `ctx.codeRuntime`)。
|
||||
|
||||
请求包含所有运行时输入;实现方拥有经校验的超时和上限默认值。注册表仅在组装 Code Mode 时查找可选的运行时,因此 native 模式不依赖它。缺失或语言不兼容的运行时会大声失败。替代基底或语言可以在同一 seam 背后替换实现,配对相应的 SDK 生成器。
|
||||
请求包含所有运行时输入;实现方拥有经校验的超时和上限默认值。注册表仅在组装 Code Mode 时查找可选的运行时,因此 native 模式不依赖它。运行时缺失或语言不兼容时会立即明确报错。替代基底或语言可以在同一 seam 背后替换实现,配对相应的 SDK 生成器。
|
||||
|
||||
### worker-thread 运行时
|
||||
|
||||
@@ -89,7 +89,7 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认
|
||||
|
||||
## 后果
|
||||
|
||||
切换到 `'code'` 的部署必须更新任何仅限 native 的 `toolOrder`。组装监听器有责任维护任何被重写的协议面的完整性。子分发在有界的重叠池下按提交顺序启动,而每次调用的上下文会通过外层结果保留其 source、信封与元数据。
|
||||
切换到 `'code'` 的部署必须更新任何仅限 native 的 `toolOrder`。组装监听器有责任维护任何被重写的协议消息的完整性。子分发在有界的重叠池下按提交顺序启动,而每次调用的上下文会通过外层结果保留其 source、信封与元数据。
|
||||
|
||||
## 测试
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md
|
||||
2026-06-17-filesystem-tool-schemas.md: 5fff2c8781ff462265a9cb2042aca2522da87b04
|
||||
2026-06-17-filesystem-tool-schemas.zh.md: 5ab19b82d6523bc81dd57c319252d998cc203911
|
||||
2026-06-17-filesystem-tool-schemas.zh.md: 04ea814ea47b5f7b108c24d5e27d796475a7a36c
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
[文件系统能力 seam Agent Note](../architecture/2026-06-17-filesystem-capability-seam.md) 定义了文件系统能力 seam(`ctx.fs`)、包拆分(`dsh-fs`、`dsh-fs-local`、`dsh-tool-fs`,加上 `dsh-fs-policy` 策略插件),以及针对 read-before-write/edit 检查的已观测文件/陈旧版本策略——[拆分文件系统 seam](../simplification/2026-06-26-fsspec-style-fs-seam.md)和[事件门控插件](../architecture/2026-06-26-file-context-as-event-gate.md) Agent Note 后来将其从 `ctx.fs` 移至 `dsh-fs-policy` 插件的 `fs/*` 事件门上。首次文件系统工具交付剩余的决策是面向模型的 schema 接口:模型在 `read`、`write` 和 `edit` 中看到哪些参数。
|
||||
[文件系统能力 seam Agent Note](../architecture/2026-06-17-filesystem-capability-seam.md) 定义了文件系统能力 seam(`ctx.fs`)、包拆分(`dsh-fs`、`dsh-fs-local`、`dsh-tool-fs`,加上 `dsh-fs-policy` 策略插件),以及针对 read-before-write/edit 检查的已观测文件/陈旧版本策略——[拆分文件系统 seam](../simplification/2026-06-26-fsspec-style-fs-seam.md)和[事件门控插件](../architecture/2026-06-26-file-context-as-event-gate.md) Agent Note 后来将其从 `ctx.fs` 移至 `dsh-fs-policy` 插件的 `fs/*` 事件门上。首次文件系统工具交付剩余的决策是面向模型的 schema:模型在 `read`、`write` 和 `edit` 中看到哪些参数。
|
||||
|
||||
该 schema 必须足够小,但又要足够稳定,使本地/远程/沙箱文件系统后端不需要改动面向模型的接口,并且必须避免从参考系统中照搬所有选项。Claude Code 和 OpenCode 暴露了类似的核心文件工具,但在命名风格和额外 flag 上有所不同;本决策选择最小的共有接口。
|
||||
|
||||
@@ -49,7 +49,7 @@ schema 使用 snake_case 字段名(`file_path`、`old_string`、`new_string`
|
||||
- `file_path: string`——必填。要写入的路径,由 `ctx.fs` 解析。
|
||||
- `content: string`——必填。要写入的完整 UTF-8 文本内容。
|
||||
|
||||
在默认 fs-policy 下,使用 `write` 更新已有文件需要同一执行上下文先前对该文件有过一次观测(read/write/edit);`dsh-fs-policy` 插件将观测到的版本作为 `fs/write-intent` 上的陈旧版本防护提供。创建新文件不需要先前观测。如果策略插件不存在,`write` 是无条件的裸提供方 create-or-overwrite。
|
||||
在默认 fs-policy 下,使用 `write` 更新已有文件需要同一执行上下文先前对该文件有过一次观测(read/write/edit);`dsh-fs-policy` 插件将观测到的版本作为 `fs/write-intent` 上的陈旧版本防护提供。创建新文件不需要先前观测。如果策略插件不存在,裸提供方会让 `write` 无条件创建文件或覆盖已有文件。
|
||||
|
||||
schema 不将 `expected_hash`、`expected_version` 或 `create_only` 作为面向模型的参数暴露。陈旧版本检查由后端产生的版本和策略插件的观测状态驱动,而非要求模型通过 schema 复制版本令牌。
|
||||
|
||||
@@ -100,13 +100,13 @@ schema 测试固定每个工具的必填/可选参数集、空 `old_string` 拒
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **Codex 风格的 patch 语法或多模式 edit API**:否决。一种严格的字面替换模式使面向模型的约定保持简单,并让后端掌控精确匹配、重复匹配、行尾和陈旧版本的语义。
|
||||
- **camelCase 参数名(OpenCode 风格)**:snake_case 与 Claude Code 及现有 harness 工具 schema 示例一致,且命名一旦发布即成为公开接口。
|
||||
- **camelCase 参数名(OpenCode 风格)**:snake_case 与 Claude Code 及现有 harness 工具 schema 示例一致,且命名一旦发布即成为公开 API。
|
||||
- **面向模型的 `expected_hash` / `expected_version` / `create_only` 参数**:否决。陈旧检查由后端产生的版本和策略插件的观测状态驱动,从不依赖模型复制的脆弱令牌。
|
||||
|
||||
## 后果
|
||||
|
||||
**首版 schema 有意小于 Claude Code 的。** 去掉 PDF pages、多模态 read、丰富的 grep/list flag 和 expected hash 字段使实现保持聚焦,但用户可能很快就会提出这些需求。它们将以独立 Agent Note 或聚焦的后续工作形式到来,而不是让初始 schema 承载过多内容。
|
||||
|
||||
**v1 中没有显式的面向模型的陈旧版本防护。** schema 不要求模型提供 expected hash/version。这是有意为之:陈旧检查来自后端产生的版本和 `dsh-fs-policy` 插件的观测状态,而非模型复制的脆弱令牌。文件系统安全失败通过 `dsh-fs` 拥有的结构化 `FsError` 代码浮现,而非模型提供的版本字段。
|
||||
**v1 中没有显式的面向模型的陈旧版本防护。** schema 不要求模型提供 expected hash/version。这是有意为之:陈旧检查来自后端产生的版本和 `dsh-fs-policy` 插件的观测状态,而非模型复制的脆弱令牌。文件系统安全失败通过 `dsh-fs` 拥有的结构化 `FsError` 代码报告,而非模型提供的版本字段。
|
||||
|
||||
**命名成为公开接口。** 一旦发布,将 `file_path` 改为 `filePath` 或 `old_string` 改为 `oldString` 会导致提示词、示例和下游客户端随之改动。本 Agent Note 预先选择 snake_case,并将其视为稳定的面向模型的约定。
|
||||
**命名成为公开 API。** 一旦发布,将 `file_path` 改为 `filePath` 或 `old_string` 改为 `oldString` 会导致提示词、示例和下游客户端随之改动。本 Agent Note 预先选择 snake_case,并将其视为稳定的面向模型的约定。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md
|
||||
2026-06-18-compaction-capability-seam.md: 2c8128b187f049b584af32f4f40f8e72da56dd73
|
||||
2026-06-18-compaction-capability-seam.zh.md: 12cf2498535d77dcf1a718b841a751bccadfdf1c
|
||||
2026-06-18-compaction-capability-seam.zh.md: 195edf8716fc2e2b1a9241e2cc63db567b0fa724
|
||||
|
||||
@@ -16,7 +16,7 @@ Status: implemented
|
||||
|
||||
### 压缩是一个能力 seam,Service Definition 与 Service provider 角色分离
|
||||
|
||||
遵循[能力 seam Agent Note](../architecture/2026-06-13-capability-seams.md),压缩以独立包发布,使约定、算法和(后续的)消费方 surface 各自独立演进:
|
||||
遵循[能力 seam Agent Note](../architecture/2026-06-13-capability-seams.md),压缩以独立包发布,使约定、算法和(后续的)消费方 API 各自独立演进:
|
||||
|
||||
1. **接口** — `@deepseek-ai/dsh-compact`:抽象 `CompactService`,拥有 `ctx.compact` 键、`CompactionResult` 词汇、`compact/*` 会话事件、手动失败分类体系以及规范的检查点消息来源。它将 `compactIfNeeded()`、`compactNow()` 和 `compactRegion()` 声明为**抽象方法**——约定说明压缩*做什么*,而非*怎么做*。
|
||||
2. **实现** — `@deepseek-ai/dsh-compact-basic`:具体的 `BasicCompactService`,消费 `ctx.tokenMeter`,并拥有尾→头保留遍历、通过 `ctx.llm.stream()` 生成摘要、surface 替换、锁、步骤前压力处理和规范的上下文溢出恢复。`summarize()` 是其唯一的子类钩子;计价与回放仍归 meter 所有。
|
||||
@@ -59,7 +59,7 @@ retry → next numbered step/start ⟵ derives from the replacement surface
|
||||
|
||||
因此失控轮次的压缩方式与其他历史完全相同:其早期*已关闭*步骤被摘要,近期步骤保持原样。当唯一可压缩的内容只剩一个不可拆分的开放尾部步骤(其工具调用尚无结果)时,压缩拒绝执行(返回 `null`)并在该步骤关闭后重试。
|
||||
|
||||
**部分单单元溢出仍不在范围内。** 摘要范围选择无法拆分不可分割的单元。当可移除的文本型工具结果内容占据大部分空间,且修剪后的余量能够容纳时,可选修剪器可以修复一个已关闭的工具对。仅信封压力、粘贴的 `user/message` 等不可分割的超大非工具节点,以及不可修剪余量仍然过大的工具单元,依旧不属于压缩范围;限制这些单元是另一个关注点。
|
||||
**部分单元内溢出仍不在范围内。** 摘要范围选择无法拆分不可分割的单元。当可移除的文本型工具结果内容占据大部分空间,且修剪后的余量能够容纳时,可选修剪器可以修复一个已关闭的工具对。仅信封压力、粘贴的 `user/message` 等不可分割的超大非工具节点,以及不可修剪余量仍然过大的工具单元,依旧不属于压缩范围;限制这些单元是另一个关注点。
|
||||
|
||||
### 头部锚定:一个自动检查点,始终在头部
|
||||
|
||||
@@ -91,7 +91,7 @@ compact/end → log-only. Releases the lock (carries `error` on a recoverab
|
||||
|
||||
### 通过日志记录的锁实现阻塞,加上崩溃/可恢复失败的分类
|
||||
|
||||
`compact/start … compact/end` 标记对承担两项职责:
|
||||
`compact/start … compact/end` 事件对承担两项职责:
|
||||
|
||||
1. **可检测的崩溃孤儿 + 已记录的摘要输入**(首要)。摘要生成是一次慢速模型调用,持久化在 `compact/start` *之后*。摘要生成中途崩溃会留下一个没有匹配 `compact/end` 的 `compact/start`——一个可检测的孤儿。最后释放锁(而非最先)将崩溃窗口从*静默损坏*转变为可检测的孤儿。
|
||||
2. **防止并发压缩。** 每个自动、手动和显式范围入口点都会拒绝活动的未匹配 `compact/start`。该标记对就是唯一的锁;没有进程本地 mutex 重复承担同一职责。
|
||||
@@ -122,7 +122,7 @@ compact/end → log-only. Releases the lock (carries `error` on a recoverab
|
||||
- **自动扩展点**:`agent/pre-step`(`@mode waterfall`)在请求派生前处理压力,`agent/request-error`(`@mode waterfall`)处理失败步骤关闭后的最终请求失败。pre-step 的 payload 携带已领取批次、轮次、步骤与 signal(参见 [payload-object 事件决策](../architecture/2026-08-06-agent-event-payload-objects.md)),不携带压缩专属的提示词/前缀 payload。
|
||||
- **`SessionEventMap`** 通过可合并扩展的声明合并获得 `compact/start` / `compact/summary` / `compact/end`;`SurfaceEventType` **未被**触及。这些是会话事件,不是 cordis `Events`,因此事件分类门禁无需新增条目。
|
||||
- **`dsh-compact`** 拥有 `COMPACT_CHECKPOINT_SOURCE`、`isCompactCheckpointSource(source)`、`toolPairingBalancedBefore(session, seq)` 与 `toolPairingBalancedAfter(session, seq)`。该标记用于跨后端实现识别替换摘要。带缓存的 surface 边缘检查会防止 `compactRegion` 和 `compactIfNeeded` 拆分工具调用/结果对,按 seq 校验当前成员关系,从每个切割点的一条平衡序列回答两侧边缘,并拒绝陈旧或缺失的 seq 与孤立结果。
|
||||
- **`dsh-session`** 通过唯一的 surface 管理器校验位置替换、引用的来源事件是否覆盖完整,以及仅内容的单节点 `tool/result` 重写。其不变式配套插件将新追加的工具结果视为执行,要求存在已打开的步骤与待处理调用,而压缩配套组件拥有数字轮次归属与独立 `null` 归属标记对之间的关系。
|
||||
- **`dsh-session`** 通过唯一的 surface 管理器校验位置替换、引用的来源事件是否覆盖完整,以及仅内容的单节点 `tool/result` 重写。其不变式配套插件将新追加的工具结果视为执行,要求存在已打开的步骤与待处理调用,而压缩配套组件负责维护数字轮次 owner 与独立 `null` owner 事件对之间的关系。
|
||||
- **接线**:`examples/tui-agent/cordis.yml` 依次加载零配置的 `dsh-token-meter`、`dsh-compact-tool-result-prune`、`dsh-compact-basic`,然后加载 `dsh-command-compact`;服务级默认值使组合无需重复数值策略即可使用。
|
||||
|
||||
## 测试
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.md
|
||||
2026-06-30-hook-protocol-lib.md: ca7d1acd356b71824a9bbfe3857b3c48c5cdff4b
|
||||
2026-06-30-hook-protocol-lib.zh.md: 4a4efae9a8950368d4db0309bc2c89a3f8c6d0d0
|
||||
2026-06-30-hook-protocol-lib.zh.md: dc9d454d1b168cbcabb620b0125cbd9b80d2783b
|
||||
|
||||
@@ -16,12 +16,12 @@ Status: implemented
|
||||
|
||||
**共享(本库):**
|
||||
- **Matcher** — `matcherDiagnostic(pattern, mode)` 与 `matchesMatcher(pattern, query, mode)`。两种方言的唯一差异收敛到 `mode` 参数:`claude` 将纯 `[A-Za-z0-9_|]+` pattern 视为字面量(管道符表示多个精确匹配备选项),其他 pattern 视为正则;`codex` 始终使用未锚定正则。缺省/`''`/`'*'` 匹配一切。每个桥接插件会在解析 group 前忽略不支持的事件,丢弃受支持但没有 matcher 匹配对象的事件所带字段,校验其余可运行 group;其中任何无效正则都会导致整份配置加载失败,并给出包含方言/pattern/事件的稳定诊断,不会注册任何钩子监听器。运行时匹配仍会将无效正则隔离为不匹配,因此直接调用本库绝不向 agent loop(智能体循环)抛异常。
|
||||
- **执行** — `runHook(bash, hook, options)`。通过 `ctx.bash` seam 而非自建 `spawn` 运行命令钩子:执行器已提供清洗但可覆盖的 env、进程组 kill 和超时,正是协议所需的能力;`dsh-bash` 的 `stdin`/`env` 字段(正是为此添加的)是进程内桥接插件被允许使用的受信插件接口。它将桥接插件构建的 payload 序列化到 stdin(仅 CC 时追加尾部换行),遵守钩子的 `timeoutSec`(否则使用 `DEFAULT_HOOK_TIMEOUT_MS`,即两种方言共享的 10 分钟参考默认值),且从不抛异常(执行器拒绝变为 non-blocking-error 的 `HookOutput`)。
|
||||
- **执行** — `runHook(bash, hook, options)`。通过 `ctx.bash` seam 而非自建 `spawn` 运行命令钩子:执行器已提供清洗但可覆盖的 env、进程组 kill 和超时,正是协议所需的能力;`dsh-bash` 的 `stdin`/`env` 字段(正是为此添加的)是进程内桥接插件被允许使用的受信插件 API。它将桥接插件构建的 payload 序列化到 stdin(仅 CC 时追加尾部换行),遵守钩子的 `timeoutSec`(否则使用 `DEFAULT_HOOK_TIMEOUT_MS`,即两种方言共享的 10 分钟参考默认值),且从不抛异常(执行器拒绝变为 non-blocking-error 的 `HookOutput`)。
|
||||
- **解码** — `parseHookOutput(exit, stdout, stderr)`,exit-code + structured-stdout 编解码器,产出方言无关的 `HookOutput`。Exit `0` → 宽松 JSON 解析 stdout;exit `2` → blocking error,`stderr` 为原因(以 `decision: 'block'` 呈现,调用方无需单独处理 exit-code 分支);其他 → non-blocking error。解析 CC structured-stdout 中在某条路径上有消费方的字段(`continue`/`stopReason`/`decision`/`hookSpecificOutput.{permissionDecision,additionalContext,updatedInput}`/`systemMessage`);桥接插件只采纳对其方言有意义的子集。在任何路径上都没有消费方的字段不予解析(CC 的 `suppressOutput`——钩子 stdout 在此处从不进入 transcript(文本记录),因此无需抑制;见 [收紧钩子协议约定 Agent Note](../simplification/2026-07-04-tighten-hook-protocol-contract.md))。
|
||||
- **合并** — `mergeHookOutputs(outputs)`,将多个匹配钩子的输出折叠为一个最严格的 `MergedHookOutcome`:权限优先级 **deny > ask > allow**,halt 在首个 `continue:false` 时粘滞,阻止原因以 `\n\n` 拼接,context/system-messages 按序累积。
|
||||
- **合并** — `mergeHookOutputs(outputs)`,将多个匹配钩子的输出折叠为一个最严格的 `MergedHookOutcome`:权限优先级 **deny > ask > allow**,首个 `continue:false` 会锁定 halt 状态,阻止原因以 `\n\n` 拼接,context/system-messages 按序累积。
|
||||
- **`hook/*` 会话事件** — `hook/invoked` / `hook/result`,通过声明合并进入 `SessionEventMap`(仅日志,如 `compact/*`——不是 `SurfaceEventType`),配有 `appendHookInvoked`/`appendHookResult` 辅助函数,确保 invoked/result 配对与由所有者定义的执行关系在各桥接插件间保持一致。`appendHookResult` 还负责定义持久化记录的语义:decision 字符串(钩子解析出的 decision,否则 `continue:false` 时为 `'stop'`,否则为 `'pass'`)和 500 字符的 `stderrSummary` 截断均从本库的 `HookOutput` 派生,而非各桥接插件各自实现。
|
||||
|
||||
**方言专属(桥接插件):** 构建每个事件的 stdin payload(CC 的 base+per-event 字段集 vs Codex 的 snake_case 加 `turn_id`/`model` 额外字段)、方言的 env 与 `${CLAUDE_PLUGIN_ROOT}` 替换(CC)vs 无替换(Codex),以及将方言无关的 `HookOutput`/`MergedHookOutcome` 映射为 harness 各扩展点专属的类型化 Decision(`PreToolDecision`、`PreStepDecision`、`ContinuationDecision`、`PostToolDecision`)。
|
||||
**方言专属(桥接插件):** 构建每个事件的 stdin payload(CC 的 base+per-event 字段集 vs Codex 的 snake_case 加 `turn_id`/`model` 额外字段)、CC 方言的 env 与 `${CLAUDE_PLUGIN_ROOT}` 替换,而 Codex 两者皆无,以及将方言无关的 `HookOutput`/`MergedHookOutcome` 映射为 harness 各扩展点专属的类型化 Decision(`PreToolDecision`、`PreStepDecision`、`ContinuationDecision`、`PostToolDecision`)。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-30-interception-extension-points.md
|
||||
2026-06-30-interception-extension-points.md: b6e7e8c94bfff8f483dc9faf7b195b130a7e9953
|
||||
2026-06-30-interception-extension-points.zh.md: 262793dd7a297f343391a22fb8d9f1d57a9ff531
|
||||
2026-06-30-interception-extension-points.zh.md: 5801992e47cc4bf3d3a32aebc944117eaa5de77c
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 拦截扩展点——钩子编程所面对的类型化 Decision 表面
|
||||
# Agent Note: 拦截扩展点——钩子编程所面对的类型化 Decision 接口
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,13 +6,13 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
harness 需要一套钩子子系统:用户像 Claude Code(CC)和 Codex 那样在生命周期节点扩展或管控 agent(智能体)。驱动本设计的关键视角转换是:**「原生钩子」不是一个包**——原生钩子只是一个普通的 Cordis 插件,订阅规范的生命周期事件。因此真正的产品是一个*强大、类型完备的规范事件表面*;CC/Codex 桥接(`dsh-hooks-claude` / `dsh-hooks-codex` 包)只是将外部 shell 钩子协议映射到同一表面的翻译层。桥接能做的事,普通插件可以直接做——而且更强大(无序列化边界、完整 `ctx`、类型化返回值)。
|
||||
harness 需要一套钩子子系统:用户像 Claude Code(CC)和 Codex 那样在生命周期节点扩展或管控 agent(智能体)。驱动本设计的关键视角转换是:**「原生钩子」不是一个包**——原生钩子只是一个普通的 Cordis 插件,订阅规范的生命周期事件。因此真正的产品是一个*强大、类型完备的规范事件接口*;CC/Codex 桥接(`dsh-hooks-claude` / `dsh-hooks-codex` 包)只是将外部 shell 钩子协议映射到同一接口的翻译层。桥接能做的事,普通插件可以直接做——而且更强大(无序列化边界、完整 `ctx`、类型化返回值)。
|
||||
|
||||
该表面需要为以下场景提供各自独立的约定:逐提示词策略(CC 的 `UserPromptSubmit`)、会话启动观测(CC 的 `SessionStart`)、工具执行前策略、环绕调度控制、工具执行后变换、最终结果观测,以及携带面向模型的原因的继续执行。如果把这些阶段混为一谈,插件就会获得不需要的 mutation 通道,而终结性将依赖监听器的注册顺序。[事件域语义 Agent Note](../architecture/2026-06-30-event-domain-semantics.md) 提供了三域规则与类型化 Decision 惯用法;本 Agent Note 将其应用于生命周期扩展点。
|
||||
该接口需要为以下场景提供各自独立的约定:逐提示词策略(CC 的 `UserPromptSubmit`)、会话启动观测(CC 的 `SessionStart`)、工具执行前策略、环绕调度控制、工具执行后变换、最终结果观测,以及携带面向模型的原因的继续执行。如果把这些阶段混为一谈,插件就会获得不需要的 mutation 通道,而终结性将依赖监听器的注册顺序。[事件域语义 Agent Note](../architecture/2026-06-30-event-domain-semantics.md) 提供了三域规则与类型化 Decision 惯用法;本 Agent Note 将其应用于生命周期扩展点。
|
||||
|
||||
## 决策
|
||||
|
||||
规范表面将可变换策略、环绕调度控制与仅观测通知分离。策略 waterfall(瀑布式事件)返回小型的、扩展点专属的**类型化 Decision 联合类型**;包装层返回规范化结果;通知接收不可变快照,无法影响结果。覆盖的钩子点包括 `session-start`、`prompt-submit`、`pre-tool`、`post-tool`、通过 continuation 实现的 `stop`,同时将非钩子的执行策略留作独立可组合。
|
||||
规范接口将可变换策略、环绕调度控制与仅观测通知分离。策略 waterfall(瀑布式事件)返回小型的、扩展点专属的**类型化 Decision 联合类型**;包装层返回规范化结果;通知接收不可变快照,无法影响结果。覆盖的钩子点包括 `session-start`、`prompt-submit`、`pre-tool`、`post-tool`、通过 continuation 实现的 `stop`,同时将非钩子的执行策略留作独立可组合。
|
||||
|
||||
**Agent 事件**(`dsh-agent`):
|
||||
- `agent/session-start({ agent, source })` ——emit,在第 1 轮次之前触发一次,携带 `SessionStartSource`(`startup` 表示全新/fork 创建,`resume` 表示重新加载的持久化会话;`clear`/`compact` 保留)。纯通知,不能阻塞启动(这是有意的空白:桥接可以记录/注入,但不管控启动)。监听器通过 `agent.inject()` 注入上下文。
|
||||
@@ -52,8 +52,8 @@ Service Definition 包**不**声明 `hook/*` 会话事件(持久的钩子调
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **将工具执行前输入重写作为本扩展点集合的一部分发布**:推迟,视为越界信号;上文已阐述一致性问题(审计、历史和展示都读取执行前记录的 `tool/call.arguments`),[工具执行前输入重写提案](../../proposed/feature/2026-06-30-pre-tool-input-rewrite.md)负责该设计。
|
||||
- **将持久的 `hook/*` SessionEvents 与扩展点一起声明**:否决。原生插件使用类型化 Decision 而完全不需要钩子日志(实际示例已证明),因此持久日志属于[钩子协议库](2026-06-30-hook-protocol-lib.md),而非扩展表面。
|
||||
- **将持久的 `hook/*` SessionEvents 与扩展点一起声明**:否决。原生插件使用类型化 Decision 而完全不需要钩子日志(实际示例已证明),因此持久日志属于[钩子协议库](2026-06-30-hook-protocol-lib.md),而非扩展接口。
|
||||
|
||||
## 后果
|
||||
|
||||
规范拦截表面具有统一的类型化,同时不给每个扩展相同的权力:钩子返回 decision,执行包装层做包装,终结 guard 只能拒绝,最终观测者只能观测。循环负责 session-start、pre-step 领取结算、工具执行后上下文缓冲和 stopping;`dsh-tools` 负责身份封存与五阶段执行流水线。它们的约定记录在 [architecture.md](../../../../docs/architecture.md)、各包 README、[核心拦截 decision](../../../../docs/subsystems/core.md#interception-decisions) 与[工具结构](../../../../docs/subsystems/tools.md)中。ACP 桥接会把 blocked 无步骤轮次中的首次 pre-step reject 结算为 `end_turn`,而钩子驱动的快照端到端验证可观测的桥接行为。
|
||||
规范拦截接口具有统一的类型化,同时不给每个扩展相同的权力:钩子返回 decision,执行包装层做包装,终结 guard 只能拒绝,最终观测者只能观测。循环负责 session-start、pre-step 领取结算、工具执行后上下文缓冲和 stopping;`dsh-tools` 负责身份封存与五阶段执行流水线。它们的约定记录在 [architecture.md](../../../../docs/architecture.md)、各包 README、[核心拦截 decision](../../../../docs/subsystems/core.md#interception-decisions) 与[工具结构](../../../../docs/subsystems/tools.md)中。ACP 桥接会把 blocked 无步骤轮次中的首次 pre-step reject 结算为 `end_turn`,而钩子驱动的快照端到端验证可观测的桥接行为。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-30-session-store-fork-api.md
|
||||
2026-06-30-session-store-fork-api.md: b634d3e561aa1142b7db1bed30158306a2318060
|
||||
2026-06-30-session-store-fork-api.zh.md: f985c93085cbc2dbb1b8a768dac82a40025ea54d
|
||||
2026-06-30-session-store-fork-api.zh.md: 410055a8052392ab515e410d6ba744ba786740ff
|
||||
|
||||
@@ -38,12 +38,12 @@ Host 通过 agent(智能体)注册表,以选定的种子和谱系创建子
|
||||
|
||||
**独立的 `ctx.sessionFork` 服务。** 较早的一版迭代曾把它作为独立服务交付;它过度套用了能力 seam 模式。代码没有可替换的后端、没有额外的事件面、没有独立的所有权生命周期,也没有超出 `ctx.sessions.create({ seed, meta })` 的持久化行为。保留独立包会迫使调用方为了在会话存储原语之上执行一层策略而去发现并安装第二个服务。
|
||||
|
||||
**两个函数:`snapshot()` 加 `fork()`。** 这保留了一个可复用的种子/元数据计算,但唯一支持的消费方会立即创建会话。它还使接口看起来比用户实际需要的具体操作更抽象。单一的 `fork()` 加显式 `boundary` 使 API 保持直接,同时仍支持对先前时间点的 fork。
|
||||
**两个函数:`snapshot()` 加 `fork()`。** 这保留了一个可复用的种子/元数据计算,但唯一支持的消费方会立即创建会话。它还使 API 看起来比用户实际需要的具体操作更抽象。单一的 `fork()` 加显式 `boundary` 使 API 保持直接,同时仍支持对先前时间点的 fork。
|
||||
|
||||
**静默裁剪未关闭轮次到最后一个已完成边界。** 这对 `dsh-subagent-fork` 是正确的——委托通常在父轮次仍然打开时开始,子会话应只继承已完成的前缀。但对常规的用户/会话分支而言是错误的,因为它隐藏了请求的 fork 点实际上不是合法边界这一事实,并且静默丢弃了父轮次的尾部。
|
||||
|
||||
## 后果
|
||||
|
||||
公开接口保持精简且易于发现:活跃会话分支是 `ctx.sessions` 的一部分,紧邻 `create({ seed })`,而非一个独立服务或一对两步辅助函数。持久化继续通过现有的 `session/created` 和 `session/flush` 行为运作:fork 出的子会话创建时便带有种子事件,因此现有后端只需持久化该种子一次,并在 header 中保存 `parentSession`/`seedLength`。
|
||||
公开 API 保持精简且易于发现:活跃会话分支是 `ctx.sessions` 的一部分,紧邻 `create({ seed })`,而非一个独立服务或一对两步辅助函数。持久化继续通过现有的 `session/created` 和 `session/flush` 行为运作:fork 出的子会话创建时便带有种子事件,因此现有后端只需持久化该种子一次,并在 header 中保存 `parentSession`/`seedLength`。
|
||||
|
||||
v1 范围仍然排除 ACP(Agent Client Protocol) `session/fork`、对未加载的已持久化会话的 fork、面向模型的工具,以及 subagent 重构。如果未来添加 ACP 方法,应在具备协议与快照覆盖后才声明支持该能力;本 Agent Note 不添加任何 ACP 协议行为,因此不需要 ACP 快照。fork 子会话的回放仍由现有的[种子边界测试 Agent Note](../testing/2026-06-22-fork-child-replay-seed-boundary.md) 覆盖;store、Host、载体与客户端的专项测试固定边界和对账约定,真实 Chromium 场景则固定组装后的消息操作与谱系树。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md
|
||||
2026-07-05-dynamic-workflows.md: 9b70e68e559727d4af323d600bccbcd9b2ade9bc
|
||||
2026-07-05-dynamic-workflows.zh.md: 8b63498fd7f82159bfc0cc3b5d29f3a151d84338
|
||||
2026-07-05-dynamic-workflows.zh.md: 084c683ee9a29176daf71637eba984bd074d986b
|
||||
|
||||
@@ -26,7 +26,7 @@ harness 可以将一个任务委派给一个子 agent(`dsh-tool-subagent`)
|
||||
|
||||
**信任前提**:工作流脚本与模型的 bash 访问具有相同的信任级别。引擎会约束有缺陷脚本的影响,并保证结果已 settled、值可安全表示为 JSON、取消后完全停稳;它不防御恶意代码。vm 上下文和 worker 线程不是安全边界:脚本可以逃逸到具有进程级权限的 Node API。沙箱化需要在此 seam 背后使用独立进程或 isolated-vm 引擎。
|
||||
|
||||
**为何选择 `node:worker_threads`**:每次运行获得一个非池化的 worker。vm 上下文限定了文档中说明的脚本接口范围,而消息端口 RPC 将 `agent()` 桥接到宿主侧的子循环。worker 防止脚本的同步工作阻塞宿主,提供序列化边界,并允许取消后强制终止。`isolated-vm` 因其维护状态和部署要求被否决。
|
||||
**为何选择 `node:worker_threads`**:每次运行获得一个非池化的 worker。vm 上下文限定了文档中说明的脚本 API,而消息端口 RPC 将 `agent()` 桥接到宿主侧的子循环。worker 防止脚本的同步工作阻塞宿主,提供序列化边界,并允许取消后强制终止。`isolated-vm` 因其维护状态和部署要求被否决。
|
||||
|
||||
宿主在发布前校验元数据并解析正文。私有枚举键 payload 映射定义协议格式;待启动记录、已发布子记录、单一取消信号、worker 死亡回收、结果优先级与 dispose 完全停稳,在此协议上保持 subagent run 约定。这些竞态算法由 [agent 作用域运行时设计 Agent Note](../architecture/2026-07-12-agent-scope-runtime-design.md#workflow-children-are-pending-starts-or-published-records) 定义。
|
||||
|
||||
@@ -40,7 +40,7 @@ harness 可以将一个任务委派给一个子 agent(`dsh-tool-subagent`)
|
||||
|
||||
一个 `workflow` 工具,镜像 `dsh-tool-subagent` 的同步形态:启动、await、`try/finally` dispose、abort 桥接 `exec.signal`、非 `completed` → `isError`。渲染意图:一张以调用的 `meta.name` 参数为标题的 `generic` 卡片(展示是参数的纯函数)。工具描述即面向模型的编写规范。使用策略以工具自身的 `tool:<toolName>` 提示词段落随工具发布(显式请求才使用的引导——工具引导存在于工具插件中,从不在部署 persona 中);harness 没有 ultracode 风格的 effort 门控。
|
||||
|
||||
对于顶层工具执行,同一消费方还会把运行及真正开始过的成员生命周期写入调用方父 Session,形成四类 log-only `tool-workflow/*` 事件。记录路径只观察、不控制执行:第一次 append 失败会禁用本运行后续写入并留下合法前缀,不改变工具结果。[`ui-workflow-run`](../../../../packages/client/ui-workflow-run/README.md) 通过 Conversation Node 引擎重建这些事实,形成独立 keyed Chat 行;现有 generic 工具行继续拥有自己的展示。持久化、回放、折叠与实时导航的详细决策见 [Chat 中的持久工作流运行](2026-08-10-durable-workflow-runs-in-chat.md)。
|
||||
对于顶层工具执行,同一消费方还会把运行及实际成员生命周期写入调用方父 Session,形成四类 log-only `tool-workflow/*` 事件。记录路径只观察、不控制执行:第一次 append 失败会禁用本运行后续写入并留下合法前缀,不改变工具结果。[`ui-workflow-run`](../../../../packages/client/ui-workflow-run/README.md) 通过 Conversation Node 引擎重建这些事实,形成独立 keyed Chat 行;现有 generic 工具行继续拥有自己的展示。持久化、回放、展开/收起与实时导航的详细决策见 [Chat 中的持久工作流运行](2026-08-10-durable-workflow-runs-in-chat.md)。
|
||||
|
||||
### 基础:subagent seam 上的结构化输出
|
||||
|
||||
@@ -58,7 +58,7 @@ worker 侧逻辑通过进程内 `MessageChannel` 运行,使 V8 覆盖率能够
|
||||
|
||||
- **后台收集**(启动工具 → run id → 完成通知 → 收集),与 bash/subagent 后台统一一起设计。
|
||||
- **日志化 + 恢复**(`resumeFromRunId`、缓存的 agent() 前缀):实现它会以脚本约定收紧的形式重新引入 CC 的确定性禁令(脚本目前可以读取时钟)。
|
||||
- **保存/打包的工作流**(`.deepseek/workflows/` 注册表、斜杠命令界面)和**脚本持久化到运行目录**(工具调用事件已经持久记录了脚本)。
|
||||
- **保存/打包的工作流**(`.deepseek/workflows/` 注册表、斜杠命令 API)和**脚本持久化到运行目录**(工具调用事件已经持久记录了脚本)。
|
||||
- **嵌套 `workflow()`**、**token `budget`**,以及 `effort`/`isolation`/`agentType` agent 选项(每个都会明确拒绝,并在消息中注明其已延迟实现)。
|
||||
- **整体运行的挂钟超时**:取消总能释放调用方(result 在宽限期内 settle),因此总运行时间上限是后台重设计的策略旋钮,不是此处的正确性需求。
|
||||
- **超越 worker 线程的引擎加固**:在同一 seam 背后使用 isolated-vm 或独立进程引擎(真正的沙箱化;内存限制)。
|
||||
@@ -67,14 +67,14 @@ worker 侧逻辑通过进程内 `MessageChannel` 运行,使 V8 覆盖率能够
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **宿主侧的恶意值防护**(无 trap 代理拒绝、从不调用访问器的描述符遍历、realm 侧预渲染抛出值、realm 构建的 promise/array/error 克隆加结构化 fatal 识别):否决。每项防御针对的都是信任前提所接受的作者,而线程的序列化边界已经从构造上保证跨 realm 值的处理对所有输入都有确定结果。
|
||||
- **进程内 `node:vm` 执行**:机械上最简——无 RPC、无线程——但 `start()` 会在脚本的初始同步切片期间阻塞调用方,第一个 await 之后的同步自旋无法在进程内终止(vm `timeout` 仅覆盖第一个切片),且 `dispose()` 只能在宿主循环上放弃一个未 settle 的脚本。worker 线程引擎保持相同的 vm 上下文脚本接口,同时解除宿主阻塞并使终止成为现实。
|
||||
- **进程内 `node:vm` 执行**:机械上最简——无 RPC、无线程——但 `start()` 会在脚本的初始同步切片期间阻塞调用方,第一个 await 之后的同步自旋无法在进程内终止(vm `timeout` 仅覆盖第一个切片),且 `dispose()` 只能在宿主循环上放弃一个未 settle 的脚本。worker 线程引擎保持相同的 vm 上下文脚本 API,同时解除宿主阻塞并使终止成为现实。
|
||||
- **后台执行作为默认**(CC 的形态):延迟。前台同步与 `dsh-tool-subagent` 的当前形态一致,后台语义应在 bash、subagent 和工作流之间统一设计一次,而非逐工具设计。
|
||||
- **工作流层为 `agent({schema})` 做 JSON 解析**:在一个消费方重复 seam 关注点,而 seam 的能力标志仍不诚实地为 `false`。
|
||||
- **Meta 嵌入脚本中作为 `export const meta = {...}`**(CC 的确切格式):保持脚本自包含且 CC 脚本可直接使用,但获取 meta 需要在宿主上执行模型编写的文本。即使一个空的限时 vm 上下文也无法约束脚本控制的 getter(当宿主读取结果对象时)。JSON 参数消除了扫描器、执行和宿主自旋漏洞;代价是 CC 脚本的 meta 头必须移入参数(正文保持可直接使用)。
|
||||
- **`ValueSchemaSpec` 作为 `outputSchema` 协议类型**:面向作者的形式如今具有等价词汇,但工作流提供的是来自其他 realm 的原始 JSON Schema 数据;将这类运行时数据假装成可信的作者声明,会跳过原始 schema 断言边界。
|
||||
- **schema 对象库(zod 或本仓库的 schemastery)用于结构化输出子集**:schema 是协议数据——纯 JSON,跨越 `agent({schema})` 中的 vm realm 边界并逐字落入强制工具的 parameters——正是活 schema 对象无法存在的位置;在运行时消费原始 JSON Schema 需要在其上加一个第三方转换器(zod core 只输出 JSON Schema,不能反向),且会在 schemastery 的配置角色旁边放置第二种 schema 语言。
|
||||
- **ajv 用于值校验**:它校验完整 JSON Schema,因此子集门控——模块的真正要点,因为每个被接受的关键字都必须是 harness 强制执行的——无论如何仍需手写;它通过 `new Function` 编译校验器;且它将成为 dsh-tools 的第一个运行时依赖,仅为替换约 70 行的值遍历器,而带路径且逐一报告所有违规的错误报告无论如何都是自定义的。
|
||||
- **提供方 JSON 模式代替捕获工具**:它保证有效 JSON,不保证 schema 一致性,且它与工具调用的交互不明确。捕获工具保留了轮次内的校验重试。提供方侧的严格工具 schema 后续可以在不改变本设计的情况下收窄接受的子集。
|
||||
- **提供方 JSON 模式代替捕获工具**:它保证有效 JSON,但不保证符合 schema,且它与工具调用的交互不明确。捕获工具保留了轮次内的校验重试。提供方侧的严格工具 schema 后续可以在不改变本设计的情况下收窄接受的子集。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-06-approval-seam.md
|
||||
2026-07-06-approval-seam.md: 46d3186e226f344ee1239dcbdfb6f286b88d84ce
|
||||
2026-07-06-approval-seam.zh.md: ef4ccf5fd2b54888a648737866ff6f5fe1678882
|
||||
2026-07-06-approval-seam.zh.md: 022db486a774d952a8b4d98bec15a6fe16535242
|
||||
|
||||
@@ -51,9 +51,9 @@ tool/result "escalated" — this one call ran under the wider mode; the gra
|
||||
|
||||
#### seam:机制与策略分离
|
||||
|
||||
经过校验并成功追加 `approval/asked` 后,服务将 `approval/request` waterfall 解析为 `allowed-once`、`rejected`、`cancelled` 或 `unavailable`。服务沿用只读的请求标识和 signal,将中止视为 `cancelled`,把应答者失败和无效返回统一转换为 `unavailable`,丢弃迟到的应答,并追加配对的 `approval/decided` 事件。提交前的审计失败会拒绝;追加后的观察者失败无法撤销权威事件。`allowed-once` 仅授权所询问的操作,而 `request()` 会拒绝打开轮次之外的调用,以保证审计对留在持久提交边界内。
|
||||
经过校验并成功追加 `approval/asked` 后,服务将 `approval/request` waterfall 解析为 `allowed-once`、`rejected`、`cancelled` 或 `unavailable`。服务沿用只读的请求标识和 signal,将中止视为 `cancelled`,把应答者失败和无效返回统一转换为 `unavailable`,丢弃迟到的应答,并追加配对的 `approval/decided` 事件。提交前的审计失败会拒绝;追加后的观察者失败无法撤销权威事件。`allowed-once` 仅授权所询问的操作,而 `request()` 会拒绝进行中的轮次之外的调用,以保证审计对留在持久提交边界内。
|
||||
|
||||
应答者是 `approval/request` waterfall 监听器。零监听器会一路委派至 `unavailable`;识别该 agent 的监听器占用先到先得的决策槽,而不识别的监听器必须调用 `next()` 委派。监听器会随其 fiber 一同 dispose(资源释放),因此卸载通道后,请求会在故障时默认被拒绝。由于兄弟插件的注册顺序不确定,部署应组合一个终端应答者,并保留 `prepend` 给「决策或委派」门禁。
|
||||
应答者是 `approval/request` waterfall 监听器。零监听器会直接落到 `unavailable`;识别该 agent 的监听器占用先到先得的决策槽,而不识别的监听器必须调用 `next()` 委派。监听器会随其 fiber 一同 dispose(资源释放),因此卸载通道后,请求会在故障时默认被拒绝。由于兄弟插件的注册顺序不确定,部署应组合一个终端应答者,并保留 `prepend` 给「决策或委派」门禁。
|
||||
|
||||
`ApprovalRequest` 携带发起请求的 `agent`、`toolName`、可选的精确 `callId`、人类可读的 `reason` 和可选的 `signal`。它使用 `CallId` brand 而不导入依赖本 seam 的 `dsh-tools`。通道适配器可按 `callId` 关联任何更丰富的调用状态;审批请求本身不重复携带工具参数。
|
||||
|
||||
@@ -63,7 +63,7 @@ tool/result "escalated" — this one call ran under the wider mode; the gra
|
||||
|
||||
#### 每会话策略层
|
||||
|
||||
seam 还拥有[沙箱 Agent Note](2026-07-06-sandbox.md) 所描述的会话级 `'ask' | 'never'` 策略。生效策略由日志中记录的切换在部署默认值之上折叠而成。`'never'` 会在任何应答者运行之前,于 `request()` 内部解析为 `rejected`;`'ask'` 则派发请求,否则一路委派至 `unavailable`。两个当前值都会在每次模型请求前加入原子化的运行时上下文快照,因此策略切换无需单独叙述;每次批准请求仍会记录审计对。
|
||||
seam 还拥有[沙箱 Agent Note](2026-07-06-sandbox.md) 所描述的会话级 `'ask' | 'never'` 策略。生效策略由日志中记录的切换在部署默认值之上折叠而成。`'never'` 会在任何应答者运行之前,于 `request()` 内部解析为 `rejected`;`'ask'` 则派发请求,否则一路委派至 `unavailable`。两个当前值都会在每次模型请求前加入原子化的运行时上下文快照,因此策略切换无需单独叙述;每次审批请求仍会记录审计对。
|
||||
|
||||
#### ACP 应答者
|
||||
|
||||
@@ -93,7 +93,7 @@ ACP 桥只应答其会话映射所拥有的精确 agent 对象。它携带既有
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **单一注册提供方而非 waterfall 监听器**:否决。`registerProvider()` 接口迫使所有组合问题——允许列表预过滤、外部钩子决策者、脚本化测试应答、人类前面的策略门禁——都塞进一个提供方实现。waterfall 直接复用运行时已有的组合能力、缺失时默认拒绝行为和 HMR(热模块替换)资源释放机制;seam 的 JSDoc 以约定固定单决策槽语义,而非发明一个提供方注册表。
|
||||
- **单一注册提供方而非 waterfall 监听器**:否决。`registerProvider()` API 迫使所有组合问题——允许列表预过滤、外部钩子决策者、脚本化测试应答、人类前面的策略门禁——都塞进一个提供方实现。waterfall 直接复用运行时已有的组合能力、缺失时默认拒绝行为和 HMR(热模块替换)资源释放机制;seam 的 JSDoc 以约定固定单决策槽语义,而非发明一个提供方注册表。
|
||||
- **在 ACP 桥中内联 `tools/pre-execute` 权限门禁**:否决。对桥拥有的每次调用都弹出提示,会将请求策略硬编码进传输层,无法服务第二个发起方(沙箱升级发生在执行开始之后,没有 pre-execute 时刻),且钩子产生的 `ask` 决策没有共享机制。
|
||||
- **通用用户交互 seam(`ctx.userInteraction`)**:否决作为审批机制。二者骨架相似(按 agent 路由、阻塞等待人类、处理缺失),但审批的约定在每个关键维度上都更窄:封闭的结果词汇而非自由文本、附着在工具调用上的协议原生提示而非通用表单、强制的缺失时失败关闭、以及审计事件。因此审批不走已交付的 `packages/interaction/user-interaction` / `ask_user_question` 信息征集路径——信息征集表单不是权限提示,自由文本应答不是封闭结果;如果二者将来趋同,共享提供方管道仍然开放。
|
||||
- **`dsh-tools` 中的静态可选注入**:否决。vendor 的 Cordis `Inject` 类型没有 optional 标志——对象形式将服务名映射到拦截配置,声明的 inject 会阻塞 fiber。`ctx.get('approval')` 是文档化的机会性消费模式(`tool-bash` 的 owner-token 查找、loop 的持久化探测),按调用读取存在性,跨 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/implemented/feature/2026-07-06-sandbox.md
|
||||
2026-07-06-sandbox.md: d37e9efc5ce8b1267c74591cb9edc01bef68ab8d
|
||||
2026-07-06-sandbox.zh.md: d3b74d441f72fc4c218979c327546031fd1b24f1
|
||||
2026-07-06-sandbox.zh.md: 42bdf546c2ea273d807b0c5d394da9ba8de42812
|
||||
|
||||
@@ -146,7 +146,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自能力边
|
||||
- **同一工具调用内自动重试**:否决。日志无法重建的隐藏重入:一个 `tool/call` 会产生两次具有不同策略的执行——重试是一次新的带有自身参数和结果事实的已记录调用。
|
||||
- **无条件公布升级字段**:否决。在 `dsh-bash-local` 下它们是死杠杆——公布 harness 无法兑现的选项会制造注定失败的授权;能力门控仅需注册时一次读取。
|
||||
- **默认值相对的升级阶梯(仅公布比执行器注册时默认值更宽的模式)**:否决。按会话覆盖使默认值成为错误的基线——切换到比默认值更窄的会话恰恰失去它需要的杠杆,而在 `danger-full-access` 默认值下字段完全消失,同时一个被覆盖为 `read-only` 的会话仍处于约束中却没有升级路径。枚举固定封闭的目标词汇;严格放宽是针对会话有效模式的按调用执行检查。
|
||||
- **按会话动态工具 schema**:否决。schema 设计上是注册表全局的(一套 assembly 词汇、固定 header 快照约定),按会话重新注册只能买到执行时严格放宽检查已保证的东西,代价是按会话的 schema 表面和每次切换的 header 变动。
|
||||
- **按会话动态工具 schema**:否决。schema 设计上是注册表全局的(一套 assembly 词汇、固定 header 快照约定),按会话重新注册只能买到执行时严格放宽检查已保证的东西,代价是按会话的 schema 和每次切换的 header 变动。
|
||||
- **将重试硬匹配到先前的拒绝**:否决。命令字符串同一性脆弱(引号、`workdir`、env 前缀、作为失败阶段重试的管道)——要么误拒诚实的重试,要么被轻易满足;真正的边界是人看到命令 + 理由。仅在 `allow_always` 授权存储需要机器可检查的范围时才重新考虑。
|
||||
- **通用 `env/state` facts map 加拥有者服务**:否决。approval 和沙箱独立组合,因此任何一方的状态都不应拖入第三个包;单键 fold 各自是一个 `findLast`,拥有者服务自然消解;没有跨旋钮的不变式,因此原子多键补丁无收益。
|
||||
- **通过 `agent.inject()` 加总线事件逐次叙述切换**:否决。独立通知会暴露归属方顺序和中间组合,而一次 pre-step 组合可以原子排队完整的当前状态。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md
|
||||
2026-07-07-mcp-client-plugin.md: 24828586645778294ad7bdafc6fd13a9bfb6745f
|
||||
2026-07-07-mcp-client-plugin.zh.md: 8f58c9359ca8447717cc353f97f1333370fa6fda
|
||||
2026-07-07-mcp-client-plugin.zh.md: 4f0e438e4f15e8b990a987c5af3eaed19fa3e163
|
||||
|
||||
@@ -200,7 +200,7 @@ v1 否决。它能防止跨服务器冲突,但无法将 MCP 注册与原生 ha
|
||||
## 后果
|
||||
|
||||
- 每个 MCP 服务器只需 `cordis.yml` 中的一条配置即完成集成:`serverName: filesystem` 加一条 stdio 命令(或一个 Streamable HTTP URL),就能将 `mcp__filesystem__read_file` 放入模型的工具列表,可调用,协议上使用原始的 `read_file`。
|
||||
- 公开名称是会话历史和权限/配置表面的一部分;命名算法是由测试固定的 v1 约定,发布后变更即为破坏性变更。
|
||||
- 公开名称是会话历史和权限/配置 API 的一部分;命名算法是由测试固定的 v1 约定,发布后变更即为破坏性变更。
|
||||
- `mcp__<serverName>__` 限定符在每个名称上消耗 token。已接受:描述和 JSON Schema 在工具定义 token 中占主导,而限定符换来了稳定标识、冲突隔离和 MCP 全局策略匹配模式(`mcp__*`、`mcp__github__*`)。
|
||||
- **MCP SDK 稳定性**:`@modelcontextprotocol/sdk` 仍在演进中;破坏性变更需要更新桥接。版本已固定,且该 SDK 被广泛采用(Claude Desktop、Cursor、VS Code),因此破坏性变更不太可能悄然发生。
|
||||
- **工具 schema 质量**:MCP 服务器可能暴露描述不佳的工具(模糊的描述、不完整的 JSON Schema)。harness 原样透传——垃圾进垃圾出;这是服务器作者的责任,不是桥接的。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-08-background-subagent-tasks.md
|
||||
2026-07-08-background-subagent-tasks.md: 2c2080d3520f8f784102af1344bea09875e6e61c
|
||||
2026-07-08-background-subagent-tasks.zh.md: 64c8bf898532ab8ab754dc950660df69fe2c39ad
|
||||
2026-07-08-background-subagent-tasks.zh.md: bf7869c694a63f2b8d7da096be5d6e87b4d7ed7f
|
||||
|
||||
@@ -18,7 +18,7 @@ subagent 需要与其他长时间运行的工具相同的启动、收集、列
|
||||
|
||||
前台调用保留其同步约定:等待提供方启动和 `run.result`;仅当状态为 `completed` 时返回最终文本;将其他终止原因映射为出错的工具结果;并且始终在返回前释放该运行。
|
||||
|
||||
对于后台调用,工具会验证父级,并在调用 `ctx.tasks.start()` 前拒绝已中止的执行信号。任务运行时会在调用生产者启动器前,预检控制器和拥有者清理。该启动器创建独立的 `AbortController` 并启动 `ctx.subagents.start()`;返回 id 之后,工具调用的信号不再拥有该子级。
|
||||
对于后台调用,工具会验证父级,并在调用 `ctx.tasks.start()` 前拒绝已中止的执行信号。任务运行时会在调用生产者启动器前,预检控制 API 和拥有者清理。该启动器创建独立的 `AbortController` 并启动 `ctx.subagents.start()`;返回 id 之后,工具调用的信号不再拥有该子级。
|
||||
|
||||
任务注册按以下方式映射 subagent seam:
|
||||
|
||||
@@ -53,7 +53,7 @@ agent 和日志可能以会话为作用域,但任务注册表和可预测 id
|
||||
|
||||
### 增量子 transcript 输出
|
||||
|
||||
将子级历史以流式方式写入父级,会模糊日志边界,并使提供方行为分化。此接口只公开最终输出;更丰富的观察应由会话或 UI 工具承担。
|
||||
将子级历史以流式方式写入父级,会模糊日志边界,并使提供方行为分化。此工具只公开最终输出;更丰富的观察应由会话或 UI 工具承担。
|
||||
|
||||
## 测试
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md
|
||||
2026-07-08-self-referential-cordis-toolset.md: 80f9a402562940727760a3686eb3c24866a053af
|
||||
2026-07-08-self-referential-cordis-toolset.zh.md: 665387a9fb24bf0e6fc04fdfb1ced88b932742ad
|
||||
2026-07-08-self-referential-cordis-toolset.zh.md: 01d40479f2ba190fdf5485b75f4aa77e769e374f
|
||||
|
||||
@@ -28,7 +28,7 @@ vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节
|
||||
|
||||
### 沙箱语义
|
||||
|
||||
挂载代码以异步函数体的形式在一个新的 vm realm 中运行。其文档化的接口面将文件、网络、进程和定时器访问引导至 Cordis 服务,使挂载保持可审视和可释放。宿主 realm 的辅助手段仍然使 Node 逃逸成为可能,这与信任姿态一致。`vmTimeoutMs` 仅约束同步执行部分。
|
||||
挂载代码以异步函数体的形式在一个新的 vm realm 中运行。其文档化的 API 将文件、网络、进程和定时器访问引导至 Cordis 服务,使挂载保持可审视和可释放。宿主 realm 的辅助手段仍然使 Node 逃逸成为可能,这与信任姿态一致。`vmTimeoutMs` 仅约束同步执行部分。
|
||||
|
||||
沙箱全局变量刻意精简:一个带标签的直写 `console`(在宿主 stdout/stderr 上输出 `[cordis:<id>] …`,这样在挂载调用之后很久才触发的监听器输出仍能落到用户可见的地方)、`harness.defineTool` / `harness.registerTool` 注册对、新 vm 上下文缺少的编码原语(`btoa`/`atob` 作为基于 `Buffer` 的宿主闭包——这是一个经过审批的例外,`Buffer` 本身从不暴露——加上 `TextEncoder`/`TextDecoder`),以及对被扣留的 Node API 的可调用陷阱(`require`、`setTimeout`/`setInterval`/`setImmediate`/`clearTimeout`/`clearInterval`、`fetch`),这些陷阱会抛出一条重定向消息指明 cordis 替代方案。只有函数形态的全局变量才设陷阱;`process` 和 `Buffer` 保持 `undefined`,这样 `typeof` 特性探测保持惰性而不会引爆一个抛异常的访问器。
|
||||
|
||||
@@ -48,7 +48,7 @@ vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节
|
||||
|
||||
### 生成的 API 目录
|
||||
|
||||
`cordis_inspect` 从生成的目录提供 API 和事件数据,而非维护一份重复的表格。生成器复用 Cordis 目录的 AST 扫描,输出服务摘要、签名、原始服务方法与事件 JSDoc、事件模式、引用的类型声明以及继承的上下文接口面。有歧义的类型名被省略,过大的声明被标记为截断。
|
||||
`cordis_inspect` 从生成的目录提供 API 和事件数据,而非维护一份重复的表格。生成器复用 Cordis 目录的 AST 扫描,输出服务摘要、签名、原始服务方法与事件 JSDoc、事件模式、引用的类型声明以及继承的上下文 API。有歧义的类型名被省略,过大的声明被标记为截断。
|
||||
|
||||
新鲜度像所有生成产物一样受门禁约束:`pnpm run verify-cordis-api`(在 `doc-sync` 中)在内存中重新生成并在有任何 diff 时失败,因此 JSDoc 或公开签名变更如果不重新生成模型读取的目录就无法合入。运行时 inspect 工具将目录与活跃运行时取交集而非直接转储:宽泛报告把有目录条目的活跃服务渲染为摘要 + 签名,把没有目录条目的活跃服务(挂载提供的)渲染为名称 + 所属 fiber,简要列出有目录条目但无活跃提供方的服务,再附上引用的类型形状。精确名称报告渲染一个活跃服务或事件,并把原始 JSDoc 紧靠在每个签名之前;让该细节按需出现,避免探索性列表承担其 token 成本。
|
||||
|
||||
@@ -66,7 +66,7 @@ vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节
|
||||
|---|---|---|
|
||||
| Schema 正确性 | `parameters` 仍然是模型编写的 JSON,需要统一 schema 校验,只是提前了一步 | 同样的校验在沙箱边界运行,同样的指导性错误信息 |
|
||||
| 代码字段 | `execute` 函数体仍然是 vm 中模型编写的 JS;realm 和服务调用的正确性问题不变 | 一个沙箱、一条规范化路径、一处受保护的注册 |
|
||||
| 能力覆盖面 | 仅限工具;监听器、服务、`inject` 关系各需另一个结构化工具——接口面无限增长 | 一套词汇(cordis 插件)覆盖当前和未来的所有效果 |
|
||||
| 能力覆盖面 | 仅限工具;监听器、服务、`inject` 关系各需另一个结构化工具——API 无限增长 | 一套词汇(cordis 插件)覆盖当前和未来的所有效果 |
|
||||
| 跨挂载组合 | 在工具注册载荷中无法表达 | 原生 `provide`/`inject`,普通的 cordis 语义 |
|
||||
| 可审视性 | 注册的东西无法在插件列表中显示为插件 | 模型挂载的正是 `cordis_inspect` 渲染的 |
|
||||
| 模型易用性 | 对最常见的单一场景有优势(无插件样板) | 通过 mount 描述中的规范示例加边界错误信息教会正确调用来缓解 |
|
||||
@@ -77,7 +77,7 @@ vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节
|
||||
|
||||
**新增 `cordis/mount` 会话事件。** 一个持久事件记录每次挂载的源码和名称,有明确先例(`hook/invoked`、`compact/start`)。v1 中予以否决:挂载和卸载已经作为 `tool/call` / `tool/result` 对可见,工具集变化已经作为完整的变更 request header 被记录,因此专用事件只会重复记录。如果审计用例需要在工具调用之外取得挂载的源码和名称,日后仍可添加。
|
||||
|
||||
**加固的/能力受限的沙箱。** 对 Node 内置模块设陷阱并向挂载代码提供白名单门面而非原始上下文,可能暗示意图是为安全而沙箱化。这里明确不是:陷阱和门面收窄的是挂载代码所见的*接口面*——将其引导至 cordis 服务、远离易泄漏的 Node 内置模块和框架内部——目的是正确性和封堵未受保护的上下文逃逸,但门面暴露的能力(`ctx.bash`、`ctx.fs`、`ctx.web`)触及真实运行时,因此它不是安全边界。真正的安全边界(独立进程、权限提示)超出了一个开发/显式启用工具集的范围,且会与其核心目的——将活跃运行时交给模型——相冲突。
|
||||
**加固的/能力受限的沙箱。** 对 Node 内置模块设陷阱并向挂载代码提供白名单门面而非原始上下文,可能暗示意图是为安全而沙箱化。这里明确不是:陷阱和门面收窄的是挂载代码所见的 *API*——将其引导至 cordis 服务、远离易泄漏的 Node 内置模块和框架内部——目的是正确性和封堵未受保护的上下文逃逸,但门面暴露的能力(`ctx.bash`、`ctx.fs`、`ctx.web`)触及真实运行时,因此它不是安全边界。真正的安全边界(独立进程、权限提示)超出了一个开发/显式启用工具集的范围,且会与其核心目的——将活跃运行时交给模型——相冲突。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md
|
||||
2026-07-10-agent-session-identity-and-log-location.md: 0b21193dbbfb05f78eb845f4a6893f6011584497
|
||||
2026-07-10-agent-session-identity-and-log-location.zh.md: b310db258f781cb5326e4bfe9e9daf3a6c1793e7
|
||||
2026-07-10-agent-session-identity-and-log-location.zh.md: ee42d45bc06b0bb3b5d5635364a5b5c068ccd59e
|
||||
|
||||
@@ -29,7 +29,7 @@ interface SessionPersistence {
|
||||
|
||||
`path` 是该后端为 `meta` 保留的专用日志的本地绝对路径;`kind` 标识其表示形式。JSONL 使用解析后的根目录和路径辅助函数返回 `{ kind: 'jsonl', path }`。SQLite 以及任何无法诚实提供逐会话本地产物的后端均返回 `undefined`。该查询不会创建或刷写任何内容,因此即使文件尚不存在,也可以报告按需创建的目标路径。
|
||||
|
||||
面向模型的 bash 包拥有一个 `ctx.bashEnv` 注册表。贡献方声明稳定名称、它可能返回的每个 `DSH_*` 键、每个键的说明,以及 `resolve(execution: ToolExecution)`。贡献方名称重复、键所有权重复、使用保留键、声明格式错误、运行时输出未声明或输出不是字符串时,系统都会明确失败。注册属于 Cordis effect,并随贡献插件的 fiber 一同移除。`list()` 无需运行解析器即可公开声明,从而让环境接口可供诊断工具和未来的提示词/UI 消费方枚举。
|
||||
面向模型的 bash 包拥有一个 `ctx.bashEnv` 注册表。贡献方声明稳定名称、它可能返回的每个 `DSH_*` 键、每个键的说明,以及 `resolve(execution: ToolExecution)`。贡献方名称重复、键所有权重复、使用保留键、声明格式错误、运行时输出未声明或输出不是字符串时,系统都会明确失败。注册属于 Cordis effect,并随贡献插件的 fiber 一同移除。`list()` 无需运行解析器即可公开声明,从而让环境 API 可供诊断工具和未来的提示词/UI 消费方枚举。
|
||||
|
||||
注册表会为每次前台和后台 bash `ToolExecution` 重新构建受信任的覆盖层:
|
||||
|
||||
@@ -78,7 +78,7 @@ bash 工具说明只讲解持久约定:当前 harness 环境事实通过受管
|
||||
|
||||
**让每个持久化后端直接注册 bash 环境。** 这会反转依赖方向,让存储层依赖某一个消费方,并迫使未使用 bash 的部署也引入它。钩子仍然需要 `locate()`。
|
||||
|
||||
**增加面向模型的 `session_info` 工具。** bash 已经提供查询接口,新增工具只会多出 schema 和一次调用;注册表可以扩展至未来的环境事实,无需为每项事实增加一个工具。
|
||||
**增加面向模型的 `session_info` 工具。** bash 已经提供查询 API,新增工具只会多出 schema 和一次调用;注册表可以扩展至未来的环境事实,无需为每项事实增加一个工具。
|
||||
|
||||
## 影响
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md
|
||||
2026-07-16-harness-level-loop.md: 8fb89c00a18c77e8f1d14eb116fd3bd7d5789fad
|
||||
2026-07-16-harness-level-loop.zh.md: 88a455f22942e406ee0e4168c39131d4469c1b07
|
||||
2026-07-16-harness-level-loop.zh.md: e2edb47decf98faa16b56d6a6572535a5c93c61f
|
||||
|
||||
@@ -124,6 +124,6 @@ Codex 提供了这里采用的最小可观察目标 UX:一个附着于聊天
|
||||
- **没有时间调度器**——间隔 `/loop`、cron、主动维护以及云端或桌面调度不在本决策范围内。
|
||||
- **没有通用 loop 日志或执行世界回退**——会话重放会重建目标历史,而不会恢复此前文件、进程、环境、凭据或外部副作用。Ralph 把当前工作区作为权威,并且没有跨运行日志。
|
||||
- **没有目标反思器**——concern 事件、自动无进展启发式、由独立反思器执行的目标修订、卡住模式检测与 `loop_split` 均未实现。人类可以直接编辑、暂停、清除或恢复目标。
|
||||
- **Ralph 策略仍然狭窄**——一个 Round 创建一个全新子 agent;Round 内扇出、评估器/工作者角色分离、动态 provider/模型选择与从结构上禁止递归调用 Ralph 工具都需要独立策略表面。提示词指导不是强制执行。
|
||||
- **Ralph 策略仍然狭窄**——一个 Round 创建一个全新子 agent;Round 内扇出、评估器/工作者角色分离、动态 provider/模型选择与从结构上禁止递归调用 Ralph 工具都需要独立策略 API。提示词指导不是强制执行。
|
||||
- **Ralph 不会重试失败的子 agent**——普通失败会保留失败 Round 与上一份有效交接,而致命工作流基础设施错误可能在该状态可用前结束。重试次数、退避与更丰富的失败传输需要独立的策略与边界设计。
|
||||
- **可移植 UI 仍较朴素**——TUI 渲染纯文本目标状态和通用 Ralph 卡片。ACP 只承载已提交的助手文本;系统没有持续状态组件、可重连命令输出、模态目标编辑器,ACP、无头 CLI 与 JSON-RPC 也没有命令平面。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md
|
||||
2026-07-19-fresh-agent-ralph-workflow-tool.md: e0f152d0e46eebe09d9d961eb241aa18cca403d0
|
||||
2026-07-19-fresh-agent-ralph-workflow-tool.zh.md: 02b1f0421263db928aff354d04abaf6bdced3249
|
||||
2026-07-19-fresh-agent-ralph-workflow-tool.zh.md: f0fca67efe41e6eae958b44d834efec3b47266ba
|
||||
|
||||
@@ -51,7 +51,7 @@ Ralph 插件的 `subagentProvider` 默认为 `spawn`。每次调用前,它要
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **把 Ralph 放进同会话目标驱动器** — 拒绝,因为 Goal Round 有意保留同一段对话,而 Ralph 的定义性属性是每个 Round 使用全新上下文;合并两者会让目标生命周期与子 agent 编排无法分离。
|
||||
- **在通用工作流工具上暴露 `fresh` 或循环标志** — 拒绝,因为模型编写的脚本表面应保持通用且与提供方无关;Ralph 的固定报告协议和停止策略值得拥有一个可评审消费方。
|
||||
- **在通用工作流工具上暴露 `fresh` 或循环标志** — 拒绝,因为模型编写的脚本 API 应保持通用且与提供方无关;Ralph 的固定报告协议和停止策略值得拥有一个可评审消费方。
|
||||
- **为了方便重放而使用 `subagent_fork`** — 拒绝,因为继承的已完成轮次是隐式、不断增长的交接状态,并违反全新上下文约定。工作区加一份结构化报告即可重放,无需插入人为取消记录。
|
||||
- **让工具直接调用 subagent seam** — 拒绝,因为现有工作流引擎已经拥有前台编排、结构化子 agent、取消传播、工作线程终止、事件和完全停稳后的 dispose。复用它可以展示插件组合,而不是构建第二个循环运行时。
|
||||
- **静默截断大型报告** — 拒绝,因为截断可能删除状态证据或下一步,却仍看似权威交接。生产者必须在配置边界内发出有效报告。
|
||||
@@ -62,7 +62,7 @@ Ralph 插件的 `subagentProvider` 默认为 `spawn`。每次调用前,它要
|
||||
- Goal Round 与 Ralph Round 保持不同概念:前者是一次同会话续行轮次,后者是前台工作流中的一个全新子 agent。
|
||||
- 工作区成为权威跨 Round 记忆,因此工作者必须检查和验证工作区,而不能信任叙事性交接。
|
||||
- 宽裕的 Round 上限允许大量自治工作,而部署配置仍会限制子 agent 数量,并且每次交接始终受大小约束。
|
||||
- 提供方路由与可降低的每次运行子 agent 上限成为显式的工作流启动关注点,但不扩展脚本或普通工作流工具表面。
|
||||
- 提供方路由与可降低的每次运行子 agent 上限成为显式的工作流启动关注点,但不扩展脚本或普通工作流工具集。
|
||||
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md
|
||||
2026-07-19-model-facing-goal-tools.md: 2f2a1c2319dec96df9da83efbe92f1149001b8f2
|
||||
2026-07-19-model-facing-goal-tools.zh.md: 481ba1abb8ac66dc379d69f546e08138f42a0b22
|
||||
2026-07-19-model-facing-goal-tools.zh.md: 814b685f528619985eebe9ea9119dc9422ffc49d
|
||||
|
||||
@@ -8,7 +8,7 @@ Status: implemented
|
||||
|
||||
持久目标领域有意把生命周期动词提供给插件,而不直接提供给模型。模型仍然需要一个小型控制 API,用于发现当前目标、根据人类意图创建目标并改变其生命周期。仅靠提示词指导无法确定是谁授权了一次变更:子智能体、注入的插件消息、陈旧的模型轮次或恢复后的会话都可能产生相同的工具参数。
|
||||
|
||||
该表面还需要保持持久状态与实时执行权限之间的分离。恢复或 fork(派生)后的会话可以回放活跃目标,但初始处于未激活状态;后续人类提出“继续”之类的请求时,模型应能重新激活目标,而无需用户使用字面命令。相反,已接纳的自主 Goal Round 必须能够报告完成或持续阻塞,却不能因此获得编辑、暂停、恢复或替换人类目标的权限。
|
||||
该工具 API 还需要保持持久状态与实时执行权限之间的分离。恢复或 fork(派生)后的会话可以回放活跃目标,但初始处于未激活状态;后续人类提出“继续”之类的请求时,模型应能重新激活目标,而无需用户使用字面命令。相反,已接纳的自主 Goal Round 必须能够报告完成或持续阻塞,却不能因此获得编辑、暂停、恢复或替换人类目标的权限。
|
||||
|
||||
## 决策
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md
|
||||
2026-07-19-persisted-same-session-goal-domain.md: bc6c9ec14c16b20bc8434bac0f1eb6070ea8920d
|
||||
2026-07-19-persisted-same-session-goal-domain.zh.md: 7c8af321ec78779ecbf4e5e00805ef6f152b2aa1
|
||||
2026-07-19-persisted-same-session-goal-domain.zh.md: f1d87837ea2276a5ac379a493310db165e243afd
|
||||
|
||||
@@ -28,7 +28,7 @@ Status: implemented
|
||||
|
||||
最多只有一个当前目标。创建要求不存在未完成的当前目标,并始终生成该会话此前未使用过、修订号为一的 id;已完成目标可以被替换。其他每次变更都携带预期的 `GoalRef`,陈旧的 id 或修订号会被拒绝。仅当回合上限仍有余量时,暂停或阻塞阶段以及已解除激活的活跃目标才能恢复。领域层校验阻塞原因的形状,但会把原因代码和是否阻塞的决策留给策略消费者。
|
||||
|
||||
从任何种子构建的缓存都以未激活状态开始,每次 `agent/session-start` 边沿也会再次解除激活。`GoalService.disarm(agent)` 还允许生命周期所有者移除进程内权限,而不写入会话事件、不改变修订号,也不发出 `goal/changed` 通知。因此,会话恢复、fork 和继续执行驱动器替换都会保留持久化目标与历史,但绝不会自行启动工作。后续人类提示词可由模型解释,其策略表面可以显式调用恢复操作并激活目标。
|
||||
从任何种子构建的缓存都以未激活状态开始,每次 `agent/session-start` 边沿也会再次解除激活。`GoalService.disarm(agent)` 还允许生命周期所有者移除进程内权限,而不写入会话事件、不改变修订号,也不发出 `goal/changed` 通知。因此,会话恢复、fork 和继续执行驱动器替换都会保留持久化目标与历史,但绝不会自行启动工作。后续人类提示词可由模型解释,其策略 API 可以显式调用恢复操作并激活目标。
|
||||
|
||||
### 服务边界
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md
|
||||
2026-07-19-plugin-command-registration.md: f2ea6fca14ef5a0d7057d2652ee5f511fc6c15e4
|
||||
2026-07-19-plugin-command-registration.zh.md: e89cb79ba1e15dfefa527fe7bc37c09c3449f5b4
|
||||
2026-07-19-plugin-command-registration.zh.md: 409b3f282f6b69d31c901679abd0580bf35194d3
|
||||
|
||||
@@ -24,7 +24,7 @@ TUI 拥有斜杠命令。如果命令名、帮助文本、自动补全、分派
|
||||
|
||||
### 作用域与生命周期
|
||||
|
||||
无作用域注册是全局注册。挂载在智能体上下文之下并注入 `commands` 的插件会继承该智能体的作用域键与生命周期,因此其定义只会为对应的同一个 agent 遮蔽同名全局定义。子插件自行声明 `commands` 注入,因为 `agent.ctx` 有意只继承核心智能体循环的依赖界面;仅为了实现作用域注册而让循环依赖 UI 服务会倒置依赖图。
|
||||
无作用域注册是全局注册。挂载在智能体上下文之下并注入 `commands` 的插件会继承该智能体的作用域键与生命周期,因此其定义只会为对应的同一个 agent 遮蔽同名全局定义。子插件自行声明 `commands` 注入,因为 `agent.ctx` 有意只继承核心智能体循环的依赖 API;仅为了实现作用域注册而让循环依赖 UI 服务会倒置依赖图。
|
||||
|
||||
注册和移除会发出未过滤、不可否决的 `commands/change` 注册表通知。适配器重新计算每个实时智能体的有效视图,而不尝试推断某次变更影响哪些会话。注册表会分别隔离并记录每个观察者失败,因此损坏的 UI 刷新无法回滚另一插件的变更,也无法阻止后续观察者。Cordis 所有权会在生产者、UI 实例或智能体作用域卸载时移除定义,因此 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/implemented/feature/2026-07-20-code-mode-typed-tool-returns.md
|
||||
2026-07-20-code-mode-typed-tool-returns.md: 6b31dbca21a22a24f2bbb0ef61097622884165c3
|
||||
2026-07-20-code-mode-typed-tool-returns.zh.md: 1bb88d29ffd96d65ce06033202499d2780814abd
|
||||
2026-07-20-code-mode-typed-tool-returns.zh.md: 3dddb54bac6023d5523191449ff7cfe9c3354f57
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
Code Mode 过去会把每个嵌套工具的结果从 `ContentBlock[]` 重新投影为一个字符串。这样虽然保留了适合人类阅读的 Native 表层,却丢失了工具已经生成的规范结果:程序只能从自然语言中提取 task id 和动态挂载 id;结构化搜索与工作流结果失去原有形态;非文本块则变为占位符。生成的 SDK 可以描述参数,却无论工具实际输出为何都只能承诺 `Promise<string>`。
|
||||
Code Mode 过去会把每个嵌套工具的结果从 `ContentBlock[]` 重新投影为一个字符串。这样虽然保留了适合人类阅读的 Native 呈现,却丢失了工具已经生成的规范结果:程序只能从自然语言中提取 task id 和动态挂载 id;结构化搜索与工作流结果失去原有形态;非文本块则变为占位符。生成的 SDK 可以描述参数,却无论工具实际输出为何都只能承诺 `Promise<string>`。
|
||||
|
||||
运行时还把绑定值和程序最终返回值当作展示数据。日志和完成值分别设置上限,导致过大或无法克隆的完成值可能被替换为检查后生成的文本,而中间值本来就不会进入模型上下文。这种设计使程序化组合产生信息损失,也混淆了内存边界与提示词边界。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md
|
||||
2026-07-22-web-multimodal-image-input-and-durable-attachments.md: f1fbcbd29b188505e647265c4c974886c066e936
|
||||
2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: a55467cacc412b6f313dd932a0e4868965c039ea
|
||||
2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md: 499bd3565b3c8e9a2c315153862d49065fecbe93
|
||||
|
||||
@@ -75,7 +75,7 @@ interface ComposerAttachment {
|
||||
|
||||
### 持久内容与提示词协议
|
||||
|
||||
附件服务边界公开不可变图片写入和经过校验的读取操作。规范元数据刻意比通用文件记录更窄:
|
||||
附件 seam公开不可变图片写入和经过校验的读取操作。规范元数据刻意比通用文件记录更窄:
|
||||
|
||||
```ts
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
@@ -97,7 +97,7 @@ interface ImageBlock {
|
||||
}
|
||||
```
|
||||
|
||||
`ImageBlock` 加入可合并扩展的核心 `ContentBlockMap`,在用户内容和助手内容中都有效。它绝不携带 base64、对象 URL、文件系统路径或提供方持有的定位符。因此,会话事件与不可变对象存储足以共同重建模型可见的确切图片。LLM 词汇由此仅在类型层面依赖附件服务边界;提供方运行时依赖仍由各适配器持有。
|
||||
`ImageBlock` 加入可合并扩展的核心 `ContentBlockMap`,在用户内容和助手内容中都有效。它绝不携带 base64、对象 URL、文件系统路径或提供方持有的定位符。因此,会话事件与不可变对象存储足以共同重建模型可见的确切图片。LLM 词汇由此仅在类型层面依赖附件 seam;提供方运行时依赖仍由各适配器持有。
|
||||
|
||||
浏览器无法生成持久引用,因此 `session.prompt` 接受范围狭窄的接收联合类型,而不是规范 `ContentBlock[]`:
|
||||
|
||||
@@ -114,7 +114,7 @@ type PromptInputPart =
|
||||
}
|
||||
```
|
||||
|
||||
Base64 只跨越一次 JSON-RPC,并在持久化后丢弃。宿主会校验规范 base64、图片数量、总字节数、单张图片字节数、声明的 MIME 与完整解码后的光栅图片是否一致、固有尺寸和解码像素数。它会在保存任何成员之前,等待服务边界上不触碰存储的 `validateImage` 完成对每个批次成员的校验,因此一张畸形图片不会把批次中的有效成员留成无引用对象。随后按提交顺序执行存储提交,以限制完整光栅解码器的内存占用。如果后续存储 I/O 操作失败,宿主不会追加用户事件,但先前的不可变内容寻址对象可能保持无引用状态;第一版将清理留给未来按引用感知的垃圾回收,而不向去重存储添加破坏性回滚。只有每张图片都成功后,宿主才会用规范化文本和按提交顺序排列的持久图片块调用 agent。失败时不公开任何附件路径或原始字节。
|
||||
Base64 只跨越一次 JSON-RPC,并在持久化后丢弃。宿主会校验规范 base64、图片数量、总字节数、单张图片字节数、声明的 MIME 与完整解码后的光栅图片是否一致、固有尺寸和解码像素数。它会在保存任何成员之前,等待seam 上不触碰存储的 `validateImage` 完成对每个批次成员的校验,因此一张畸形图片不会把批次中的有效成员留成无引用对象。随后按提交顺序执行存储提交,以限制完整光栅解码器的内存占用。如果后续存储 I/O 操作失败,宿主不会追加用户事件,但先前的不可变内容寻址对象可能保持无引用状态;第一版将清理留给未来按引用感知的垃圾回收,而不向去重存储添加破坏性回滚。只有每张图片都成功后,宿主才会用规范化文本和按提交顺序排列的持久图片块调用 agent。失败时不公开任何附件路径或原始字节。
|
||||
|
||||
`session.attachment` 是只读且限定于会话作用域的端点。只有该会话中的持久事件引用了所请求的附件标识符,宿主才提供字节。会话处于渲染状态时,客户端会按会话和附件标识符对加载操作去重;已渲染会话释放时会撤销已解析的 URL,并在分配对象 URL 前拒绝已失效的延迟加载,以免已卸载的会话或已释放的服务重新写入缓存。
|
||||
|
||||
@@ -159,11 +159,11 @@ Pi-AI 适配器是首条视觉输入路径:它在请求时解析 `ctx.attachme
|
||||
| `packages/client/ui-conversation` | 每个会话的草稿图片、附件栏、用户与助手图片控件和原图预览。 |
|
||||
| `packages/acp/acp` | 图片块的明确兜底渲染。 |
|
||||
|
||||
附件包(package)构成一个能力服务边界的接口与实现侧。输入区行为留在会话对象层,提供方转换留在适配器中,无需修改 `agent-loop`。
|
||||
附件包构成一个能力 seam 的接口与实现侧。输入区行为留在会话对象层,提供方转换留在适配器中,无需修改 `agent-loop`。
|
||||
|
||||
### 实现
|
||||
|
||||
已实现的范围包括附件服务边界、角色无关的图片块、Pi-AI 输入转换、DeepSeek 拒绝、宿主持久化顺序、Web 上传与读取协议、当前图片限制执行、大小受限的 Web 请求体、内存草稿图片、粘贴与拖放附件栏、用户与助手历史图片渲染、单击预览、压缩处理,以及组装后无需密钥的 Web 覆盖。
|
||||
已实现的范围包括附件 seam、角色无关的图片块、Pi-AI 输入转换、DeepSeek 拒绝、宿主持久化顺序、Web 上传与读取协议、当前图片限制执行、大小受限的 Web 请求体、内存草稿图片、粘贴与拖放附件栏、用户与助手历史图片渲染、单击预览、压缩处理,以及组装后无需密钥的 Web 覆盖。
|
||||
|
||||
预发布提示词协议不需要兼容包装层;引入相应切片时会同时修改所有调用点和 fixture。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-23-web-assistant-markdown.md
|
||||
2026-07-23-web-assistant-markdown.md: 0ad74546b9a54d5eadfc3e7991efa4e0d9b7bc77
|
||||
2026-07-23-web-assistant-markdown.zh.md: e7544b2c731581e29b026026a2bb627defb91ca7
|
||||
2026-07-23-web-assistant-markdown.zh.md: 60f80043f5e6b19787e2fda84fc53b7a695a3e6b
|
||||
|
||||
@@ -28,7 +28,7 @@ assistant 生成的链接目标地址仅限绝对 HTTP、HTTPS 与 mailto URL。
|
||||
|
||||
**将现有的 mdast 与 micromark 开发依赖提升为正式依赖,并维护自定义 React walker。**此方案避免引入新的解析器体系,但产品需要自行负责每种节点映射、GFM 扩展和安全敏感的渲染分支。专用 React 渲染器将这套遍历交由上游维护,同时保留 AST 到 React 的处理路径。*后因新证据被推翻——增量流式解析需要纯字符串封装无法提供的 AST 级输入;该决策由[增量 AST 渲染器 Note](../architecture/2026-08-06-web-markdown-incremental-ast-renderer.md) 拥有。*
|
||||
|
||||
**将 `MessageText` 替换为 Markdown 渲染。**这会产生格式化用户提示词与 steering 的副作用。在产品明确选择此行为之前,这两类输入内容仍按字面渲染。
|
||||
**将 `MessageText` 替换为 Markdown 渲染。**这会产生格式化用户提示词与 steering 的副作用。在产品明确选择此行为之前,这些输入仍按字面渲染。
|
||||
|
||||
**将 Markdown 解析为会话快照。**这会让 React 节点或呈现层 AST 成为持久的运行时状态,并重新引入最终输出与流式输出之间的模式边界。解析仍留在呈现层的叶节点中。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.md
|
||||
2026-07-28-skill-invocation-policy.md: 7a4f83ecae3dea82ab6864e748c9d8b8599f6bfc
|
||||
2026-07-28-skill-invocation-policy.zh.md: 20d41ec1b2fdbfe5bbbe3c54a681dd4f807c703e
|
||||
2026-07-28-skill-invocation-policy.zh.md: 1fbdfead85be443f037f418c64c24ed619f731b9
|
||||
|
||||
@@ -22,7 +22,7 @@ skill 注册表最初将发现操作视为模型目录:`ctx.skills.list()` 会
|
||||
|
||||
这些规则允许以下四种组合:
|
||||
|
||||
| 策略 | 模型侧接口 | 用户侧接口 |
|
||||
| 策略 | 模型调用 | 用户调用 |
|
||||
|---|---|---|
|
||||
| `{ modelInvocable: true, userInvocable: true }` | 包含 | 包含 |
|
||||
| `{ modelInvocable: true, userInvocable: false }` | 包含 | 排除 |
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-03-fs-tool-error-remedy.md
|
||||
2026-08-03-fs-tool-error-remedy.md: 1829a47363079aacc2090e166e94b5c524c87deb
|
||||
2026-08-03-fs-tool-error-remedy.zh.md: 88309bd630732794dfb2401b60ad799a3d63146b
|
||||
2026-08-03-fs-tool-error-remedy.zh.md: 2c92f4b29c1290ced7bc3bd2417bba067ab402b7
|
||||
|
||||
@@ -21,7 +21,7 @@ Status: implemented
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
- **在 `dsh-fs` / `dsh-fs-local` 的提供方消息中追加恢复指令。** 被拒绝:这些消息是面向机器的 seam 词汇,除面向模型的接口外还被重试、权限与 UI 层消费;面向模型的措辞应位于模型边界,即 `dsh-tool-fs` 已经拥有结果格式化之处([文件系统能力 seam](../architecture/2026-06-17-filesystem-capability-seam.md))。
|
||||
- **在 `dsh-fs` / `dsh-fs-local` 的提供方消息中追加恢复指令。** 被拒绝:这些消息是面向机器的 seam 词汇,被重试、权限、UI 和面向模型的各层消费;面向模型的措辞应位于模型边界,即 `dsh-tool-fs` 已经拥有结果格式化之处([文件系统能力 seam](../architecture/2026-06-17-filesystem-capability-seam.md))。
|
||||
- **改为在提示词引导中加入恢复方式。** 被拒绝:失败发生在任务中途;静态指令无法可靠地影响重试决策,而错误消息恰好在模型必须行动时出现。
|
||||
- **用新的 `FsError` 错误码表达恢复指令。** 被拒绝:这两种失败对应的条件,重试层本就已经处理;拆分错误码会使相同语义采用不同路由。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-05-per-agent-tool-presentation.md
|
||||
2026-08-05-per-agent-tool-presentation.md: adb93b51c73d341c153b8fcafe2a08f0a5598478
|
||||
2026-08-05-per-agent-tool-presentation.zh.md: fa83bd4daec9d8e6e9e43295bb81af97782b1fdf
|
||||
2026-08-05-per-agent-tool-presentation.zh.md: 636bbee5c9cbd6d573e7b4093217af2c9bdd9659
|
||||
|
||||
@@ -4,13 +4,13 @@ Status: implemented
|
||||
|
||||
[English](2026-08-05-per-agent-tool-presentation.md) | 中文
|
||||
|
||||
## Problem
|
||||
## 问题
|
||||
|
||||
agent preset 已经能按会话组装一个 agent 的工具,却管不了这些工具以何种**形态**抵达模型。Code Mode——一个 `run_code` 工具加一份生成的 TypeScript SDK,用一段程序替代一串调用——此前是宿主 `dsh-tools` 那一行上的部署级 `mode` 字段。一个部署要么所有会话都跑 Code Mode,要么一个都不跑,于是那个显而易见的产品形态(预设选择器里「代码模式」与标准/极简/创造并列)无处安放。
|
||||
|
||||
「把 tools 下沉到 agent 平面」这个字面读法行不通。`ctx.tools` 有一批跟不下来的宿主平面消费者:`dsh-agent-loop` 读它私有的调度器 seam,`dsh-apiproxy` 读它的 presenter 来渲染工具卡,每个工具插件都往里注册。按本 stack 自己的规则——只有**所有**消费者一起下沉,服务才能下沉——注册表必须留在原地。
|
||||
|
||||
## Decision
|
||||
## 决策
|
||||
|
||||
把注册表和它的投影拆开。注册表留在宿主平面;**呈现方式**变成它内部按 scope 的状态,与已经住在那里的作用域限制和守卫并列。
|
||||
|
||||
@@ -25,7 +25,7 @@ SDK 提示词段由 code 模式的部署全局注册(不变),并由 `prese
|
||||
|
||||
preset 用一行来表达这个选择:`@deepseek-ai/dsh-agent-tool-mode`,其全部内容就是一次 `presentAs` 调用。code 类模式通过 `ctx.inject` 等待 `ctx.codeRuntime` 而非假定它存在:运行时在宿主平面,而一个 pending 的行正是 `dsh-agent-presets` 已经会报告的「不可用挂载」并会指名该行——于是在无运行时的部署上选择 Code Mode 的 preset,会在操作者能够动手的地方失败。
|
||||
|
||||
## Alternatives considered
|
||||
## 考虑过的替代方案
|
||||
|
||||
**在 preset 的 isolate realm 里再起一个 `ToolRegistry`。** 否决:`dsh-agent-loop` 通过一个私有 symbol 从宿主上下文一次性解析注册表,因此按 agent 的注册表对调度器不可见。把 loop 改成按 agent 解析注册表,远比把一个字段变成 scope 感知的改动大。
|
||||
|
||||
@@ -37,7 +37,7 @@ preset 用一行来表达这个选择:`@deepseek-ai/dsh-agent-tool-mode`,其
|
||||
|
||||
**用 include 共享 `standard` 的组装。** 按本 stack 自己的惯例否决:`cordis` 已经复制了一份 `standard`,而 preset 的价值恰在于整份组装能在一个文件里读完。代价——第三份约 240 行、且必须同步演进的副本——是真实的,也正是未来引入 include 机制最有力的论据。
|
||||
|
||||
## Consequences
|
||||
## 后果
|
||||
|
||||
同一进程内的两个会话现在可以有不同的呈现方式,因此「模型看到哪些工具」不再能只凭部署配置回答,必须给出 agent。凡是引用模式的诊断信息,现在引用的都是该 scope 的,而不是服务的。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md
|
||||
2026-08-06-manager-owned-subagent-settlement-delivery.md: 11669d4603e658d1478557f81c2d2b4d5495bca7
|
||||
2026-08-06-manager-owned-subagent-settlement-delivery.zh.md: f3684777ccb3800226803e998438147ebc1dbf16
|
||||
2026-08-06-manager-owned-subagent-settlement-delivery.zh.md: 86e1578a89d8e7137d6be2f6a62ece5d77652832
|
||||
|
||||
@@ -4,7 +4,7 @@ Status: implemented
|
||||
|
||||
[English](2026-08-06-manager-owned-subagent-settlement-delivery.md) | 中文
|
||||
|
||||
## Problem
|
||||
## 问题
|
||||
|
||||
可继续后台委派是模型唯一一种能够发起、却无法抵达终点的异步操作。其他每一种形态都有取回原语或返回值:后台 bash 命令与一次性后台 subagent 都通过 Task 结算,`task_output(wait: true)` 可以阻塞等待;workflow 与前台 subagent 会把结果返回给调用方。可继续后台 child 只返回它持久化的 id,而父级既没有可等待的对象,也不会被交付任何东西。
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
信号本身早就存在。自可继续 Activation 发布以来,`subagent/end` 就一直携带 `stopReason` 与 `lastAssistantMessage`。缺的是把它变成父级模型能看到的上下文的那个消费者。
|
||||
|
||||
## Decision
|
||||
## 决策
|
||||
|
||||
继续执行管理器自己投递这份记账,就在结束 Activation 的那笔 dispose 事务内部完成。
|
||||
|
||||
@@ -66,7 +66,7 @@ Status: implemented
|
||||
|
||||
拒绝与中断两种措辞在单元测试中逐字钉死,而不进入重放 transcript:触发它们需要一个会拒绝的策略插件、或一次在 step 边界被栅栏卡住的取消,而无密钥组装本身并不携带这些;通知通路本身已由整体组装场景端到端钉住。
|
||||
|
||||
## Alternatives considered
|
||||
## 考虑过的替代方案
|
||||
|
||||
**给可继续 child 引入 Task。** Task 是一次性契约:一个生产者、一次结算、一个结果。Activation 会执行许多轮次、比其中任何一轮活得更久,并且可以在结束后被恢复。用 Task 包装它,恰好重建了可继续 child 当初为消除而引入的生命周期错配,还会让某一个轮次看起来是终局。
|
||||
|
||||
@@ -80,7 +80,7 @@ Status: implemented
|
||||
|
||||
**始终使用 `followup`。** 更简单也更统一,但一批同时结算的 child 会各自消耗一个父级轮次。step 边界的批量语义本来就存在,用它是免费的。
|
||||
|
||||
## Consequences
|
||||
## 后果
|
||||
|
||||
- 可继续 child 的父级会为每个已结算 Activation 收到一条消息。因此,做扇出的部署会增加父级轮次;steer 会把同时结算的一批压缩到一个 step。
|
||||
- `tool-subagent` 在其 schema 中承诺该通知,因为返回通道是服务行为,不是可选插件。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.md
|
||||
2026-08-08-windows-acl-restricted-token-sandbox.md: 972713e02860218853f421aa700a8b60b33ada5b
|
||||
2026-08-08-windows-acl-restricted-token-sandbox.zh.md: da41cb3f9aa46bab96a5fbb6c035205b22c442aa
|
||||
2026-08-08-windows-acl-restricted-token-sandbox.zh.md: 6150726ef836ecd92a68a641ffcd86f9f0878355
|
||||
|
||||
@@ -4,11 +4,11 @@ Status: implemented
|
||||
|
||||
[English](2026-08-08-windows-acl-restricted-token-sandbox.md) | 中文
|
||||
|
||||
## Problem
|
||||
## 问题
|
||||
|
||||
最初的[沙箱决策](2026-07-06-sandbox.md)将 `PLATFORM_CHAINS.win32` 留空,因此交付的 Windows profile 因不存在隔离执行器而退化为 danger-full-access。win32 档必须约束沙箱词汇表中的两种文件效果模式——`read-only`(不显式授予任何可写根目录)与 `workspace-write`(允许写入工作区根目录及后端定义的临时区域)——并报告其机制无法约束的任何效果;读取、网络与进程可见性仍在这套词汇之外。
|
||||
|
||||
## Decision
|
||||
## 决策
|
||||
|
||||
直接基于原始 ACL 机制实现该档:把调用者令牌复制为 `WRITE_RESTRICTED` 受限令牌(`CreateRestrictedToken`,`WRITE_RESTRICTED` + `DISABLE_MAX_PRIVILEGE` + `LUA_TOKEN`),其 restricting SIDs 携带彼此独立的工作区能力与私有临时目录能力。`WRITE_RESTRICTED` 只对写访问做交集检查,因此读取保留调用者的环境访问,而写入还必须匹配这些能力 ACE 之一。该机制来自 huoyaoyuan/windows-acl-restrict-poc(`10e4dfb`)的演示;本移植检查每个 API 调用并 fail-closed(POC 因忽略失败而 fail-open)。工作区 SID 由规范工作区路径确定性派生(`workspaceWriteSid`——sha256 → `S-1-4-x-y`);其常驻工作区 ACE 是跨会话复用缓存,精确 ACE 跳过可避免重复的急切全树传播。每个活跃的会话/工作区对则获得一个随机私有临时目录,以及一个从该路径派生的、经过域分离的 SID(`tempWriteSid`);其 ACE 可回收,TMP/TEMP 指向该目录,令牌默认 DACL 列入该临时 SID,因此新建的临时对象不会获得共享的工作区能力。fork 因此无法写入同级会话的临时目录树。即使恢复的是同一会话,新的提供方也会选择新的路径和 SID,因此崩溃残留只是失效垃圾,而非冲突或继承的能力;无 agent(智能体)的调用会逐调用创建并移除同样的形态。环境临时根目录绝不会被隐式授权。如果工作区等于或包含临时根目录,调用会在任何 ACL 改动发生前失败,因为否则其可继承的常驻 ACE 会向每个私有子目录授权;直接 API 会拒绝可写根目录与实际私有临时目录在任一方向上的重叠。PowerShell 可借助这项私有临时目录能力完成启动时的 AppLocker 探针,因此在没有主机范围策略时,`workspace-write` 会保持 FullLanguage;`read-only` 无法创建探针文件,会保守地进入 ConstrainedLanguage。这一区别属于 PowerShell 启动行为,不是 ACL 边界的一部分。令牌列表为 read-only = [登录 SID、Everyone],workspace-write = [登录 SID、Everyone、工作区 SID、可选临时 SID]。登录 SID + Everyone 是保活不变式(没有它们,早期 DLL 初始化会以 0xC0000142 死亡,CNG 会让 pwsh 以 0xE0434352 崩溃)。由于 Everyone 仍在列表中,向 Everyone 授予写访问的外部对象会通过两次检查;由于 NTFS ACL 属于文件对象,工作区内获授权的硬链接也会使同一对象的外部别名获得授权。拒绝所有硬链接会让普通 pnpm 工作区不可用,因此提供方报告 `enforcement: 'partial'`,原生套件则钉住这两个缺口。Read-only 不含任何能力 SID,因此常驻工作区 ACE 在模式降级后保持失效。Authenticated Users 在两种列表中都不存在——CIM 不可用,从而关闭 C:\-root 建树逃逸——INTERACTIVE/LOCAL 也不存在,因此 Public 树写入被拒绝。新建匿名管道和同步对象通过 `SetTokenInformation(TokenDefaultDacl)` 继承临时 SID(禁用临时目录时继承工作区 SID,read-only 下继承 Everyone);named pipe 保持 Win32 层 owner/SYSTEM/Admins 全权、Everyone/ANONYMOUS 只读的模板,因此受限孙进程的管道 stdio 仍被拒绝。它以 [`@deepseek-ai/dsh-sandbox-windows-acl`](../../../../packages/sandbox/sandbox-windows-acl/README.md)、[`dsh-sandbox-local`](../../../../packages/sandbox/sandbox-local/README.md) 的 `win32` 档,以及作为隔离执行器的 [`@deepseek-ai/dsh-pwsh-sandbox`](../../../../packages/bash/pwsh-sandbox/README.md) 交付。
|
||||
|
||||
@@ -16,7 +16,7 @@ Status: implemented
|
||||
|
||||
身份路线靠"**谁**在跑子进程"来限制,本档靠"令牌派生"来限制。身份路线(landstrip 的 restricted-user、AppContainer)用全新账户或容器 SID 运行子进程,该身份在宿主的文件上从零条 ACE 开始——一切访问(包括读)默认拒绝,子进程要碰的每条路径都必须事后为那个身份补写 ACE 才能放行:这正是让两个备选方案出局的全盘 DACL 改造。受限令牌保留调用者自己的 SID 与 logon session:[`CreateRestrictedToken`](https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-createrestrictedtoken) 派生一个加入 restricting SIDs 与 `WRITE_RESTRICTED` 标志的令牌,于是 Windows 做两次访问检查——一次按正常 SID,一次按 restricting SIDs——只有两次都放行,写类访问才被授予。读只凭正常检查即可通过(调用者的 SID 在其可读范围内本来就携带读权限),所以本档不需要任何读授权、也不需要新账户;写还必须额外通过能力 SID 检查,而只有工作区与临时目录的 ACE 能满足它。`DISABLE_MAX_PRIVILEGE | LUA_TOKEN` 在令牌侧合成了新账户的受限用户效果,即使提升过的调用者派生的也是过滤令牌。同一原语其实也能限制读(`SidsToDisable` 把 SID 变为 deny-only),但受限读的令牌需要逐路径的读授权——恰好重新引入身份路线付出的代价——而沙盒词汇表从不要求读隔离。
|
||||
|
||||
## Alternatives considered
|
||||
## 考虑过的替代方案
|
||||
|
||||
### 为什么不选 mxc(Microsoft xContainer)?
|
||||
|
||||
@@ -30,11 +30,11 @@ AppContainer 令牌没有环境读访问:每个可读路径都必须预先通
|
||||
|
||||
[landstrip 评估](../../rejected/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)在实现前已被否决(未经实战检验;自建 launcher 方案胜出),且其 Windows 后端是 AppContainer 形态,继承同样的任意路径读问题。
|
||||
|
||||
## Consequences
|
||||
## 后果
|
||||
|
||||
所得:仅写隔离、不引入新的 OS 版本下限(`CreateRestrictedToken` 比 mxc 的版本早二十年)、读/网络/进程可见性完全不受影响(与模式词汇表一致),且 fail-closed 错误携带 API 名与精确 Win32 错误码。会话共享有意常驻的工作区能力,但不共享各自可回收的临时能力;重启残留既不能阻塞恢复的会话,也不能向其授权。所失:强制执行在结构上只能是部分的,因为此令牌形态无法把 Everyone 授予的写入与 NTFS 硬链接别名限制在路径边界内;无读侧或网络隔离;控制台隔离不可用(隐藏控制台子进程以 `STATUS_DLL_INIT_FAILED` 死亡;子进程共享宿主控制台);工作区常驻 ACE 改动(复用缓存,以及工作区改名后的失效残留)与异常关闭后遗留的随机临时目录垃圾,直到 OS 卫生机制将其回收;工作区授权采用急切的全树传播(`SetNamedSecurityInfoW` 立即遍历每个后代——大型工作区上耗时数十秒),每台机器每个工作区只付一次;CIM 在两种受限模式下均不可用(Authenticated Users 不存在,从而关闭 C:\-root 建树逃逸);FAT 类无 ACL 目标仍可写;NULL-DACL 目录在 grant/revoke 往返下不保持身份;`whoami` 与令牌检查 cmdlet 在受限令牌下失败;read-only pwsh 会进入 ConstrainedLanguage,而在没有主机策略时 workspace-write 保持 FullLanguage;named pipe 打开仍被拒绝,因此 libuv 管道 stdio 的孙进程以 EPERM 失败,而继承/忽略的 stdio 与匿名管道可用。包 README 负责记录这些运行限制。
|
||||
|
||||
## Testing
|
||||
## 测试
|
||||
|
||||
产品可见的 Windows 阵容切换仅存在于 win32,而必须在 macOS/Linux 上可重放的 keyless 快照无法覆盖它;替代证据是 bundle 组合 spec 加上 win32 真实 runner 套件,组装态信号由 CI 的 Windows lane 负责。`sandbox-local/tests/acl-grants.spec.ts` 在 mock Win32 的情况下钉住随机临时目录分配、按会话/工作区复用、fork/工作区分离、崩溃后恢复不冲突、成对 argv SID、失败清理,以及常驻/可回收生命周期。在 Windows 上,`workspace-sid.spec.ts` 钉住工作区/临时目录派生与域分离;`acl.spec.ts` 钉住真实 DACL 生命周期;`runner.spec.ts` 钉住成对 SID 验证、共享工作区 SID 时对同级会话临时目录的拒绝、无 agent 调用的逐调用临时目录创建/移除、TMP/TEMP 重写、模式降级、Public 拒绝、Everyone/硬链接部分边界、按模式区分的 PowerShell 语言行为与孙进程 stdio。ARM64 与模拟 x64 原生运行负责提供架构特定的验收证据。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-10-web-session-log-export.md
|
||||
2026-08-10-web-session-log-export.md: 8fa62b877df1be55de2c373d4281672881dc2b9d
|
||||
2026-08-10-web-session-log-export.zh.md: 3040dda992492187245bfe92d29bc0812ef01ef2
|
||||
2026-08-10-web-session-log-export.zh.md: 1dbc5a3219f446464c2f6e17445e5d7dffabcf0a
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note:Web 会话日志导出——宿主流式 ZIP 下载
|
||||
# Agent Note: Web 会话日志导出——宿主流式 ZIP 下载
|
||||
|
||||
状态:implemented
|
||||
|
||||
|
||||
Reference in New Issue
Block a user