Merge branch 'master' into worktree/dsh-arg-parser

Integrate the Commander argument adapter and dsh-front-door work with master's
config-tree `dsh web` (#601: AppCLIEntry + apps/cli/cordis.yml) and the
packages/ui/acp → packages/acp/acp relocation.

- web.ts: keep master's AppCLIEntry-based boot, but take the adapter's parsed
  (host, port, dev) instead of an internal parseArgs. The adapter's host/port
  defaults (127.0.0.1/3080) match cordis.yml, so always passing them is
  behavior-equivalent to master's "undefined keeps the yml default".
- apps/cli/package.json: master's expanded config-tree dep set + commander.
- retire-readline Agent Note: point the TUI refusal proof at
  apps/cli/tests/built-bin.e2e.ts (both languages), re-record the pair.
- READMEs reconciled (demo-bin removal + master's ACP/channel rewording).
This commit is contained in:
Turtle
2026-07-25 14:37:57 +08:00
640 changed files with 13210 additions and 24114 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-19-drop-mutable-session-summary.md: 80fe043e365352b17d2a5b3efa1ab8d396d311c4
2026-06-19-drop-mutable-session-summary.zh.md: e875d96d003367dbe8a671e6057a6b4a6a44ca74
2026-06-19-drop-mutable-session-summary.md: f87378a1c3737950eb536be8e3f6776586eb8a01
2026-06-19-drop-mutable-session-summary.zh.md: 05b6711b71ef87602b46706cd4340a10453e66ad

View File

@@ -12,8 +12,8 @@ The summary was designed for a future session picker (recency ordering via `upda
- `SessionPersistence.update()` has **zero production callers** (every `.update(` hit is `createHash().update()` or a test).
- `firstPrompt` is **never read** anywhere in production.
- `title` *is* read in the ACP bridge — but from a tool-call **presenter** (`present.title`), never from stored session metadata.
- `updatedAt` has **no consumer**: the only production caller of `list()` reads `meta.cwd` (a `SessionHeader` field) to validate a workspace on `session/load`; resume reads `createdAt`/`cwd`/`parentSession` — all header fields.
- Session titles come from durable `session/title` events, while tool-card titles come from tool presenters; neither reads mutable session metadata.
- Persistence-list consumers use immutable header identity, creation, lineage, and cwd fields. Recency and previews derive from the log rather than an `updatedAt` summary.
- Decisively: the live `Session.header` was already typed `SessionHeader`, not `SessionMeta` — the summary never existed on the live session object; it lived only in the persistence layer, written and read by nothing but its own contract test.
## Decision

View File

@@ -12,8 +12,8 @@ Status: implemented
- `SessionPersistence.update()` **零个生产调用方**(所有 `.update(` 匹配都是 `createHash().update()` 或测试代码)。
- `firstPrompt` 在生产代码中**从未被读取**。
- `title` *确实*在 ACPAgent Client Protocol桥接层被读取过但读的是工具调用的 **presenter**`present.title`),从未读取存储的会话元数据。
- `updatedAt` **没有消费方**`list()` 唯一的生产调用方读取的是 `meta.cwd``SessionHeader` 字段),用于在 `session/load` 时校验工作区;恢复会话读取的是 `createdAt`/`cwd`/`parentSession`——全是 header 字段
- 会话标题来自持久的 `session/title` 事件,工具卡片标题来自工具 presenter二者都不读取可变的会话元数据。
- 持久化列表的消费方使用不可变 header 中的标识、创建、谱系和 cwd 字段。近期排序和预览派生自日志,而非某个 `updatedAt` 摘要
- 决定性的一点:活跃的 `Session.header` 类型本来就是 `SessionHeader` 而非 `SessionMeta`——摘要从未存在于活跃会话对象上;它只存在于持久化层,除了自身的契约测试外无人写入、无人读取。
## 决策

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-collapse-trace-only-session-events.md: fce5c48ef6fcb1abc6e2fbb95dc7e83d22956660
2026-06-20-collapse-trace-only-session-events.zh.md: 5be2d77644c7b4084b892013a2c1e5b97a333d74
2026-06-20-collapse-trace-only-session-events.md: c77062c3cd44286b43c175702c35b36f9cc31da6
2026-06-20-collapse-trace-only-session-events.zh.md: b232b3fb60822e60b1f5767066db42b0228269a8

View File

@@ -6,7 +6,7 @@ English | [中文](2026-06-20-collapse-trace-only-session-events.zh.md)
## Problem
The session event vocabulary includes first-class events that are not part of replayable conversation history and have little or no production consumption. `usage` is already present as a model stream chunk before the loop also appends a separate `usage` event. `error` duplicates the `turn/end { kind: 'error', message, code }` reason for loop failures; ACP settlement reads the turn-end reason, ACP rendering ignores the `error` event, and `deriveMessages()` skips it.
The session event vocabulary includes first-class events that are not part of replayable conversation history and have little or no production consumption. `usage` is already present as a model stream chunk before the loop also appends a separate `usage` event. `error` duplicates the `turn/end { kind: 'error', message, code }` reason for loop failures; ACP settlement reads the turn-end reason, while message and UI projections skip the standalone `error` event.
These events make the canonical transcript look more useful as telemetry than it currently is. They add event variants, invariants, tests, snapshots, and persistence cases, but they are not load-bearing as separate records. The facts they carry can still be useful: token usage should remain available for accounting, and an error's step number should not silently disappear. The simplification is to fold those facts into nearby events consumers already must understand, not to record less information.

View File

@@ -6,7 +6,7 @@ Status: implemented
## 问题
会话事件词汇中包含一些一等事件,它们不属于可回放的对话历史,在生产环境中几乎没有消费方。`usage` 已经作为模型流分片存在,之后循环又追加了一个独立的 `usage` 事件。`error``turn/end { kind: 'error', message, code }` 中的循环失败原因重复ACPAgent Client Protocol结算读取轮次结束原因ACP 渲染忽略 `error` 事件,`deriveMessages()` 也跳过它
会话事件词汇中包含一些一等事件,它们不属于可回放的对话历史,在生产环境中几乎没有消费方。`usage` 已经作为模型流分片存在,之后循环又追加了一个独立的 `usage` 事件。`error``turn/end { kind: 'error', message, code }` 中的循环失败原因重复ACPAgent Client Protocol结算读取轮次结束原因而消息投影和 UI 投影都会跳过独立的 `error` 事件
这些事件让规范的 transcript文本记录看起来比实际更像遥测数据。它们增加了事件变体、不变式、测试、快照和持久化用例但作为独立记录并不承载实际功能。它们携带的事实仍然有用token 用量应当保留以供计费,错误的步骤编号也不应悄然消失。简化的方式是将这些事实折叠进消费方本已必须理解的邻近事件,而非减少记录的信息量。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-prune-dead-seam-methods.md: 70596d908bb1d7559e876d93bce0e25874ce1ff0
2026-06-20-prune-dead-seam-methods.zh.md: 1fbc7dbf2f7abb5b17b1df10ef06e490b11335a4
2026-06-20-prune-dead-seam-methods.md: 91a18b4c327d2f3cb636d6d9e0c26e4246a6ffd2
2026-06-20-prune-dead-seam-methods.zh.md: b962bc45052b723eefa533b98fe85f4508d6b6a7

View File

@@ -12,7 +12,7 @@ A capability seam ([interface / implementation / consumer](../architecture/2026-
### `SessionPersistence.has()` and `.delete()`
The abstract service declared its operations beyond create/append: `load`, `list`, `has`, `delete`. Production consumers of `ctx.sessionPersistence` use only two: the agent-loop resume path calls `load()` ([packages/core/agent-loop/src/index.ts:176](../../../../packages/core/agent-loop/src/index.ts)), and the ACP bridge calls `list()` for `session/list` ([packages/ui/acp/src/index.ts:494](../../../../packages/ui/acp/src/index.ts)). Grepping every `sessionPersistence.*` / `persistence.*` use across `packages/*/src` and `examples/` finds no `has(` and no `delete(` on the service. The `.has(`/`.delete(` calls in `packages/ui/acp/src/index.ts` are on the in-memory `SessionStore` and a local `Set` of loading ids, not persistence. The only callers of `has`/`delete` were the contract suites and per-backend specs.
The abstract service declared its operations beyond create/append: `load`, `list`, `has`, `delete`. Production consumers use `load()` and `list()` for resume and session discovery, while no production caller uses persistence `has()` or `delete()`. The similarly named in-memory collection calls in protocol and UI code are unrelated. The only callers of persistence `has`/`delete` were the contract suites and per-backend specs.
`has()` was not just unused: it added a tracked-vs-untracked coordinator probe and a contract branch even though `loadStored(id)` already owns durable existence checks. `delete()` dragged the `deleteStored` backend hook that every backend had to implement. This is the [drop-mutable-session-summary](2026-06-19-drop-mutable-session-summary.md) pattern: a contract test exercised both, but no shipping code asks "is this session persisted?" or removes one.
@@ -33,7 +33,7 @@ Re-adding a seam method with a live consumer is cheap and better-designed than t
## Verification
`has`/`delete`/`deleteStored` are gone from the persistence seam, impl, and contract suites with no new dead exports; the remaining operations (`create`/`append`/`load`/`list`) are untouched, with ACP `session/list` and crash-recovery behaving identically; and the seam README and `docs/architecture.md` list only the surviving methods.
`has`/`delete`/`deleteStored` are gone from the persistence seam, impl, and contract suites with no new dead exports; the remaining operations (`create`/`append`/`load`/`list`) are untouched, with persistence-backed session queries and crash recovery behaving identically; and the seam README and `docs/architecture.md` list only the surviving methods.
## Consequences

View File

@@ -12,7 +12,7 @@ Status: implemented
### `SessionPersistence.has()` 与 `.delete()`
该抽象服务在 create/append 之外声明了更多操作:`load``list``has``delete``ctx.sessionPersistence` 的生产消费方只用了两个agent loop智能体循环的恢复路径调用 `load()`[packages/core/agent-loop/src/index.ts:176](../../../../packages/core/agent-loop/src/index.ts)ACPAgent Client Protocol桥接层为 `session/list` 调用 `list()`[packages/ui/acp/src/index.ts:494](../../../../packages/ui/acp/src/index.ts))。在 `packages/*/src``examples/` 中 grep 所有 `sessionPersistence.*` / `persistence.*` 的使用,找不到对该服务`has(``delete(` 调用。`packages/ui/acp/src/index.ts` 中的 `.has(`/`.delete(` 调用作用于内存中的 `SessionStore` 和一个本地的 loading id `Set`,而非持久化`has`/`delete` 的唯一调用者是契约测试套件和各后端的 spec。
该抽象服务在 create/append 之外声明了更多操作:`load``list``has``delete`生产消费方用 `load()``list()` 完成恢复与会话发现,而没有任何生产调用方使用持久化`has()``delete()`。协议和 UI 代码中名称相似的内存集合调用与此无关。持久化 `has`/`delete` 的唯一调用者是契约测试套件和各后端的 spec。
`has()` 不仅未被使用:在 `loadStored(id)` 已负责持久化存在性检查的情况下,它仍增加了协调器的已跟踪/未跟踪探测和一个契约分支。`delete()` 则拖入每个后端都必须实现的 `deleteStored` 后端钩子。这属于[删除可变会话 summary](2026-06-19-drop-mutable-session-summary.md) 的同类模式:契约测试覆盖了两者,但已发布代码从不会询问“这个会话是否已持久化?”或删除某个会话。
@@ -33,7 +33,7 @@ Status: implemented
## 验证
`has`/`delete`/`deleteStored` 已从持久化 seam、实现和契约测试套件中移除没有新增无用导出剩余操作`create`/`append`/`load`/`list`)未受影响,ACP `session/list` 和崩溃恢复行为完全一致seam README 和 `docs/architecture.md` 仅列出存留的方法。
`has`/`delete`/`deleteStored` 已从持久化 seam、实现和契约测试套件中移除没有新增无用导出剩余操作`create`/`append`/`load`/`list`)未受影响,基于持久化的会话查询和崩溃恢复行为完全一致seam README 和 `docs/architecture.md` 仅列出存留的方法。
## 后果

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-public-agent-stop-surface.md: 81a21de30bfbc25688069efbffb21647889b1bdb
2026-06-20-public-agent-stop-surface.zh.md: 1f5a9ea3c1c3dd6f598c258574a81e52e291f3b7
2026-06-20-public-agent-stop-surface.md: e22c4389df18f3c9ca96763fc097eabefcc5b761
2026-06-20-public-agent-stop-surface.zh.md: e2647b498a8c906579b4fd2b50f94d1c326fe784

View File

@@ -18,7 +18,7 @@ The extra surface area made the loop carry a public verb that is mostly a teardo
`cancel()` is the only public *stop* primitive on `Agent`. Lifecycle owners use `AgentHandle.dispose()` to stop and unregister an agent; non-owners use `cancel()` to abandon current and queued work. The implementation keeps a private turn cancellation holder, but it is not part of the plugin-facing `Agent` contract.
`whenIdle()` is **retained** as the public quiescence-observation primitive (resolve once the agent settles out of `running`, resolve immediately when already idle, await the loop exit when disposed). It is not a stop verb; it is how a non-owner observes the stop *completing* without disposing the agent. Its live consumers are ACP and agent tests that await settlement through this public seam (`packages/ui/acp/tests`, `packages/core/agent-loop/tests`); the production ACP bridge owns its agents and tears them down through `AgentHandle.dispose()`, so `packages/ui/acp/src` itself has no `whenIdle()` call.
`whenIdle()` is **retained** as the public quiescence-observation primitive (resolve once the agent settles out of `running`, resolve immediately when already idle, await the loop exit when disposed). It is not a stop verb; it is how a non-owner observes the stop *completing* without disposing the agent. Its live consumers are ACP and agent tests that await settlement through this public seam (`packages/acp/acp/tests`, `packages/core/agent-loop/tests`); the production ACP bridge owns its agents and tears them down through `AgentHandle.dispose()`, so `packages/acp/acp/src` itself has no `whenIdle()` call.
Public `abort()` is absent, and the disposer remains async and waits for the loop to stop. Tests exercise cancellation through the public typed cause and explicit signal seams rather than reaching into the holder.

View File

@@ -18,7 +18,7 @@ Status: implemented
`cancel()``Agent` 上唯一的公共*停止*原语。生命周期拥有者使用 `AgentHandle.dispose()` 停止并注销 agent非拥有者使用 `cancel()` 放弃当前和已排队工作。实现保留一个私有轮次取消 holder但它不属于面向插件的 `Agent` 契约。
`whenIdle()` **保留**为公开的完全停稳观测原语agent 从 `running` 状态稳定后 resolve已处于 idle 时立即 resolvedispose 后等待循环退出)。它不是停止动词;它是非所有者在不 dispose agent 的前提下观测停止*完成*的方式。它的活跃消费方是 ACP 和通过此公开 seam 等待结算的 agent 测试(`packages/ui/acp/tests``packages/core/agent-loop/tests`);生产环境的 ACP 桥接层拥有其 agent 并通过 `AgentHandle.dispose()` 销毁它们,因此 `packages/ui/acp/src` 本身没有 `whenIdle()` 调用。
`whenIdle()` **保留**为公开的完全停稳观测原语agent 从 `running` 状态稳定后 resolve已处于 idle 时立即 resolvedispose 后等待循环退出)。它不是停止动词;它是非所有者在不 dispose agent 的前提下观测停止*完成*的方式。它的活跃消费方是 ACP 和通过此公开 seam 等待结算的 agent 测试(`packages/acp/acp/tests``packages/core/agent-loop/tests`);生产环境的 ACP 桥接层拥有其 agent 并通过 `AgentHandle.dispose()` 销毁它们,因此 `packages/acp/acp/src` 本身没有 `whenIdle()` 调用。
公共 `abort()` 已不存在disposer 仍为异步并等待循环停止。测试通过公共类型化原因和显式 signal seam 验证取消,而不会伸入 holder 内部。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-remove-agent-boundary-mirror-events.md: 8c5bb74f2347fe0269cbb9c6504137de761ab919
2026-06-20-remove-agent-boundary-mirror-events.zh.md: 1bb166b7f4453b4f0605e2b1b9120bc5a05266b0
2026-06-20-remove-agent-boundary-mirror-events.md: 31acbb122cf56adfdcfcbce602d09a35f7f13e17
2026-06-20-remove-agent-boundary-mirror-events.zh.md: f386e8d1ccfc2df094645a3cf9ae9524091d2a4e

View File

@@ -15,7 +15,7 @@ English | [中文](2026-06-20-remove-agent-boundary-mirror-events.zh.md)
## Problem
The loop records the canonical transcript in `SessionEvent` and also emitted a parallel set of live `agent/*` boundary mirror events: `agent/turn-start`, `agent/turn-end`, `agent/step-start`, and `agent/step-end`. The mirrors made consumers choose between two sources of truth for the SAME durable fact. ACP already chose the session log for the editor-facing transcript because it is the one durable, replayable record; consuming a live mirror would require reconciling its timing with the boundary already stored in that log. The stdio UI was the only production consumer that still rendered turn boundaries from the mirror events; it already rendered tool calls and results from `session/event`.
The loop records the canonical transcript in `SessionEvent` and also emitted a parallel set of live `agent/*` boundary mirror events: `agent/turn-start`, `agent/turn-end`, `agent/step-start`, and `agent/step-end`. The mirrors made consumers choose between two sources of truth for the SAME durable fact. ACP already chose the session log for prompt settlement and committed output because it is the one durable, replayable record; consuming a live mirror would require reconciling its timing with the boundary already stored in that log. The stdio UI was the only production consumer that still rendered turn boundaries from the mirror events; it already rendered tool calls and results from `session/event`.
This duplication is not free. Every lifecycle change had to update the session event, the mirror event, docs, invariants, tests, and snapshot expectations. The duplicate boundary events also made failure ordering subtle: a turn can be durably closed before a live `agent/turn-end` listener runs, so a post-boundary listener failure has no valid in-log position left and must be reported out of band.

View File

@@ -15,7 +15,7 @@ Status: implemented
## 问题
循环在 `SessionEvent` 中记录规范 transcript文本记录同时还发出一组并行的实时 `agent/*` 边界镜像事件:`agent/turn-start``agent/turn-end``agent/step-start``agent/step-end`。这些镜像迫使消费方在同一持久事实的两个真源之间做选择。ACPAgent Client Protocol已经为面向编辑器的 transcript 选择会话日志因为它是唯一持久、可重放的记录消费实时镜像需要把它的时序与日志中已经存储的边界进行调和。stdio UI 是唯一仍从镜像事件渲染轮次边界的生产消费方;它已经从 `session/event` 渲染工具调用和结果。
循环在 `SessionEvent` 中记录规范 transcript文本记录同时还发出一组并行的实时 `agent/*` 边界镜像事件:`agent/turn-start``agent/turn-end``agent/step-start``agent/step-end`。这些镜像迫使消费方在同一持久事实的两个真源之间做选择。ACPAgent Client Protocol已经为提示词结算和已提交输出选择会话日志因为它是唯一持久、可重放的记录消费实时镜像需要把它的时序与日志中已经存储的边界进行调和。stdio UI 是唯一仍从镜像事件渲染轮次边界的生产消费方;它已经从 `session/event` 渲染工具调用和结果。
这种重复并非零成本。每次生命周期变更都需要同时更新会话事件、镜像事件、文档、不变式、测试和快照预期。重复的边界事件还使失败排序变得微妙:一个轮次可能在实时 `agent/turn-end` 监听器运行之前就已被持久化关闭,因此边界之后的监听器失败在日志中已没有合法位置可以插入,只能带外上报。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-02-remove-stream-chunk-mirror.md: 5dd816940a4b2c2b63980e01f1bd4e0aac53a3e2
2026-07-02-remove-stream-chunk-mirror.zh.md: ca65a3c2ad46d14835416c14d0087ef542de03bb
2026-07-02-remove-stream-chunk-mirror.md: 8b26589a9e89f83d631fa98e801a8d3e08e105d0
2026-07-02-remove-stream-chunk-mirror.zh.md: fcd8c53b8b1b2a5b81f9f929ffd9e06ff6128e45

View File

@@ -27,7 +27,7 @@ The premise the deferral hinged on is settled: chunk persistence is authoritativ
Remove `agent/stream-chunk` from the agent event taxonomy. The token stream is read off `session/event` as `assistant/chunk`, the same feed persistence and replay already use — `session/event` is the single live transcript stream (assistant chunks, turn/step boundaries, tool activity, todos).
**Consumers.** The only production consumer that mattered — the ACP bridge (`dsh-acp`), the real editor-facing streaming surface — already renders `assistant/chunk` off `session/event`, never `agent/stream-chunk`, so it is unaffected. The stdio UI (`dsh-ui-stdio`, a disposable test REPL) was the sole live consumer; it already had a `session/event` listener (from the boundary migration), so its chunk rendering folded into that listener as an `assistant/chunk` case. Consolidating to one listener also removed a latent hazard: the `inReasoning` dim-SGR flag was previously shared across two separate listeners (`agent/stream-chunk` and `session/event`), so a chunk and a boundary racing on it had no defined order; a single listener over the append order makes the interleaving deterministic.
**Consumers.** Persistence, replay, and interactive renderers consume the authoritative session stream directly. The [automation-only ACP bridge](2026-07-23-acp-automation-only-protocol.md) emits committed `assistant/message` text rather than raw chunks, so it needs neither event. No production consumer requires an `Agent`-first token mirror.
## Scope

View File

@@ -27,7 +27,7 @@ ctx.emit('agent/stream-chunk', agent, turn, step, chunk) // ← the mirror
从 agent 事件分类体系中移除 `agent/stream-chunk`。token 流通过 `session/event` 以 `assistant/chunk` 的形式读取——持久化与回放已经使用的正是同一个序列。`session/event` 是唯一的实时 transcript文本记录assistant 分片、轮次/步骤边界、工具活动、todo
**消费方。** 唯一重要的生产消费方——ACPAgent Client Protocol桥接`dsh-acp`,面向编辑器的真实流式输出接口)——已经从 `session/event` 渲染 `assistant/chunk`,从未使用 `agent/stream-chunk`因此不受影响。stdio UI`dsh-ui-stdio`,一个一次性的测试 REPL是唯一的实时消费方它在边界迁移时已经有了 `session/event` 监听器,因此其分片渲染被折叠进该监听器作为 `assistant/chunk` 分支。合并为一个监听器还消除了一个潜在隐患:`inReasoning` dim-SGR 标志此前在两个独立监听器(`agent/stream-chunk` 和 `session/event`)之间共享,分片与边界在该标志上竞争时没有确定的顺序;单一监听器按追加顺序处理,使交错变为确定性的
**消费方。** 持久化、回放和交互式渲染器直接消费权威的会话流。[仅面向自动化的 ACPAgent Client Protocol桥接层](2026-07-23-acp-automation-only-protocol.md)发出已提交的 `assistant/message` 文本而非原始分片,因此两种事件它都不需要。没有生产消费方需要一个 `Agent` 优先的 token 镜像
## 范围

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-04-drop-image-content-block.md: 566803ab5b9f213b7dc87fcf779ed7a963d988fd
2026-07-04-drop-image-content-block.zh.md: 667ddbc1b7bce2a276fe7528f46d3e43916bad83
2026-07-04-drop-image-content-block.md: cdedf4bd5dfe60c72cea185d88b83d3b93928ff0
2026-07-04-drop-image-content-block.zh.md: 683fd1cdb47e3fcd68ff601c0b0ce4b46f8b06d2

View File

@@ -6,17 +6,17 @@ English | [中文](2026-07-04-drop-image-content-block.zh.md)
## Problem
`ImageBlock` (`packages/llm/llm/src/types.ts`) had no production producer, and every consumer on every path DROPPED it: the deepseek adapter's serializer skipped image blocks (a documented MVP limitation), the pi-ai converter skipped them as unrepresentable, the ACP codec neither advertises image prompt capability nor forwarded image blocks outbound and REJECTS image prompt content inbound, and the compaction estimator charged a flat token constant and rendered `[image]`. An `ImageBlock` constructed then would silently vanish from the wire — the vocabulary advertised a capability no path honored, which is the silent-data-loss shape AGENTS.md's defensive patterns warn against. The only constructors anywhere were tests pinning the skip/drop/estimate branches.
`ImageBlock` (`packages/llm/llm/src/types.ts`) had no production producer, and every consumer on every path DROPPED it: the DeepSeek adapter's serializer skipped image blocks (a documented MVP limitation), the pi-ai converter skipped them as unrepresentable, and the compaction estimator charged a flat token constant and rendered `[image]`. ACP independently rejected image prompt content. An `ImageBlock` constructed then would silently vanish from the provider wire — the vocabulary advertised a capability no path honored, which is the silent-data-loss shape AGENTS.md's defensive patterns warn against. The only constructors anywhere were tests pinning the skip/drop/estimate branches.
## Decision
Remove `ImageBlock`, its map entry, and image-specific branches from adapters, ACP rendering, and compaction. Update the owning vocabulary docs and generated references in the same change. Unknown extension blocks still exercise default branches, and ACP continues to reject inbound image prompt content independently of the harness vocabulary.
Remove `ImageBlock`, its map entry, and image-specific branches from adapters and compaction. Update the owning vocabulary docs and generated references in the same change. Unknown extension blocks still exercise default branches, and ACP continues to reject inbound image prompt content independently of the harness vocabulary.
## Alternatives considered
### Why not keep it?
`ContentBlockMap` can reintroduce images when adapters, ACP, and compaction all support them. Keeping a core type whose only implementation is rejection would advertise an unusable surface; absence gives producers an immediate compile-time failure instead.
`ContentBlockMap` can reintroduce images when adapters and compaction support them. ACP may remain a text-only automation protocol. Keeping a core type whose only implementation is rejection would advertise an unusable surface; absence gives producers an immediate compile-time failure instead.
The recorded fallback, had review landed on keeping the slot: keep `ImageBlock` but replace every silent skip with a loud rejection, and document that policy in the vocabulary — the silent drop was the one state with no defender. Review landed on removal; the fallback stands as the documented alternative should the slot ever return ahead of a full feature.
@@ -26,4 +26,4 @@ No harness `ImageBlock` is constructed outside Agent Note records. ACP's indepen
## Consequences
Re-adding a core vocabulary type later touches several packages at once — but that coordinated change is the shape a real multimodal feature needs anyway (adapter mapping, ACP advertisement, compaction pricing), and none of it existed to preserve.
Re-adding a core vocabulary type later touches several packages at once — but that coordinated change is the shape a real multimodal feature needs anyway (adapter mapping and compaction pricing), and none of it existed to preserve.

View File

@@ -6,17 +6,17 @@ Status: implemented
## 问题
`ImageBlock``packages/llm/llm/src/types.ts`)没有任何生产环境的生产者,而每条路径上的每个消费方都将其丢弃:deepseek 适配器的序列化器跳过 image 块(这是文档中注明的 MVP 限制pi-ai 转换器因无法表示而跳过;ACPAgent Client Protocol编解码器既不宣告图像提示词能力、也不向外转发 image 块,并且会拒绝入站的图像提示词内容;压缩compaction估算器对其收取一个固定 token 常量并渲染为 `[image]`。此时构造的 `ImageBlock` 会在协议格式wire format上静默消失——词汇宣告了一种没有任何路径兑现的能力这正是 AGENTS.md 防御性模式所警告的静默数据丢失形态。唯一的构造调用出现在测试中,用于覆盖 skip/drop/estimate 分支。
`ImageBlock``packages/llm/llm/src/types.ts`)没有任何生产环境的生产者,而每条路径上的每个消费方都将其丢弃:DeepSeek 适配器的序列化器跳过 image 块(这是文档中注明的 MVP 限制pi-ai 转换器因无法表示而跳过压缩compaction估算器对其收取一个固定 token 常量并渲染为 `[image]`ACPAgent Client Protocol独立地拒绝图像提示词内容。此时构造的 `ImageBlock` 会在提供方协议格式wire format上静默消失——词汇宣告了一种没有任何路径兑现的能力这正是 AGENTS.md 防御性模式所警告的静默数据丢失形态。唯一的构造调用出现在测试中,用于覆盖 skip/drop/estimate 分支。
## 决策
移除 `ImageBlock`、其 map 条目,以及适配器、ACP 渲染和压缩中的 image 专用分支。在同一个变更中更新所属的词汇文档与生成的引用。未知扩展块仍然覆盖默认分支ACP 继续独立于 harness 词汇拒绝入站的图像提示词内容。
移除 `ImageBlock`、其 map 条目,以及适配器和压缩中的 image 专用分支。在同一个变更中更新所属的词汇文档与生成的引用。未知扩展块仍然覆盖默认分支ACP 继续独立于 harness 词汇拒绝入站的图像提示词内容。
## 曾考虑的替代方案
### 为什么不保留?
当适配器、ACP 和压缩全部支持 image 时,`ContentBlockMap` 可以重新引入。保留一个唯一实现就是拒绝的核心类型,等于宣告一个不可用的对外服务接口;移除后,生产者会立即得到编译期错误。
当适配器和压缩支持 image 时,`ContentBlockMap` 可以重新引入。ACP 可以继续作为纯文本的自动化协议。保留一个唯一实现就是拒绝的核心类型,等于宣告一个不可用的对外服务接口;移除后,生产者会立即得到编译期错误。
评审中记录的回退方案(假如评审决定保留该槽位):保留 `ImageBlock`,但将所有静默跳过替换为显式拒绝,并在词汇文档中记录该策略——静默丢弃是唯一没有辩护者的状态。评审最终决定移除;此回退方案作为文档化的替代方案保留,以备该槽位在完整功能就绪之前回归。
@@ -26,4 +26,4 @@ Status: implemented
## 后果
日后重新添加核心词汇类型需要同时改动多个包package——但这种协调变更本就是真正的多模态功能所需的形态适配器映射、ACP 能力宣告、压缩定价),而当前并不存在需要保留的实现。
日后重新添加核心词汇类型需要同时改动多个包package——但这种协调变更本就是真正的多模态功能所需的形态适配器映射压缩定价),而当前并不存在需要保留的实现。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-04-fold-stdio-ui-helper.md: 01165df19942e8e0adf84ede3a371f53ad12e464
2026-07-04-fold-stdio-ui-helper.zh.md: 37b316283e96d7640e1e7af5fdbb05ab487aa6fa
2026-07-04-fold-stdio-ui-helper.md: b9c4c6cfb7643890a7cf4dcdeb9014d7c7158818
2026-07-04-fold-stdio-ui-helper.zh.md: d71fc878c603757f7da20aab3e7e219e368e7745

View File

@@ -22,7 +22,7 @@ The earlier support helper package was removed: its manifest, tsconfig reference
### Why not promote it to `ui/` instead?
Promotion would have resolved the support-vs-product mismatch while keeping the boundary — the right call only if the readline UI were an independently swappable integration or had a second composer, and the consumer census said neither. The structured ACP bridge stays its own package because it is the product protocol surface with its own contract and snapshot tiers; the readline helper is scaffolding for one app's front door. Re-extraction stays cheap pre-release: if a second product app wants the readline UI, split it back out then, with that consumer shaping the package contract.
Promotion would have resolved the support-vs-product mismatch while keeping the boundary — the right call only if the readline UI were an independently swappable integration or had a second composer, and the consumer census said neither. The structured ACP bridge stays its own package because it is an automation protocol surface with its own contract and snapshot tiers; the readline helper is scaffolding for one app's front door. Re-extraction stays cheap pre-release: if a second product app wants the readline UI, split it back out then, with that consumer shaping the package contract.
## Consequences

View File

@@ -22,7 +22,7 @@ readline UI 曾是一个完整的包(`packages/support/` 下的 `@deepseek-ai/
### 为什么不将其提升到 `ui/` 而是折入?
提升可以解决 support 与 product 之间的错位,同时保留边界——只有在 readline UI 是一个可独立替换的集成或有第二个组合方时才是正确选择,而消费方普查表明两者皆非。结构化的 ACPAgent Client Protocol桥接保留为独立包因为它是具有自身契约和快照层级的产品协议表面readline 辅助模块只是一个应用前门的脚手架。在发布前重新拆分成本很低:如果将来有第二个产品应用需要 readline UI届时再拆出来由那个消费方来塑造包契约。
提升可以解决 support 与 product 之间的错位,同时保留边界——只有在 readline UI 是一个可独立替换的集成或有第二个组合方时才是正确选择,而消费方普查表明两者皆非。结构化的 ACPAgent Client Protocol桥接保留为独立包因为它是具有自身契约和快照层级的自动化协议表面readline 辅助模块只是一个应用前门的脚手架。在发布前重新拆分成本很低:如果将来有第二个产品应用需要 readline UI届时再拆出来由那个消费方来塑造包契约。
## 后果

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-04-trim-acp-bridge-unreachable-surface.md: ce0c623930192d02c8347c3956c497ee13048904
2026-07-04-trim-acp-bridge-unreachable-surface.zh.md: e89b0e7c9c7c8438fb71adacf1b525a0b94bf04b
2026-07-04-trim-acp-bridge-unreachable-surface.md: 959cad37f279888fda79b1632c3553ea122803c5
2026-07-04-trim-acp-bridge-unreachable-surface.zh.md: 9127b7f167c3e5c74cdb99266828c20e79f3cb90

View File

@@ -4,16 +4,18 @@ Status: implemented
English | [中文](2026-07-04-trim-acp-bridge-unreachable-surface.zh.md)
> The handshake-identity simplification remains current. The generic-card fallback was removed when [ACP became automation-only](2026-07-23-acp-automation-only-protocol.md); UI transports retain the provider-neutral presentation contract.
## Problem
Two pieces of `dsh-acp` surface were unreachable from any shipped configuration:
1. **`AcpConfig.agentName` / `agentVersion`** (`packages/ui/acp/src/index.ts`). The shipped app package hands the bridge only `{ model }` (`packages/examples/acp-demo/src/index.ts`), so no leaf `cordis.yml` — the only production config surface — could set the knobs at all; they were settable solely by direct-mounting the bridge, which only a unit test did. Every snapshot expected output — the hook-matrix scenarios included — pins the schema defaults (`deepseek-harness-acp` / `0.0.1`). The pair also carried a live `TODO(double-default)`: the literals existed twice (schema `.default(...)` plus `??` fallbacks), with the TODO asking to pick one home.
1. **`AcpConfig.agentName` / `agentVersion`** (`packages/acp/acp/src/index.ts`). The shipped app package hands the bridge only its agent target (`packages/examples/acp-demo/src/index.ts`), so no leaf `cordis.yml` — the only production config surface — could set the knobs at all; they were settable solely by direct-mounting the bridge, which only a unit test did. Every snapshot expected output — the hook-matrix scenarios included — pins the schema defaults (`deepseek-harness-acp` / `0.0.1`). The pair also carried a live `TODO(double-default)`: the literals existed twice (schema `.default(...)` plus `??` fallbacks), with the TODO asking to pick one home.
2. **The `toolKindFor` name heuristic** (same file) special-cased `bash*`/`read*`/`write`/`edit*` tool names in the generic-fallback path. Since the [render-intent union](../architecture/2026-07-02-tool-render-intent-union.md), every first-party tool those arms matched ships its own `presentCall` carrying its kind, and the presenter-less production tools (`subagent`, `subagent_fork`) fell through to `other` anyway. The arms were production-reachable only when a tool declined to present its own call — a `presentCall` that THROWS (the containment fallback), or model arguments that fail the tool's schema so `defineTool`'s `presentCall` wrapper returns `undefined` (e.g. a `bash` call missing the required `description`) — and the bridge's own module doc states the design rule the heuristic violated: "the bridge never special-cases tool names".
## Decision
Hardcode the existing handshake identity `{ name: 'deepseek-harness-acp', version: '0.0.1' }` at initialization and remove the unreachable config fields and duplicate defaults. Replace `toolKindFor` with neutral `'other'` at both presenter fallbacks. Normal first-party presentations are unchanged; malformed or failed presentations now render an honest generic card instead of inferring a kind from the tool name. Initialize tests and snapshots pin the handshake; only the malformed calls in `hook-codex-posttool-block` change fallback card kind.
Hardcode the existing handshake identity `{ name: 'deepseek-harness-acp', version: '0.0.1' }` at initialization and remove the unreachable config fields and duplicate defaults. The original implementation also replaced `toolKindFor` with neutral `'other'` at both presenter fallbacks; ACP no longer projects tool cards, so that fallback has left the transport entirely. Initialize tests and snapshots pin the handshake.
## Alternatives considered
@@ -23,4 +25,4 @@ Branding can return when the app package exposes it to deployments. Inferring pr
## Consequences
Nothing beyond the fallback rendering trade described above — degenerate paths whose neutral card is more diagnosable than an inferred first-party one.
The bridge exposes no branding knobs. UI transports own generic presentation fallback without tool-name inference, while ACP carries no tool-card surface.

View File

@@ -4,16 +4,18 @@ Status: implemented
[English](2026-07-04-trim-acp-bridge-unreachable-surface.md) | 中文
> 握手标识简化仍然有效。通用卡片回退已随 [ACP 转为仅面向自动化](2026-07-23-acp-automation-only-protocol.md)一并移除UI 传输层保留提供方无关的展示契约。
## 问题
`dsh-acp` 有两处对外表面在任何已交付的配置中都不可达:
1. **`AcpConfig.agentName` / `agentVersion`**`packages/ui/acp/src/index.ts`)。已发布应用包只向 bridge 传递 `{ model }``packages/examples/acp-demo/src/index.ts`),因此没有任何叶子 `cordis.yml`——唯一的生产配置表面——能够设置这些配置项;只有直接挂载 bridge 才能设置它们,而这种做法只存在于一个单元测试中。每份快照预期输出——包括钩子矩阵场景——都固定 schema 默认值(`deepseek-harness-acp` / `0.0.1`)。这对配置项还带有一个尚未解决的 `TODO(double-default)`字面量存在两次schema `.default(...)``??` 后备值TODO 要求为它们选择一个归属。
1. **`AcpConfig.agentName` / `agentVersion`**`packages/acp/acp/src/index.ts`)。已发布应用包只向 bridge 传递其 agent 的提供方/模型目标`packages/examples/acp-demo/src/index.ts`),因此没有任何叶子 `cordis.yml`——唯一的生产配置表面——能够设置这些配置项;只有直接挂载 bridge 才能设置它们,而这种做法只存在于一个单元测试中。每份快照预期输出——包括钩子矩阵场景——都固定 schema 默认值(`deepseek-harness-acp` / `0.0.1`)。这对配置项还带有一个尚未解决的 `TODO(double-default)`字面量存在两次schema `.default(...)``??` 后备值TODO 要求为它们选择一个归属。
2. **`toolKindFor` 名称启发式**(同一文件)在通用回退路径中对 `bash*`/`read*`/`write`/`edit*` 工具名做了特殊处理。自[render-intent 联合类型](../architecture/2026-07-02-tool-render-intent-union.md)以来,这些分支匹配到的每个第一方工具都自带 `presentCall` 并携带其 kind而没有 presenter 的生产工具(`subagent``subagent_fork`)本来就落入 `other`。这些分支只有在工具拒绝自行呈现调用时才在生产中可达:`presentCall` 抛出异常(容错回退),或模型参数未通过工具 schema 导致 `defineTool``presentCall` 包装层返回 `undefined`(例如 `bash` 调用缺少必需的 `description`)。而桥接层自身的模块文档明确声明了该启发式所违反的设计规则:"桥接层绝不对工具名做特殊处理"。
## 决策
在初始化时硬编码现有的握手标识 `{ name: 'deepseek-harness-acp', version: '0.0.1' }`,移除不可达的配置字段与重复默认值。在两个 presenter 回退处`toolKindFor` 替换为中性的 `'other'`。正常的第一方呈现不受影响;格式错误或失败的呈现现在会渲染一个诚实的通用卡片,而非从工具名推断 kind。初始化测试和快照固定握手标识只有 `hook-codex-posttool-block` 中格式错误的调用改变了回退卡片的 kind
在初始化时硬编码现有的握手标识 `{ name: 'deepseek-harness-acp', version: '0.0.1' }`,移除不可达的配置字段与重复默认值。最初的实现还在两个 presenter 回退处将 `toolKindFor` 替换为中性的 `'other'`ACP 不再投影工具卡片,因此该回退已完全离开传输层。初始化测试和快照固定握手标识
## 曾考虑的替代方案
@@ -23,4 +25,4 @@ Status: implemented
## 后果
除上述回退渲染的取舍外没有其他影响——退化路径下,中性卡片比推断出的第一方卡片更易于诊断
桥接层不暴露品牌配置项。UI 传输层拥有不做工具名推断的通用展示回退,而 ACP 不承载任何工具卡片表面

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-20-retire-readline-front-door.md: d8e6a5c172b576ce6bc76911186c9f81a4ece88f
2026-07-20-retire-readline-front-door.zh.md: bea685cdc3f8f1530d56ae3eb5dcc34cff0b46af
2026-07-20-retire-readline-front-door.md: bf2ff2e1498668e4a45b9fcf310d92b2f3ea3099
2026-07-20-retire-readline-front-door.zh.md: ee22437a9e7ab8fbde612ba14f296c3d230928f1

View File

@@ -26,13 +26,13 @@ Pipes remain the default test medium. PTY-driven subprocess tests are sanctioned
- `examples/echo-agent/tests/echo.e2e.ts` proves the Loader boot + mock-model tool round-trip through `stream-json` records instead of readline transcript lines.
- The CI demo-smoke gate (`scripts/run-gates.ts`, AGENTS.md) runs `demo:echo --output-format stream-json -p "echo ci smoke"` and parses the records structurally.
- The TUI's piped-launch refusal (nonzero exit + pointer at the one-shot CLI) is covered by the `dsh` TTY guard exercised in `examples/tui-agent`'s PTY smoke; the echo-round-trip-under-plain-Node and missing-config fail-loud proofs live in `cli-demo`'s built-bin suite.
- The TUI's piped-launch refusal (nonzero exit + pointer at the one-shot CLI) is covered by `apps/cli/tests/built-bin.e2e.ts` (the `dsh` TTY guard under plain Node); the echo-round-trip-under-plain-Node and missing-config fail-loud proofs live in `cli-demo`'s built-bin suite.
- `packages/context/time-context/tests/time-context.e2e.ts` runs one one-shot turn; multi-turn elapsed rendering stays unit-covered in its spec.
## Accepted losses
- **Piped multi-turn in one process** — the readline channel could script several turns over stdin; the one-shot bin runs one task per process. Multi-turn continuity is covered by `RESUME_SESSION_ID`/resume e2es and the TUI's scripted PTY conversation.
- **Non-TTY `ask_user_question`** — the readline provider was the only non-TTY terminal implementation of `ctx.userInteraction`. A headless run whose model calls `ask_user_question` now fails that tool call (no provider); the ACP bridge remains the non-terminal provider. A future headless deployment that needs it composes its own provider.
- **Non-TTY `ask_user_question`** — the readline provider was the only non-TTY terminal implementation of `ctx.userInteraction`. A headless or ACP automation run whose model calls `ask_user_question` fails that tool call unless its composition supplies a provider; Web owns the shipped non-terminal provider.
## Alternatives considered

View File

@@ -26,13 +26,13 @@ Status: implemented
- `examples/echo-agent/tests/echo.e2e.ts` 通过 `stream-json` 记录证明 Loader 启动 + mock 模型的工具往返,而不是匹配 readline 文本记录行。
- CI 演示冒烟门禁(`scripts/run-gates.ts`、AGENTS.md运行 `demo:echo --output-format stream-json -p "echo ci smoke"` 并结构化解析记录。
- TUI 对管道启动的拒绝(非零退出 + 指向单次任务 CLI 的提示)由 `examples/tui-agent` 的 PTY 冒烟测试所行使`dsh` TTY 守卫覆盖;纯 Node 下的 echo 往返证明与缺失配置的快速失败证明位于 `cli-demo` 的 built-bin 套件。
- TUI 对管道启动的拒绝(非零退出 + 指向单次任务 CLI 的提示)由 `apps/cli/tests/built-bin.e2e.ts`(纯 Node 下`dsh` TTY 守卫覆盖;纯 Node 下的 echo 往返证明与缺失配置的快速失败证明位于 `cli-demo` 的 built-bin 套件。
- `packages/context/time-context/tests/time-context.e2e.ts` 运行一个单次任务轮次;多轮 elapsed 渲染仍由其单元测试覆盖。
## 接受的损失
- **单进程内的管道多轮对话**——readline 通道可以通过 stdin 脚本化多个轮次;单次任务 bin 每个进程只运行一个任务。多轮连续性由 `RESUME_SESSION_ID`/resume e2e 和 TUI 的脚本化 PTY 对话覆盖。
- **非 TTY 的 `ask_user_question`**——readline 提供方是 `ctx.userInteraction` 唯一的非 TTY 终端实现。模型调用 `ask_user_question` 的 headless 运行现在会让该工具调用失败没有提供方ACP 桥接仍是非终端提供方。未来需要它的 headless 部署自行组合提供方
- **非 TTY 的 `ask_user_question`**——readline 提供方是 `ctx.userInteraction` 唯一的非 TTY 终端实现。模型调用 `ask_user_question` 的 headless 或 ACP 自动化运行会让该工具调用失败,除非其组合提供相应的 providerWeb 拥有已交付的非终端 provider
## 曾考虑的替代方案

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-22-plan-specific-collaboration-state.md: 2fc163213ca0ee1de5633e4d7db14a814b2f7bb2
2026-07-22-plan-specific-collaboration-state.zh.md: 811f657bf31c96dde88e400fc25fe2fe6df1f157
2026-07-22-plan-specific-collaboration-state.md: d6b606d2235b5dbcb7e1882dd34e8965799c1199
2026-07-22-plan-specific-collaboration-state.zh.md: c0dc22f6ec296a293681b78cebe7cc05f49f774b

View File

@@ -8,7 +8,7 @@ English | [中文](2026-07-22-plan-specific-collaboration-state.zh.md)
The first plan-mode implementation introduced a generic named-mode registry even though the product shipped only `plan`. `ModeConfig.modes`, definition-name validation, `ctx.modes.list()`, retired-definition fallback, and a synthetic `review` mode in tests existed only to support hypothetical future collaboration modes. The production-specific behavior—plan guidance, `/plan`, and `exit_plan_mode`—still lived in the same package, so the generic API did not isolate a reusable mechanism from plan policy.
The word “mode” also spans unrelated domains. Sandbox mode is an enforcing policy owned by `ctx.sandboxPolicy` and logged as `sandbox/mode`; plan mode is a collaboration stance that contributes guidance and a reviewed exit. Treating both as instances of one named-mode abstraction would obscure their independent ownership. ACP's protocol happens to expose a generic mode picker, but that is an adapter vocabulary rather than evidence that the harness needs a generic mode domain.
The word “mode” also spans unrelated domains. Sandbox mode is an enforcing policy owned by `ctx.sandboxPolicy` and logged as `sandbox/mode`; plan mode is a collaboration stance that contributes guidance and a reviewed exit. Treating both as instances of one named-mode abstraction would obscure their independent ownership. A transport's generic vocabulary is not evidence that the harness needs a generic mode domain.
## Decision
@@ -16,7 +16,7 @@ Plan mode owns a plan-specific product package: `@deepseek-ai/dsh-plan-mode` at
Configuration is exactly `{ section: string }`. The package registers the fixed `plan:policy` section, `/plan [message]`, the exact `/plan off` direct-exit form, and `exit_plan_mode` itself. Bare `/plan` selects active; another non-empty argument selects it first and then sends the trimmed text through `agent.steer()`, making the text an ordinary logged user message in the affected step. `/plan off` selects inactive without model input and can cancel an entry that is still pending at the boundary. The exit tool remains registered while plan mode is inactive so the request tool catalog stays stable.
ACP keeps its protocol-level `default` and `plan` ids. The bridge maps those two ids to the boolean service, advertises only that fixed pair, rejects every other id at the adapter boundary, and maps committed `plan/mode` events back to `current_mode_update`. The protocol remains generic without forcing genericity into the product domain.
Human-facing compositions own plan selection and review. This note originally kept ACP's protocol-level `default`/`plan` picker as an adapter over the boolean service; [ACP as an automation-only protocol](2026-07-23-acp-automation-only-protocol.md) supersedes that wire projection, so the ACP composition now mounts neither plan mode nor a mode-selection protocol.
Sandbox mode and approval policy remain separate enforcement axes. Plan mode neither reads nor writes them, and the simplification introduces no shared base type, registry, or preset abstraction across those concepts.
@@ -33,15 +33,14 @@ Sandbox mode and approval policy remain separate enforcement axes. Plan mode nei
**Fold sandbox mode into the same service.** Rejected because collaboration guidance and execution confinement have different owners, lifecycle semantics, and consumers. Their shared English noun is not a domain relationship.
**Let ACP own plan state.** Rejected because TUI, resume, fork, prompt assembly, and the exit tool need the same logged fact independently of ACP. ACP owns only the wire projection.
**Let one presentation transport own plan state.** Rejected because TUI, Web, resume, fork, prompt assembly, and the exit tool need the same logged fact independently of any one transport. Presentation adapters own only their projections.
## Verification
- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and Code Mode schemas, review outcomes, and invariant coverage through the boolean service.
- Command tests cover bare `/plan`, `/plan <message>`, active `/plan off`, pending-entry cancellation, inactive idempotence, absence of `/mode` and `/review`, and effect-scoped removal.
- ACP tests cover fixed advertisement, both ids, unknown-id rejection, optimistic updates, committed exits, and load replay.
- The keyless TUI scenarios enter through `/plan <message>`, leave through `/plan off`, and prove that each committed `plan/mode` precedes the request header it changes, the entry message is logged under plan guidance, and the post-exit request omits that guidance.
## Consequences
The implementation has one vocabulary for one shipped feature. Adding another collaboration stance is now an explicit design decision instead of a config entry, while ACP clients continue to see their standard mode picker. The migration intentionally rejects old `mode/set` logs and old `modes.plan.section` configuration under the repository's pre-release format policy.
The implementation has one vocabulary for one shipped feature. Adding another collaboration stance is an explicit design decision instead of a config entry, and automation clients do not acquire human mode controls through ACP. The migration intentionally rejects old `mode/set` logs and old `modes.plan.section` configuration under the repository's pre-release format policy.

View File

@@ -8,7 +8,7 @@ Status: implemented
产品只交付了 `plan`,首个 plan mode 实现却引入了通用的具名模式注册表。`ModeConfig.modes`、定义名称校验、`ctx.modes.list()`、已退役定义的回退逻辑,以及测试中合成的 `review` 模式都只为支持假想中的未来协作模式而存在。plan 引导、`/plan``exit_plan_mode` 这些生产专用行为仍位于同一个包package因此通用 API 并未将可复用机制与 plan 策略隔离开来。
「mode」一词还横跨互不相关的领域。沙箱模式是由 `ctx.sandboxPolicy` 拥有、以 `sandbox/mode` 记录日志的强制执行策略plan mode 则是一种协作方式,会贡献引导内容和经评审的退出路径。若把两者都视为同一个具名模式抽象的实例,就会掩盖二者各自独立的归属关系。ACPAgent Client Protocol协议恰好暴露了通用模式选择器但这只是适配器词汇并不能证明 harness 需要通用模式领域。
「mode」一词还横跨互不相关的领域。沙箱模式是由 `ctx.sandboxPolicy` 拥有、以 `sandbox/mode` 记录日志的强制执行策略plan mode 则是一种协作方式,会贡献引导内容和经评审的退出路径。若把两者都视为同一个具名模式抽象的实例,就会掩盖二者各自独立的归属关系。传输协议的通用词汇并不能证明 harness 需要通用模式领域。
## 决策
@@ -16,7 +16,7 @@ Plan mode 拥有一个 plan 专用产品包:位于 `packages/plan/plan-mode/`
配置严格为 `{ section: string }`。该包自行注册固定的 `plan:policy` 段、`/plan [message]`、精确匹配的 `/plan off` 主动退出形式,以及 `exit_plan_mode`。不带参数的 `/plan` 选择激活;其他非空参数则先选择激活,再通过 `agent.steer()` 发送去除首尾空白后的文本,使该文本在受影响的步骤中成为一条记录到日志的普通用户消息。`/plan off` 选择未激活,不产生模型输入,并可取消仍待在边界生效的进入选择。即使 plan mode 未激活,退出工具仍保持注册,以确保请求工具目录稳定。
ACP 保留协议`default``plan` id。桥接层把这两个 id 映射到布尔服务,只公布这组固定选项,在适配器边界拒绝其他所有 id并把已提交的 `plan/mode` 事件映射回 `current_mode_update`。协议仍保持通用性,但不会迫使产品领域也采用通用抽象
面向人类的组合拥有 plan 选择与评审。本笔记最初把 ACP 协议`default`/`plan` 选择器保留为布尔服务之上的适配器;[ACP 作为仅面向自动化的协议](2026-07-23-acp-automation-only-protocol.md)取代了那个线上投影,因此 ACP 组合现在既不挂载 plan mode也不提供模式选择协议
沙箱模式与审批策略仍是彼此独立的强制约束轴。Plan mode 既不读取也不写入二者;此次简化也没有为这些概念引入共享基类型、注册表或预设抽象。
@@ -33,15 +33,14 @@ ACP 保留协议层的 `default` 和 `plan` id。桥接层把这两个 id 映射
**将沙箱模式折叠进同一服务。** 不予采纳因为协作引导与执行约束有不同的归属方、生命周期语义和消费方。二者的英文名称都含「mode」不代表存在领域关系。
**让 ACP 拥有 plan 状态。** 不予采纳,因为 TUI、恢复、fork、提示词组装和退出工具都需要在 ACP 之外独立使用同一项已记录事实。ACP 只拥有协议投影。
**让一种呈现传输拥有 plan 状态。** 不予采纳,因为 TUI、Web、恢复、fork、提示词组装和退出工具都需要独立于任何单一传输使用同一项已记录事实。呈现适配器只拥有各自的投影。
## 验证
- 包测试通过布尔服务继续覆盖边界顺序、重试、追加失败、HMR热模块替换资源释放、提示词组装、稳定的原生 schema 与 Code Mode schema、评审结果和不变式。
- 命令测试覆盖不带参数的 `/plan``/plan <message>`、激活状态下的 `/plan off`、取消待生效的进入选择、未激活状态下的幂等性、不存在 `/mode``/review`,以及随 effect 作用域移除。
- ACP 测试覆盖固定模式列表公布、两个 id、未知 id 拒绝、乐观更新、已提交退出和加载回放。
- 无密钥 TUI 场景通过 `/plan <message>` 进入、通过 `/plan off` 退出,并证明每个已提交的 `plan/mode` 都先于其所改变的请求头,进入消息在 plan 引导下记录到日志,且退出后的请求不含该引导。
## 后果
该实现只用一套词汇描述一项已交付功能。若要添加另一种协作方式,必须显式作出设计决策,而不能只增加配置项;ACP 客户端仍可看到标准模式选择器。根据仓库的预发布格式策略,本次迁移有意拒绝旧的 `mode/set` 日志与 `modes.plan.section` 配置。
该实现只用一套词汇描述一项已交付功能。若要添加另一种协作方式,必须显式作出设计决策,而不能只增加配置项;自动化客户端不会通过 ACP 获得面向人类的模式控制。根据仓库的预发布格式策略,本次迁移有意拒绝旧的 `mode/set` 日志与 `modes.plan.section` 配置。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-22-tui-titles-from-session-title-service.md: b54b99647230255cf241415f94aa21b2630c44cd
2026-07-22-tui-titles-from-session-title-service.zh.md: 67cc3332f0694887d5af0d71997d140b74669f46
2026-07-22-tui-titles-from-session-title-service.md: 735c940dbb8a84104ab4320d5c535b41690953d5
2026-07-22-tui-titles-from-session-title-service.zh.md: 8e7e3ef070cc6518476fe0f53355cb0705e74c6a

View File

@@ -6,7 +6,7 @@ English | [中文](2026-07-22-tui-titles-from-session-title-service.zh.md)
## Problem
Two model-title implementations coexisted after the tui-staging line merged onto master. The TUI carried its own `autoTitle` feature: a fire-and-forget `ctx.llm.stream` call after the first user message that set the terminal window title via OSC 0, with a one-shot latch, its own prompt, its own 40-character cap, and its own resume re-derivation ([auto-title Agent Note](../feature/2026-07-21-tui-auto-pane-title.md), [default-on Agent Note](../feature/2026-07-21-tui-auto-title-default-on.md)). Master had meanwhile landed [log-backed session titles](../feature/2026-07-21-log-backed-session-titles.md): a `sessionTitle` capability whose accepted revisions are durable `session/title` events, with a deterministic fallback and optional model providers. The TUI already consumed `session/title` for its header subtitle and window title, so a session could be titled twice by different strategies, and the TUI's process-local title was invisible to every other consumer (ACP, resume listings, forks).
Two model-title implementations coexisted after the tui-staging line merged onto master. The TUI carried its own `autoTitle` feature: a fire-and-forget `ctx.llm.stream` call after the first user message that set the terminal window title via OSC 0, with a one-shot latch, its own prompt, its own 40-character cap, and its own resume re-derivation ([auto-title Agent Note](../feature/2026-07-21-tui-auto-pane-title.md), [default-on Agent Note](../feature/2026-07-21-tui-auto-title-default-on.md)). Master had meanwhile landed [log-backed session titles](../feature/2026-07-21-log-backed-session-titles.md): a `sessionTitle` capability whose accepted revisions are durable `session/title` events, with a deterministic fallback and optional model providers. The TUI already consumed `session/title` for its header subtitle and window title, so a session could be titled twice by different strategies, and the TUI's process-local title was invisible to resume listings, forks, and Web consumers.
## Decision

View File

@@ -6,7 +6,7 @@ Status: implemented
## 问题
tui-staging 分支合入 master 后两套模型标题实现并存。TUI 自带 `autoTitle` 特性:在首条用户消息后发起一次 fire-and-forget 的 `ctx.llm.stream` 调用,通过 OSC 0 设置终端窗口标题,带有一次性闩锁、自己的提示词、自己的 40 字符截断和自己的恢复重推导([auto-title Agent Note](../feature/2026-07-21-tui-auto-pane-title.md)、[default-on Agent Note](../feature/2026-07-21-tui-auto-title-default-on.md))。而 master 已落地[日志承载的会话标题](../feature/2026-07-21-log-backed-session-titles.md):一个 `sessionTitle` 能力,其被接受的修订是持久的 `session/title` 事件,带确定性回退和可选的模型 provider。TUI 已经消费 `session/title` 作为横幅副标题和窗口标题,于是一个会话可能被两种策略各标题一次,且 TUI 的进程本地标题对其他所有消费者ACP、恢复列表、fork不可见。
tui-staging 分支合入 master 后两套模型标题实现并存。TUI 自带 `autoTitle` 特性:在首条用户消息后发起一次 fire-and-forget 的 `ctx.llm.stream` 调用,通过 OSC 0 设置终端窗口标题,带有一次性闩锁、自己的提示词、自己的 40 字符截断和自己的恢复重推导([auto-title Agent Note](../feature/2026-07-21-tui-auto-pane-title.md)、[default-on Agent Note](../feature/2026-07-21-tui-auto-title-default-on.md))。而 master 已落地[日志承载的会话标题](../feature/2026-07-21-log-backed-session-titles.md):一个 `sessionTitle` 能力,其被接受的修订是持久的 `session/title` 事件,带确定性回退和可选的模型 provider。TUI 已经消费 `session/title` 作为横幅副标题和窗口标题,于是一个会话可能被两种策略各标题一次,且 TUI 的进程本地标题对恢复列表、fork 和 Web 消费方不可见。
## 决策

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-23-acp-automation-only-protocol.md: 2a92f306065b348764f35e1e63f0d7750a636372
2026-07-23-acp-automation-only-protocol.zh.md: 5889d668310e3bd63f3934a39d4e5250f83f063d

View File

@@ -0,0 +1,47 @@
# Agent Note: ACP as an automation-only protocol
Status: implemented
English | [中文](2026-07-23-acp-automation-only-protocol.zh.md)
## Problem
The ACP bridge had become a second interactive product UI. It translated durable events into editor cards, terminal metadata, diffs, plans, titles, reasoning, commands, modes, model and permission pickers, session navigation, and human elicitation. Those responsibilities duplicated the TUI and the Web client while coupling an automation transport to UI services, persistence queries, presentation policy, and editor-specific conventions.
ACP still has one useful role: another agent or automated controller can start a harness process, create an isolated session, send text, receive the committed answer, cancel work, and answer a permission request. The out-of-process ACP subagent backend depends on that standard protocol boundary.
The snapshot suite complicates removal. Most ACP scenarios exercise the assembled agent backend rather than ACP presentation, so deleting the suite with the editor bridge would discard broad keyless behavioral coverage.
## Decision
`@deepseek-ai/dsh-acp` is an automation transport under [`packages/acp/acp`](../../../../packages/acp/acp/README.md), outside the `ui` package group. Its public protocol is intentionally small: version negotiation, fresh text sessions with one in-flight prompt each, committed assistant text updates, per-session cancellation, concurrent sessions, and connection-owned teardown. Prompts carry the spec-required baseline only — text plus resource links flattened to bracketed textual references; the bridge rejects additional directories, MCP servers, beyond-baseline prompt content (image, audio, embedded resources), empty prompts, unknown sessions, and overlapping prompts.
The bridge emits only committed `assistant/message` text. Reasoning, raw chunks, tool activity, todos, plans, titles, retry markers, terminal metadata, diffs, locations, and resource links remain in the durable session log or in UI-specific transports. It does not provide session load/list/delete, commands, modes, configuration selectors, model switching, plan review, or human elicitation.
One-shot `session/request_permission` remains. It is a machine policy channel for bridge-owned agents, not a human approval UI: the client chooses allow once, reject once, or cancel, and the bridge never turns that response into a durable grant. [`dsh-subagent-acp`](../../../../packages/subagent/subagent-acp/README.md) uses this channel programmatically.
The app composition contains the agent spine, persistence, checkpoint policy, and ACP transport. It does not mount command, session-query, session-reference, plan-mode, permission-picker, or user-interaction services for ACP. SDK scaffolding likewise treats `ask_user_question` as TUI-only.
Disconnect and plugin disposal share one memoized quiescence boundary. Both successful and failed transport closure settle pending prompts as cancelled, dispose every bridge-owned agent, and await loop and session cleanup. A create that loses the close race disposes its unpublished handle.
## Snapshot boundary
The ACP snapshot suite still boots the assembled ACP example and retains scenarios that pin backend behavior. Only scenarios driven through deleted UI methods leave the suite; semantic-checkpoint recovery runs through the headless `stream-json` example because ACP no longer loads sessions.
## Alternatives considered
**Keep ACP as an editor UI until Web reaches parity.** Rejected because it leaves two interactive contracts to evolve and keeps editor conventions in the automation boundary.
**Replace ACP with a private subagent RPC.** Rejected because ACP already supplies a typed, interoperable process protocol and is used by the out-of-process subagent backend.
**Remove machine permission requests with the other interaction features.** Rejected because an automated parent must answer a child agent's one-shot policy decision; this is control flow between agents, not presentation.
**Delete the ACP snapshot suite or migrate every scenario in this change.** Rejected because most scenarios test the backend and remain valuable, while a full harness migration is an independent testing change. Only scenarios whose driver was a deleted UI method leave this suite.
## Consequences
ACP has a narrow contract suitable for agents and automation, while TUI and Web own human interaction and presentation. The package has fewer injected services, dependencies, protocol branches, and lifecycle states, and it no longer claims compatibility as a general editor front door.
Automation clients receive complete committed text rather than token deltas or structured tool UI. They inspect durable logs or another API when they need reasoning, tool traces, titles, or richer state. Fresh-session-only operation also means callers that need durable browsing or resume use a host API rather than ACP.
Backend snapshot coverage therefore remains transport-coupled to ACP even though that transport is incidental to the behavior under test.

View File

@@ -0,0 +1,47 @@
# Agent NoteACP 作为仅面向自动化的协议
Status: implemented
[English](2026-07-23-acp-automation-only-protocol.md) | 中文
## 问题
ACPAgent Client Protocol桥接层已经变成第二套交互式产品 UI。它将持久事件转换为编辑器卡片、终端元数据、diff、计划、标题、推理、命令、模式、模型和权限选择器、会话导航以及面向人类的询问。这些职责与 TUI 和 Web 客户端重复,同时将自动化传输层与 UI 服务、持久化查询、展示策略和编辑器特定约定耦合在一起。
ACP 仍有一个有用的职责:另一个 agent智能体或自动化控制器可以启动 harness 进程、创建隔离会话、发送文本、接收已提交的回答、取消工作并回答权限请求。跨进程 ACP subagent 后端依赖这个标准协议边界。
快照套件使移除工作更复杂。大多数 ACP 场景测试的是组装后的 agent 后端,而不是 ACP 展示层;如果随编辑器桥接层一起删除整个套件,就会丢失大量无密钥行为覆盖。
## 决策
`@deepseek-ai/dsh-acp` 是位于 [`packages/acp/acp`](../../../../packages/acp/acp/README.md) 下、独立于 `ui` 包组的自动化传输层。其公开协议特意保持精简版本协商、全新文本会话每个会话最多允许一个进行中的提示词、已提交的助手文本更新、按会话取消、并发会话以及由连接负责的资源清理。提示词只承载规范要求的基线内容——文本加上被展平为方括号文本引用的资源链接桥接层会拒绝附加目录、MCP 服务器、超出基线的提示词内容(图片、音频、内嵌资源)、空提示词、未知会话和重叠提示词。
桥接层只发出已提交的 `assistant/message` 文本。推理、原始分片、工具活动、待办事项、计划、标题、重试标记、终端元数据、diff、位置和资源链接仍保留在持久会话日志或 UI 专用传输层中。它不提供会话加载、列出与删除、命令、模式、配置选择器、模型切换、plan 评审或面向人类的询问。
保留一次性 `session/request_permission`。它是为桥接层拥有的 agent 提供的机器策略通道,而不是面向人类的审批 UI客户端可选择允许一次、拒绝一次或取消桥接层绝不会将该响应转换为持久授权。[`dsh-subagent-acp`](../../../../packages/subagent/subagent-acp/README.md) 会以程序化方式使用该通道。
应用组装包含 agent 主干、持久化、检查点策略和 ACP 传输层。它不会为 ACP 挂载命令、会话查询、会话引用、plan mode、权限选择器或用户交互服务。SDK 脚手架同样将 `ask_user_question` 视为 TUI 专属功能。
断开连接与插件 dispose资源释放共享同一个经记忆化处理的静止边界。传输关闭无论成功还是失败都会将待处理提示词以已取消状态结算dispose 每个由桥接层拥有的 agent并等待循环和会话清理完成。创建流程如果在与关闭的竞态中落败就会 dispose 其尚未发布的 handle。
## 快照边界
ACP 快照套件仍会启动组装后的 ACP 示例,并保留用于锁定后端行为的场景。从该套件移出的只有通过已删除的 UI 方法驱动的场景;由于 ACP 不再加载会话,语义检查点恢复通过 headless `stream-json` 示例执行。
## 考虑过的替代方案
**在 Web 达到同等能力前,继续将 ACP 作为编辑器 UI。** 不予采用,因为这会留下两套需要演进的交互契约,并使编辑器约定继续存在于自动化边界中。
**用私有 subagent RPC 替换 ACP。** 不予采用,因为 ACP 已经提供类型化、可互操作的进程协议,并由跨进程 subagent 后端使用。
**随其他交互功能一起移除机器权限请求。** 不予采用,因为自动化父 agent 必须回答子 agent 的一次性策略决策;这是 agent 之间的控制流,而不是展示层。
**删除 ACP 快照套件,或在本次变更中迁移每个场景。** 不予采用,因为大多数场景测试后端且仍有价值,而完整的 harness 迁移是一项独立的测试变更。只有驱动脚本依赖已删除 UI 方法的场景才离开该套件。
## 结果
ACP 具有适合 agent 与自动化的精简契约,而 TUI 和 Web 拥有面向人类的交互与展示。该包注入的服务、依赖、协议分支和生命周期状态更少,也不再将自身定位为通用编辑器入口。
自动化客户端收到完整的已提交文本,而不是 token 增量或结构化工具 UI。当它们需要推理、工具跟踪信息、标题或更丰富的状态时需要查看持久日志或其他 API。只支持全新会话也意味着需要浏览持久会话或恢复会话的调用方必须使用 host API而不是 ACP。
因此,后端快照覆盖仍与 ACP 传输层耦合,尽管对于受测行为而言,该传输层只是附带因素。