Merge remote-tracking branch 'origin/master' into codex/basic-session-search

# Conflicts:
#	packages/client/connection/src/client/fixture.ts
#	packages/client/runtime/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.md
#	packages/host/apiproxy/README.zh.md
This commit is contained in:
Hypatia May
2026-07-27 15:34:41 +08:00
68 changed files with 1082 additions and 73 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-29-todo-write-tool.md: df1bee2801b0e01b290b63f6edbe2e5b1be80cb7
2026-06-29-todo-write-tool.zh.md: 7fa5cb2aad2b32ef0662df04ff6576be14a3a8e7
2026-06-29-todo-write-tool.md: 760373d64f462e3717a174d5793e6d47ab76b0b4
2026-06-29-todo-write-tool.zh.md: fd29e6049e6a729c2c7e78860ad7c065422da60e

View File

@@ -10,7 +10,7 @@ The harness gives the model bash and subagent tools but no way to record a struc
## Decision
Add a model-facing `todo_write(todos: [{ content, status }])` tool whose whole-list state lives on the event-sourced session log as a new `todo/write` `SessionEventMap` variant. Interactive hosts render from the durable event; the TUI folds it directly, while the [automation-only ACP bridge](../simplification/2026-07-23-acp-automation-only-protocol.md) deliberately omits todo presentation.
Add a model-facing `todo_write(todos: [{ content, status }])` tool whose whole-list state lives on the event-sourced session log as a new `todo/write` `SessionEventMap` variant. Interactive hosts render from the durable event: the TUI folds it directly, the web client projects it into `ConversationSnapshot.todos` ([web todo display](2026-07-23-web-todo-display.md)), while the [automation-only ACP bridge](../simplification/2026-07-23-acp-automation-only-protocol.md) deliberately omits todo presentation.
### Whole-list replace, three-state status
@@ -18,7 +18,7 @@ The model sends the entire list every call; the new list replaces the old (last-
### State on the session log, not a service
The list is appended as a `todo/write` event carrying the full `{ todos }` snapshot. The harness is event-sourced — the LLM history, tool calls, and turn structure all live on the log — so the todo list lives there too. This buys durability, replay, and resume reconstruction for free: a reopened session re-derives the current list from the latest `todo/write`, with no separate persistence backend, in-memory service to rehydrate, or extra wiring. An in-memory `ctx.todos` service would have to reinvent all of that.
The list is appended as a `todo/write` event carrying the full `{ todos }` snapshot. The harness is event-sourced — the LLM history, tool calls, and turn structure all live on the log — so the todo list lives there too. This buys durability, replay, and resume reconstruction for free: a reopened session re-derives the current list from the latest `todo/write`, with no separate persistence backend, in-memory service to rehydrate, or extra wiring. An in-memory `ctx.todos` service would have to reinvent all of that. (Full-log consumers get this reconstruction outright; the web client's paged window gets it from the tail history page's host-computed projection — see the [web todo display note](2026-07-23-web-todo-display.md).)
### NOT a surface event

View File

@@ -10,7 +10,7 @@ harness 为模型提供了 bash 和 subagent 工具,却没有办法记录结
## 决策
新增一个面向模型的 `todo_write(todos: [{ content, status }])` 工具,其整列表状态作为新的 `todo/write` `SessionEventMap` 变体存储在事件溯源的会话日志上。交互式宿主从持久事件渲染TUI 直接折叠它,而[仅面向自动化的 ACPAgent Client Protocol桥接层](../simplification/2026-07-23-acp-automation-only-protocol.md)有意省略 todo 展示。
新增一个面向模型的 `todo_write(todos: [{ content, status }])` 工具,其整列表状态作为新的 `todo/write` `SessionEventMap` 变体存储在事件溯源的会话日志上。交互式宿主从持久事件渲染TUI 直接折叠它,web 客户端将其投影进 `ConversationSnapshot.todos`[web todo 展示](2026-07-23-web-todo-display.md)而[仅面向自动化的 ACPAgent Client Protocol桥接层](../simplification/2026-07-23-acp-automation-only-protocol.md)有意省略 todo 展示。
### 整列表替换,三态 status
@@ -18,7 +18,7 @@ harness 为模型提供了 bash 和 subagent 工具,却没有办法记录结
### 状态在会话日志上,而非服务
列表作为 `todo/write` 事件追加到日志,携带完整的 `{ todos }` 快照。harness 是事件溯源的——LLM大语言模型历史、工具调用和轮次结构都在日志上——所以 todo 列表也在那里。这免费获得了持久性、回放和恢复重建:重新打开的会话从最新的 `todo/write` 重新推导当前列表,无需独立的持久化后端、无需重新注水的内存服务、无需额外接线。一个内存中的 `ctx.todos` 服务需要重新发明以上所有。
列表作为 `todo/write` 事件追加到日志,携带完整的 `{ todos }` 快照。harness 是事件溯源的——LLM大语言模型历史、工具调用和轮次结构都在日志上——所以 todo 列表也在那里。这免费获得了持久性、回放和恢复重建:重新打开的会话从最新的 `todo/write` 重新推导当前列表,无需独立的持久化后端、无需重新注水的内存服务、无需额外接线。一个内存中的 `ctx.todos` 服务需要重新发明以上所有。(全量 log 消费者直接获得这份重建web 客户端的分页窗口则从尾页 history 携带的 host 计算投影获得——见 [web todo 展示 Note](2026-07-23-web-todo-display.md)。)
### 不是 surface 事件

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 .agents/notes/implemented/feature/2026-07-23-web-todo-display.md
2026-07-23-web-todo-display.md: 830f55c86c893c4942a1a9d3b8529395d5f5e38b
2026-07-23-web-todo-display.zh.md: e68928d7eddaaa92ac831722a738ee2002342b38

View File

@@ -0,0 +1,36 @@
# Agent Note: Web todo display — snapshot side-effect channel + two render surfaces
Status: implemented
English | [中文](2026-07-23-web-todo-display.zh.md)
## Problem
`todo_write` appends `todo/write` whole-list snapshots to the session log; the TUI renders a persistent plan panel (the automation-only ACP bridge deliberately omits todo presentation). The web client dropped the event entirely: the host mux stream already forwards every session event, but `todo/write` is not a surface type (it never folds into `ConversationSnapshot.nodes`), and no side-effect branch accumulated it — the browser had no consumption point and no display surface.
## Decision
Consume `todo/write` as a Session side effect, not a surface node, and render it on two surfaces matching the split the TUI already draws.
### Side-effect channel, converging with window replay
`applyEventSideEffects` gains a `todo/write` case (whole list, last write wins). Unlike partial/openCalls, `rebuildDerivedFromWindow` deliberately does NOT reset it: the value is session-level — taken from the tail history page's full-log projection — and an arbitrary window may not contain the latest write, so an older-page prepend keeps it and only an in-window or live write overwrites it. Every `installWindow` caller is a tail request (`doOpen`, its gap re-pull, `repairGap`; `loadOlder` prepends without it), which the host answers with the projection or omits it only when the full log holds no `todo/write` — so an absent field is the authoritative empty list and is assigned as such. That distinction matters on rollback: a live write whose host crashed before persisting leaves the log empty, and preserving the prior value instead would strand the rolled-back plan on screen indefinitely. `ConversationSnapshot.todos` is the read surface. This follows the event's own contract ("log-only UI state; never derived history"): surfacing each write as a conversation node would render superseded lists as if they were still standing.
### TodoPanel: the durable list as a persistent strip
The panel mounts through the `conversation.input.dock` slot (a plain registrant plugin, `todoDockEntry`, the QueueDock posture: `inject: ['slots', 'conversation']` as the load-order seam, `order: -1` above the queue rows), hidden while empty, collapsible with the in-progress item as the collapsed one-line hint; ✓/●/○ glyphs mirror the TUI plan panel. It reads `snapshot.todos` via the standard-kit `useSession` hook the dock entry receives — no store, no service, no ctx. The inner component stays props-complete and framework-free; the dock adapter is a one-line wrapper.
### TodoRow: the per-call row through the keyed toolview slot
The dedicated `todo_write` chat row is a plain registrant plugin (`todoToolview`, mounted from `apply`) that registers into the keyed `conversation.chat.toolview` slot via `ctx.slots.register` — the same seam and load-order posture as the bash sample (`inject: ['slots', 'conversation']`), but a product registration. The summary derives from call args (`N/M done · active item`); unparseable args fall back to the generic row summary; clicking opens the details column with the raw args. No `ToolEventView` is added for todo — presentation is client-owned, and the durable list renders from the session event, not the tool card.
## Alternatives considered
- **Fold todo writes into `nodes` as surface entries** — replayed windows would render every superseded list; the event is deliberately not a surface type.
- **Hardcoding the panel inside `ConversationRoot`** — the original landing spot before the input-dock slot existed; the dock is the architecture's home for always-on strips above the composer, and a hardcode bypasses the slot registry's disposal and ordering.
- **Details column for the panel** — the details slot is single-occupant and selection-driven, a different lifetime than an always-on strip.
- **Host-computed view (a todo `ToolEventView`)** — presentation belongs to the client; the wire already carries the whole snapshot in the event payload.
## Consequences
Replay correctness is owned by one code path: any future change to window rebuild keeps todos consistent for free, and the fixture (fx-alpha turn 65) plus the assembled keyless snapshot (`apps/web/tests/todo-display.snapshot.ts`) pin the full chain (row summary and state, dock panel content, collapse round-trip) over the built client graph. `todos` is a required `ConversationSnapshot` field, so scripted fakes in specs must carry it. The TUI panel is untouched (the automation-only ACP bridge deliberately omits todo presentation); the web surfaces render the same event, adding one wire field and no new event type. That field is how cold-load reconstruction stays host-backed: the tail history page carries `todos` — the full-log latest `todo/write`, computed independently of the page window (the same backscan posture the view pairing uses) — so a reopened session restores the plan even when the last write precedes the window; that value survives an older-page prepend, is overwritten by any later write, and resets to empty when a tail response carries no projection.

View File

@@ -0,0 +1,36 @@
# Agent Note: Web todo 展示——快照副作用通道 + 两个渲染面
Status: implemented
[English](2026-07-23-web-todo-display.md) | 中文
## Problem
`todo_write``todo/write` 的整份列表快照追加进会话日志TUI 渲染一块常驻的 plan 面板(自动化专用的 ACP 桥接刻意不做 todo 呈现。Web 客户端把这个事件整个丢弃了host mux 流本已转发每一个会话事件,但 `todo/write` 不是 surface 类型(它从不 fold 进 `ConversationSnapshot.nodes`),也没有任何副作用分支累积它——浏览器既无消费点,也无展示面。
## Decision
`todo/write` 当作 Session 副作用消费,而非 surface 节点,并在两个面上渲染它,这两个面正对应 TUI 已经绘制的那套划分。
### 副作用通道,与窗口回放收敛
`applyEventSideEffects` 新增一个 `todo/write` 分支(整份列表,后写覆盖先写)。与 partial/openCalls 不同,`rebuildDerivedFromWindow` 刻意不重置它:该值是会话级的——取自尾页 history 携带的全量 log 投影——而任意窗口未必包含最近一次写入,因此往前翻页保留它,只有窗口内或实时的写入才会覆盖。`installWindow` 的每个调用方都是尾页请求(`doOpen`、其补洞重拉、`repairGap``loadOlder` 只往前拼接、不走它),而 host 对尾页请求要么带上投影、要么仅在全量 log 没有任何 `todo/write` 时省略——因此字段缺失就是权威的空列表,直接照此赋值。这个区分在回滚场景上要紧:实时写入若在 host 持久化前崩溃log 里就是空的,此时保留旧值会让已回滚的计划永远留在屏幕上。`ConversationSnapshot.todos` 是读取面。这遵循事件自身的契约(「仅日志 UI 状态,绝非派生历史」):把每次写入作为对话节点呈现,会让已被取代的列表看起来仍然有效。
### TodoPanel长驻列表作为一条常驻横条
面板经 `conversation.input.dock` slot 挂载(普通注册者插件 `todoDockEntry`QueueDock 同款姿势:`inject: ['slots', 'conversation']` 载序 seam`order: -1` 排在队列条上方),空列表时隐藏,可折叠——折叠态以进行中项作为单行提示;✓/●/○ 字形与 TUI plan 面板一致。它经 dock entry 收到的标准件 `useSession` hook 读取 `snapshot.todos`——无 store、无 service、无 ctx。内部组件保持 props 完备且框架无关dock 适配件只是一行包装。
### TodoRow经 keyed toolview slot 的逐调用行
专用的 `todo_write` 对话行是一个普通注册者插件(`todoToolview`,由 `apply` 挂载),经 `ctx.slots.register` 注册进 keyed 的 `conversation.chat.toolview` slot——与 bash 样例同一接缝、同一载序姿态(`inject: ['slots', 'conversation']`),但属产品级注册。摘要由调用 args 推导(`N/M done · active item`);无法解析的 args 回退到通用行摘要;点击会以原始 args 打开 details 列。todo 不新增任何 `ToolEventView`——呈现归客户端所有,常驻列表从会话事件渲染,而非工具卡。
## Alternatives considered
- **把 todo 写入作为 surface 条目折叠进 `nodes`**——回放的窗口会渲染每一份已被取代的列表;该事件被刻意设计成非 surface 类型。
- **面板硬编码进 `ConversationRoot`**——input-dock slot 出现之前的原始落点dock 是本架构给"composer 上方常开横条"安排的家,硬编码绕开了 slot 注册表的 disposal 与定序。
- **面板放进 details 列**——details slot 单占用且由选中驱动,生命周期不同于一条常开横条。
- **host 计算的视图(一个 todo `ToolEventView`**——呈现属于客户端;协议已在事件载荷里携带整份快照。
## Consequences
回放正确性由一条代码路径掌管:未来对窗口重建的任何改动都免费保持 todos 一致fx-alpha 第 65 轮的 fixture测试前置数据加 assembled keyless snapshot`apps/web/tests/todo-display.snapshot.ts`在构建产物客户端全图上钉住整条链行摘要与状态、dock 面板内容、折叠往返)。`todos``ConversationSnapshot` 的必填字段,所以 spec 里脚本化的 fake 必须带上它。TUI 面板未受改动(自动化专用的 ACP 桥接刻意不做 todo 呈现Web 各面渲染同一个事件,只新增一个协议字段,不新增事件类型。冷加载重建正是靠这个字段由 host 兜底history 尾页附带 `todos`——全量 log 上最新一次 `todo/write` 的投影,独立于分页窗口计算(与 view 配对同一种 backscan 姿势)——因此重开会话时即使最后一次写入落在窗口之前,计划也照常恢复;该值跨往前翻页保留,之后的任何写入照常覆盖,而尾页响应不带投影时复位为空。