41 KiB
Agent Note: Client 从原始 Session 工具事件派生展示
Status: implemented
English | 中文
Problem
Session 历史是持久 journal 接口,工具卡片属于 Client 展示。在 page/follow 中计算卡片 view 会让历史读取依赖 Tools registry、Agent preset、恢复后的 scope、presenter 执行和临时 UI 类型。
tool/result 不重复记录工具名称和参数。Host 端结果展示因此需要 call index 或按 callId 回扫;maxMessages 不直接限制事件数量,工具密集页面上的重复扫描可能接近二次方成本。
Host 投影还会重复结构化数据。read、diff、search 与 web 结果已在 tool/result.data.meta 中持久化有界事实;另一份 view 只增加 Remote payload 与 Client 解码成本,不增加持久语义。
Client 已经拥有完整的工具展示入口。ui-chat 将 tool/call、tool/result 与 Code Dispatch 事件组装成稳定的 ToolCallBlock;ui-tool 拥有递归调用树、按工具名称分发的 tool.call.toolview keyed slot、Generic fallback、卡片模型和 details output;业务 Client 插件可以为自己的工具名称注册 renderer。
Host presenter 与 Client keyed renderer 分担展示会形成对同一事件的两套解释。keyed renderer 是 Web 扩展点,因此中间 Host view 不提供独立 Web 能力。
ToolDefinition.presentCall/presentResult 仍是保留的 Host API;ACP 采用 automation-only 协议,仓库也没有生产 TUI consumer。是否删除这些定义与 Session 读取是否独立于展示是两个决定。
所需结果是一条原始 Session journal 和一个 Client 展示 owner,且不发生可见退化或顺带增强。专用卡片、交互和 Code Dispatch 拓扑保持稳定,transport 不再携带临时 view。
Decision
Session Remote journal 只下发原始、已验证、可持久化的 Session event。session.page 和 session.follow 不解析工具参数,不查询 Tools registry,不恢复 presenter scope,不执行 presentCall/presentResult,也不构造或克隆任何 tool view。
Client Conversation 层继续负责工具调用与结果的 identity、配对、生命周期、Code Dispatch 拓扑和稳定 Chat Node。它不解释具体工具名称,也不生成 terminal、diff、read、search 或 web 组件 props。
Client ui-tool 继续负责 card model 和具体 renderer。每个 card model 改为直接读取 ToolCallBlock 中的工具名称、原始参数、结果内容、错误、持久 metadata、Session cwd 与 Host home,并生成与现有页面相同的组件 props。
Client 不建立第二套 presenter registry。工具名称分发只使用现有 tool.call.toolview keyed slot;Client 中的纯 card-model helper 属于 renderer 实现,不成为 Cordis service、公开 registry 或 wire DTO。
Host 的 ToolDefinition.presentCall、ToolDefinition.presentResult、ToolCallView、ToolResultView 及现有 presenter 实现全部保留。Session Controller 不调用它们,Client 不导入或消费它们;未来非 Client consumer 是否使用它们不属于本决定。
ToolOutputDefinition.presentationMeta 与持久 tool/result.data.meta 保留。它们携带模型可见结果文本无法无损表达、而现有专用卡片需要的执行结果事实。Client 直接校验并消费 meta,不要求 Host 在历史读取时再把它转换成 view。
目标与非目标
| 类别 | 决定 |
|---|---|
| 不存在 | SessionEventEntry.view、SessionToolView、SessionToolCallView |
| 不存在 | history.ts 的 viewFor、backscanArgs、parseToolCall、jsonView 与 presenter scope lookup |
| 不存在 | follow 中只服务 presentation 的 openCalls 与 fallback event scan |
| 不存在 | Client Session 的平行 views 数组、Conversation input 的 view、Tool block 的 callView/resultView |
| 派生 | terminal、diff、read、search、web card model 读取 raw block/meta |
| 派生 | Deliverables 读取成功 mutation 的名称与参数 |
| 保留 | Host ToolDefinition.presentCall/presentResult API、类型、实现与直接测试 |
| 保留 | output.presentationMeta 与持久 tool/result.data.meta |
| 保留 | Session 日志格式、Remote journal 生命周期与 Conversation identity/topology |
| 保留 | 现有 keyed slot、Generic fallback、Chat、Details 与 Trajectory 结构 |
| 禁止 | 新 Client presenter service、平行 registry 或 wire renderer id |
| 禁止 | 新卡片、视觉改版、交互改版或 Code Dispatch rich-card 增强 |
| 禁止 | 为兼容保留双写、版本协商或旧 view 字段 |
术语
原始 Session event指持久日志中的 SessionEvent 事实,包括 tool/call 的 name 与原始 arguments 字符串,以及 tool/result 的 content、isError、结构化错误和可选 meta。
持久 metadata指 ToolOutputDefinition.presentationMeta 在工具成功执行时生成并写入 tool/result.data.meta 的 JSON 值。它是结果事实的一部分,不是预先排版的 React 或 card DTO。
Host tool view指 ToolDefinition.presentCall/presentResult 返回的 ToolCallView/ToolResultView;Session Remote 不运输它。
Client card model指 ui-tool/src/client/tool/models/ 下直接供 TerminalBlock、DiffBlock、ReadBlock、SearchBlock、WebBlock 或 ToolRow 使用的纯 props 数据。
专用卡片指 terminal、diff、read、search 与 web 的结构化正文;标题、摘要、状态点和普通 IN/OUT 文本仍属于通用工具行。
对等指同一受支持输入产生由现有组件、组装与浏览器证据固定的用户可见结果和交互,不要求相同的中间 TypeScript 类型或内部函数调用。
无增强指本决定不让被固定为 Generic fallback 的输入获得新专用卡片,也不扩大已有卡片的数据或交互。
架构与所有权
工具执行与持久化
- 工具注册
output.schema、output.render和可选output.presentationMeta。 - 成功执行产生 canonical JSON value。
- Tools runtime 对 value 做快照、schema 校验和冻结。
output.render(args, value)生成模型可见ContentBlock[]。- 顶层调用若声明
output.presentationMeta,runtime 同时生成 JSON-safe metadata。 - Agent loop 把模型可见结果与 metadata 写入
tool/resultSession event。 - Session log 不保存
ToolCallView或ToolResultView。
Host journal 读取
session.page取得 attached 或 persisted 事件。paginate()按 append-origin user/assistant message 边界切页。- tail page 通过已注册 projection 的 snapshot/restore 路径取得 baseline。
- 每个 page entry 只包含
{event}。 session.follow先建立 listener,再执行 catch-up read、发送 opening cursor 并流式下发连续{event}frame。- 两条路径都不为展示解析 preset/Tools scope、解析工具参数、调用 presenter 或建立 call index。
Client 数据与展示
- Client Session 保存一个连续 raw event window。
SessionEventSource发布只含 event 的SessionEventEntry。ui-conversation在没有 presentation companion 的情况下 fold 每个事件。- Chat 与 Trajectory Tool Definition 按 callId 配对顶层 call/result,并组装 Code Dispatch 子树。
RunningToolCall与ToolResultNode保存 raw facts、metadata 与既有 parent identity。ToolCallTree按 wire tool name 分发tool.call.toolview。ui-tool在 render site 从 block 派生 card component props。
生产消费者审计
| 对象 | 生产者 | 生产消费者 | 决定 |
|---|---|---|---|
presentCall/presentResult |
各 Host 工具 | 可能存在的非 Client caller | 保留在 Session Remote 之外 |
SessionEventEntry.view |
无 | 无 | wire 不存在 |
callView/resultView |
无 | 无 | Client model 不存在 |
presentationMeta |
Tools runtime | tool/result、Client card model 与 Host presenter |
保留的持久输入 |
| fixture presenter mirror | 无 | 无 | fixture 下发 raw metadata |
ACP 不消费 Session tool view,也不映射 Host render intent。仓库没有生产 TUI consumer;Host presenter 保留,但 Session Remote 不作为其 transport。
数据流
Tool execute
-> canonical value
-> output.render(args, value)
-> model-visible result content
-> output.presentationMeta(args, value), when declared
-> durable tool/result event
Session page/follow
-> raw Session event envelope
-> no tool lookup
-> no preset lookup for presentation
-> no call backscan
-> no render-intent serialization
Client SessionEventSource
-> Conversation Tool Definition
-> root call/result pairing + Code Dispatch topology
-> ToolCallBlock(name, argsRaw, content, error, meta)
-> tool.call.toolview keyed dispatch
-> Client card model
-> existing React component
这条链路保留一次持久 metadata 投影,因为它发生在 canonical result 尚在内存时;删除的是读取历史时的第二次展示投影。
分层责任
| 层 | 负责 | 不负责 |
|---|---|---|
| Tools runtime | 执行、canonical value、模型文本、可重放 metadata | Web 卡片选择和组件 props |
| Session log | 持久事实、顺序、回放 | 临时 card DTO |
| Session Controller | 地址、权限、冷读、分页、follow、projection baseline | tool lookup、presenter、展示 scope |
| Client Session | Remote journal 生命周期与连续窗口 | 工具含义、卡片类型 |
| Conversation Tool Definition | call/result 配对、lifecycle、root/subcall topology | 工具名到组件的解释 |
ui-tool |
card model、通用 fallback、Chat/Details 展示 | Session 分页与 Host registry |
| 业务 Client 插件 | 自有 tool name 的 keyed renderer | root/subcall 编排与全局 registry |
ui-deliverables |
当前第一方 mutation 的 produced path | UI card 或 Host render intent |
Remote 与持久数据约定
SessionEventEntry
SessionEventEntry 保留为 journal entry envelope,只含 event: SessionWireEvent。本次不顺带把 page entries 改成裸事件,也不重构 RemoteJournalStream 的通用 entry 约定。
SessionPage.events 仍是 SessionEventEntry[]。
SessionFollowFrame 仍是 opening frame 或带 event 的 event frame。
删除 SessionToolCallView、SessionToolView 和 SessionEventEntry.view。
Client connection 不再从 dsh-tools/presentation 转出 ToolCallView/ToolResultView 供 Session 消费。
生成 catalog 与 graph 从各自 source owner 派生已收窄的 Remote 类型和 package dependency。
持久日志
tool/call.data.name保持原样。tool/call.data.arguments保持模型产生的原始 JSON 字符串。tool/result.data.message.content保持模型可见结果。tool/result.data.error保持结构化失败身份。tool/result.data.meta保持工具私有 JSON 值。- Client card model 不写入 Session log。
- renderer key 与 Host tool implementation id 不写入 Session log。
- 现有持久 Session 无需迁移,
SESSION_FORMAT_VERSION不变。
presentationMeta
presentationMeta 不是 Host tool view。它在工具执行完成时读取 canonical value,而该 value 不会持久化;删除它会使下列现有展示无法无损恢复:
- read 的 path、offset、lines、totalLines 与 lang;
- write/edit 的 applied contextual hunks;
- grep/glob 的分组结果、截断标志与总数;
- web_search 的来源字段与 provider answer;
- web_fetch 的最终 URL、HTTP status 与有效截断标志。
Client 对 meta 做局部运行时收窄。是否把 presentationMeta 改名为更中性的 result metadata 不属于本决定。
Host 端设计
SessionHistoryController.page() 在取得 source events 后只执行分页与现有 projection baseline 计算。attached Session 使用 projection registry snapshot;detached Session 使用该 registry 对 inspected log 的 restore 路径。history 不通过挂载 preset 改变已注册的 projection 集合。
SessionHistoryController.follow() 保留 listener-first、opening cursor、gap-free replay、live buffering、取消和 teardown;它不为工具事件维护额外状态。
Controller 不存在 presenterScopeFor()、viewFor()、backscanArgs()、parseToolCall() 或 jsonView() 路径。page state 不含 presenter scope 或参数 resolver;follow state 不含 openCalls、fallbackEvents 或 presentation 参数 resolver。每个 page/follow event 只包装成 {event},地址、ownership、cursor、seq 与 projection 逻辑保持完整。
不可变 event 转换 helper 可以保持窄实现或内联;只要 history 不执行 presentation 工作,其名称没有语义。
Session Controller dependency 只在其他 package responsibility 需要时保留;manifest 与 project reference 不含 presentation-only dependency。
性能约束
page()的工具相关工作为零。- 页面增加 tool result 不增加对既有页面事件的重复扫描。
follow()不维护展示索引。- history 不触发 Cordis
toolsservice proxy。 - history 不等待 presenter standing scope。
- history 不执行工具参数 JSON parse。
- history 不执行 tool view JSON clone。
- Remote payload 不重复携带
meta已表达的结构化数据。 - Client 不扫描完整 Session event window 生成单个卡片。
- Client 只在对应 immutable Tool block 变化时重新派生 card model。
Client Session 与 Conversation
Client Session 不含与 raw event window 平行的私有 views 数组。installWindow()、prependWindow() 和 appendLive() 只处理 event entries、cursor/hasMore、queue、projection 与通知。
ConversationEventInput 只携带 event。Conversation assembler 不认识 SessionToolView,其 replace/prepend/append、Context identity、Location 与 publication cadence 不变。
Chat 和 Trajectory 的 Tool Definition 都不读取 view,而从事件生成以下数据:
- callId;
- tool name;
- raw arguments;
- turn、step、seq 与 time;
- result content;
- isError 与 structured error;
- result metadata;
- root/subcall parent-child topology;
- interruption synthetic result。
RunningToolCall 不含 callView。
ToolResultNode 不含 callView 与 resultView。
ToolCallBlock 不新增通用 view、card、kind 或 locations 字段替代被删除字段。具体展示仍只属于 ui-tool 与 keyed renderer。
Root 与 Code Dispatch 子调用
Host presenter API 描述顶层 call/result。Code Dispatch 子调用使用 Generic/flattened Client 展示;Client 能识别子调用名称并不赋予它结构化卡片。
Code Dispatch start 与 result event 已经携带 parentCallId。Conversation 在每个 child ToolCallBlock 上保留这项现有事实,root Session call 则不携带它。五类结构化 card model 只接受没有 parentCallId 的 block,原本有意支持嵌套调用的 renderer 则继续收到同一个 child block。
Details panel 原样委托选中的 block。同一组 card model 读取 parentCallId,让选中的 Code Dispatch child 保持现有 raw fallback,因此 Details slot 不需要 placement 字段。
keyed slot 仍按每个子调用的真实 tool name 分发;parentCallId 只控制本决定覆盖的 terminal/diff/read/search/web 结构化模型。Skill、Cordis 等已经直接读取 raw block 的专用 renderer 保持现状。
缺失调用头
结果节点在当前窗口没有配对 call 时,ToolResultNode.call 保持 null。Client 不扫描窗口、不发额外 RPC,也不根据 result 文本猜测工具名称。
需要名称或参数的专用派生在 call === null 时走当前 Generic fallback。只依赖 result metadata 的模型也不借机增强,因为当前 Host presentResult 必须先取得配对调用。
older page 后续补入调用头时,Conversation Context 按既有 replay 规则重建,届时才允许生成当前已有的专用卡片。
参数与 metadata 收窄
Client 从 argsRaw 解析 JSON,解析失败返回 Generic,不抛出 React render 错误。
Chat 与 Details 通过纯 helper 复用同一 block 的解析。未来缓存必须使用 immutable block identity,不能按 callId 建立跨 Session 全局状态。
每个专用模型只检查它需要的字段。Client 不复制完整 Host tool schema,也不调用 Host defineTool validator。
合法第一方事件必须与当前 presenter 输出等价。畸形、旧版本或手工修改日志只承诺不崩溃并使用 Generic fallback。
Client card-model 设计
现有 ui-tool/src/client/tool/models/ 继续是 Chat 与 Details 共享派生的唯一位置。helper 直接返回组件 props,不返回 ToolCallView/ToolResultView,也不创建同构的 ClientToolView union。
工具名称分支只存在于 ui-tool card model、现有 row 分类表,或拥有该工具 keyed renderer 的 Client 插件;不得进入 Session Controller、Client Session、Conversation assembler 或通用 Slot renderer。
未知工具继续由 GenericToolCard 显示 name、原始 args、结果 content 与错误。
通用工具行
toolRowModel() 直接从 toolName、argsRaw、result content、error、cwd 与 home 派生通用行,并保持以下行为:
search、read、bash、write、edit、code与others分类;- 现有标题与工具专用标题;
- summary 字段优先级和单行截断;
- 多 query 的逗号拼接;
- cwd 相对化与 home 缩写;
- file path 点击;
- args pretty JSON 与非 JSON 原文 fallback;
- result content flatten 与 structured error fallback;
- running、ok、error 与 stopped 状态。
Generic Host presentCall 的 title、kind、rawInput、content 与 locations 当前并不驱动普通 Web 行;Generic presentResult.content 也不驱动 Web 输出,因此无需把这些未消费值复制到 Client。
Terminal 卡片
Client terminal model 从工具名称、调用参数、结果 content、error、现有 parentCallId 与 Session cwd 派生现有 TerminalBlock props。
| 输入 | 保持的结果 |
|---|---|
标准 bash/pwsh 前台 running |
terminal prompt、description、cwd、running 状态 |
| 标准前台 success | terminal output、exit code/signal、成功或失败状态点 |
run_in_background:true |
Generic 行与原始结果 |
| 工具执行 error | Generic IN/OUT 与错误摘要 |
persistent bash/pwsh running |
terminal prompt |
persistent bash/pwsh settled |
Generic flattened result,不新增 exit card |
terminal_send 前台 |
terminal prompt 与 output |
terminal_send background/error |
Generic 结果 |
| Code Dispatch child | 当前 flattened Generic 形态 |
标准 shell 结果继续解析末尾 [exit code: N] 与 [killed by signal: X]。已解析的 marker 从正文移除;timeout、sandbox denial 与没有 pill 的 marker 留在正文。
调用 description 继续显示在 card 上方并覆盖折叠摘要。workdir 继续按绝对、相对和缺失三种情况处理;相对路径基于 Session cwd,且保留 .、..、盘符与 UNC root 的归一化。
对于 terminal_send,非空 input 与 session id 保持为逐字工具数据;空 input fallback 与 session label 通过 render site 的 conversation locale 解析。
同名普通与 persistent provider 是特殊兼容点。Client 使用当前有效参数与结果特征保留已交付差异;不足以无歧义识别的输入选择 Generic settled 结果,不增加新表现。
TerminalBlock 的 ANSI、光标重放、宽字符、行数上限、展开、复制与辅助技术文本完全不变。
Diff 卡片
| 输入 | 保持的结果 |
|---|---|
running write |
从 file_path 与 content 生成 intended added-only diff |
running edit |
从 file_path、old_string、new_string 生成 intended replacement diff |
running str_replace_editor create |
从 path 与 file_text 生成 intended added-only diff |
running str_replace_editor str_replace |
从 path、old_str 与 new_str 生成 intended replacement diff |
settled write/edit success |
从 meta.diffs 生成 applied contextual hunks |
settled str_replace_editor |
Generic,因为该工具没有 result presenter |
| write create 或 applied metadata 缺失、畸形、为空 | 当前 args fallback |
| error、畸形 args、edit 的 metadata 畸形、Code Dispatch child | Generic |
路径、oldText:null、newText、结果覆盖调用时 diff、Chat 8 行上限、Details 全高显示和文件打开行为不变。
Read 卡片
running read 继续只有摘要行。成功 settled read 从 result meta 读取 path、offset、lines、totalLines 与 lang,并确认结果是单个文本块且符合 read envelope。
meta 缺失、字段畸形、result envelope 不匹配、error、缺失 call head 或 Code Dispatch child 都走 Generic。路径 label 的 cwd 相对化、home 缩写、语法语言、总行数、Chat 8 行上限与 Details 全高显示不变。
Client 不需要构造 Host ReadResultView.content;Generic fallback 始终可直接读取原始 result content。
Search 卡片
running grep/glob 继续只有参数摘要。成功结果分别从 meta.shape:'matches' 与 meta.shape:'paths' 生成 grouped matches 或 path list。
Client 校验 path、lineNumber、line、truncated 与 total。空 matches/paths 是有效卡片;缺失/畸形 meta、未知 shape、error、缺失 call head 与 Code Dispatch child 走 Generic。
truncated:true 时继续从原始 result content 显示 recovery locator;未截断时不显示。Chat 8 行上限、Details 全高显示和展开行为不变。
Web 卡片
running web_search/web_fetch 继续只有摘要行。成功 search 从 meta.sources、meta.answer、meta.truncated 生成卡片;成功 fetch 从 meta.url、meta.statusCode、meta.truncated 生成卡片。
Client 校验每个 source 的 url、title、snippet 与 publishedAt,并继续只把 http/https URL 渲染为链接。meta 缺失或畸形、error、缺失 call head 与 Code Dispatch child 走 Generic。
search 的 answer、来源顺序、label fallback 与截断提示不变;fetch 的最终 URL、状态、截断提示与 Details 下方原始正文不变。
已直接使用 raw block 的 renderer
- Todo row 继续从 args 计算 completed/active 摘要。
- Question row 继续从 result content 与 error 计算等待、回答、取消和中止状态。
- Skill row 继续从 args/result 计算名称与状态。
- Cordis define/run/action rows 继续从 args/result 与各自 Client service 计算。
- 这些 renderer 的 props、slot key、注册顺序与可见结果不变。
Deliverables
ui-deliverables 独立于展示意图派生 mutation 业务事实,因此 produced-file 行为不与卡片截图耦合。
Deliverables Definition 按 callId 观察 root tool/call 与成功 tool/result,保存最小的 Client-owned mutation candidate,不扫描 Session window,也不依赖 UI renderer。
| 工具 | mutation 判定 | path 来源 |
|---|---|---|
write |
任意成功调用 | file_path |
edit |
任意成功调用 | file_path |
str_replace_editor |
create、str_replace、insert |
path |
str_replace_editor |
view |
不产生 path |
| 其他 | 无当前第一方 mutation 语义 | 不产生 path |
失败、interrupted、orphan result、缺失 path 与畸形 args 不产生 deliverable。同一路径保持 first-seen 去重,closing Assistant seq 之后落定的结果继续排除。
本次不新增通用“工具副作用”注册表。Host-only 第三方 presenter 通过 kind:'edit'/locations 自动加入 Deliverables 的能力被有意移除;未来若有真实第三方 mutation 需求,应由 Client 业务贡献表达,不能恢复 Session view。
Fixture 与测试数据
Client fixture 删除手写 presentCall()、presentResult()、viewFor() 与 fixture tool-view 类型。它继续产生与真实日志相同的 raw call、result content 和 result meta。
| Fixture | 必须保留的原始事实 |
|---|---|
| terminal | 参数与真实结果 status marker |
| diff | 参数与 result meta.diffs |
| read | result meta 的 path/offset/lines/totalLines/lang |
| grep/glob | result meta 的 shape/files 或 paths/truncated/total |
| web | result meta 的 sources/answer 或 url/statusCode/truncated |
| generic/custom | name、argsRaw、content、error |
fixture 不导入 Host 工具包来计算页面展示,也不保留 presenter 镜像。同一 raw fixture 继续驱动 jsdom、built Web snapshot 与 ?fixture 浏览器路径。
展示等价矩阵
“当前展示”由已提交的组件测试、组装测试与 Web browser expected 共同定义。transport 或 ownership 重构不能作为 refresh snapshot 的理由;获批产品变化需要独立证据。
| 场景 | 必须保持的展示 |
|---|---|
| 未知工具 running | Generic 行,工具名与 args 摘要 |
| 未知工具 settled | Generic 行与原始 output |
| malformed args | 安全 Generic fallback |
| orphan result | callId 标题与 Generic output |
| interrupted call | warning/stopped 状态 |
| bash/pwsh 前台 | 当前 terminal prompt、正文、cwd 与状态 |
| bash/pwsh background/error | 当前 Generic IN/OUT |
| persistent shell | 当前 running terminal、settled Generic |
| terminal_send | 当前前台 terminal、后台/error Generic |
| write/edit | 当前 intended/applied diff 与 error fallback |
| read | 当前 running 摘要、settled ReadBlock 与 error fallback |
| grep/glob | 当前 grouped/path card、截断与 recovery |
| web_search/web_fetch | 当前来源/摘要 card 与原始正文 |
| Todo/Question/Skill/Cordis | 当前专用行 |
| Code Dispatch subcall | 当前 Generic/flattened 形态 |
| Chat 与 Details | 同一调用使用相同 card fields |
| Trajectory | 当前 identity、树、选择和 details |
| Deliverables | 当前成功 mutation chips 与链接 |
Client 扩展约定
tool.call.toolview 继续是唯一工具 UI 注册机制。一个工具若要在 Client 获得专用表现,必须由 Client 插件注册自己的 wire tool name。
注册方接收 raw ToolCallBlock、Session path 信息和宿主动作,自行校验它认识的 args/meta 字段。注册方不调用 Host tool registry,不依赖 presentCall/presentResult,也不能要求 SessionEventEntry.view。
没有 Client renderer 的工具稳定降级为 Generic。同一 tool name 只能有一个生效 keyed registration,重复 key 继续 loud failure。
Session-scoped slot 可以表达 Client 侧会话差异,但不从 preset 推断 renderer 变体。Host-only presenter 不自动赋予 Web rich card,这是“Host 描述展示”与“Client 插件拥有展示”的明确边界。
失败与 fallback
- Client 把 args 与 meta 当作 wire JSON,在消费点收窄。
- 参数 JSON 解析失败走 Generic。
- 已知工具缺少必要字段走 Generic。
- metadata 缺失或畸形走 Generic;成功
write例外,它按当前 presenter 行为保留由参数派生的整文件 diff。 - error result 不因 metadata 存在而显示成功卡片。
- 缺失 call head 不猜测工具名称或参数。
- 未知 metadata 字段被忽略。
- 新 metadata variant 在旧 Client 中走 Generic。
- card-model helper 捕获可预期解析失败,不依赖 React error boundary 完成普通 fallback。
- keyed renderer 自身的意外异常仍由现有 Slot error isolation 处理。
同名 Host provider
Host registry 允许不同 scope 为同一 tool name 提供不同定义;Session view 通过 presenter scope 理论上可以按 preset 选择不同 render intent。删除 view 后,Client keyed slot 只观察 wire name,不能观察 Host definition identity。
当前第一方显著实例是普通与 persistent bash/pwsh。Client 派生使用有效参数与结果特征保持它们的已交付差异,不增加 provider-id wire 字段;无法判别的畸形或自定义同名 provider 输入采用 Generic。
本次不承诺保留第三方同名 provider 仅通过 Host presenter 表达的差异。若未来产品确需同名 provider 的不同 Client 展示,必须定义稳定、非展示性的 Client identity;不得恢复按页 Host view 计算。
已交付范围
Session Controller
SessionEventEntry只包含 raw event。- 两个 Session tool-view 类型都不存在。
- history 不含 presentation import、helper 或 page/follow presentation state。
- 地址、分页、follow 与 projection 逻辑仍由 Session owner 负责。
- Host 测试固定 raw journal 约定。
Session Controller Client
Session.views不存在。- EventSource replace/prepend/append delta 保持不变。
- transport、fixture 与 test-support 类型携带 raw entry。
- event identity 与引用稳定性保持不变。
UI Conversation、Chat 与 Trajectory
- Conversation input 与 Tool block 不含 view 字段。
- Chat/Trajectory Tool Definition 读取 raw event。
- event pairing、Context replay、树与 target snapshot 保持不变。
- child Tool block 保留现有 Code Dispatch
parentCallId;row 与 Details slot owner props 都不增加独立 placement 字段。
UI Tool 与 Deliverables
- card model 从 raw block/meta 派生。
- Chat 与 Details 复用相同 helper。
- Generic fallback 与 keyed dispatch 保持不变。
- Deliverables 识别第一方 mutation args。
Fixture、文档与生成物
- fixture 只发 raw event/meta。
- Session Controller 与 Client README/JSDoc 描述 raw journal 和 Client presentation owner。
- 工具 cookbook 记录 Web Client 接入路径。
- 本文是该决定的 owner;保留的 Host presenter Note 继续拥有各自决定。
- 手写 Remote 类型、dependency、README、pairing record 与 generated reference 保持同步。
验证矩阵
Host
- page 返回连续 raw event entries。
- follow 返回 opening cursor 与连续 raw event entries。
- page/follow 在无 Tools service 时行为相同。
- cold page 不解析或挂载 preset。
- tail page 通过标准 projection registry 计算 baseline;provider 是否存在由 projection composition 决定,不引入 history 侧 setup 路径。
- 地址、ownership、message-aligned boundary 与 tail projection 不变。
- listener-before-read、reconnect catch-up 与 gap repair 不变。
- 大量 tool results 不触发每结果回扫。
- wire 结果不含 view。
session-history-journal.host.spec.ts 负责分页、连续性和 history error 行为,不含 presenter 断言。
Client Conversation
- replace、prepend 与 append 接受无 view entry。
- Chat 与 Trajectory root call/result 配对不变。
- Code Dispatch 树不变。
- result-only fallback 不变。
- interruption synthetic result 不复制 view。
- registry rebuild、older prepend 与 live append 的 Node identity 不变。
Client card model
- terminal 用 raw args/content 得到已固定的 props。
- diff 用 args/meta 得到已固定的 diffs。
- read 用 meta/content 得到已固定的 lines。
- search 用 meta/content 得到已固定的 grouped/path card 与 recovery。
- web 用 meta/content 得到已固定的 sources/fetch summary。
- unknown、malformed、error、missing-call 与 missing-meta 继续 Generic。
parentCallId缺失与存在的用例证明结构化展示不会到达 Code Dispatch descendant。- Chat 与 Details 对同一 block 得到相同 card fields。
Deliverables
- write/edit 成功产生
file_path。 - str_replace_editor create/str_replace/insert 产生
path。 - str_replace_editor view 不产生 path。
- failure、interrupted、malformed 与 orphan 不产生 path。
- first-seen 去重与 closing seq cut 不变。
组装与浏览器
- terminal、diff、read、search、web browser expected 不刷新并全部通过。
- tool tree、details、trajectory 与 deliverables 的可见断言不改预期。
- built Client 通过真实 Remote page/follow 取得 raw events 后仍显示同样卡片。
- fixture 与真实 Host 使用同一 Client derivation。
- minimal preset 单独固定 persistent shell 行为。
静态与文档
- 生产代码不存在
SessionToolView/SessionToolCallView。 - Session history 不引用
dsh-tools/presentation、ctx.tools、presenterScopeFor或backscanArgs。 - Client Conversation 不引用
ToolCallView/ToolResultView。 - Client model 不读取
callView/resultView。 - fixture 不定义 presenter mirror。
- Host
presentCall/presentResult与presentationMeta仍存在。 - 没有新增 Client registry 或 Host→Client presentation hint。
- 受影响的手写类型、README、Agent Note、catalog 与 graph 保持同步。
验证命令
修改本决定时使用 dsh-pre-push-checks 按最终 diff 选择命令;所需证据包括:
- Session Controller history/transport 聚焦测试;
- ui-chat 与 ui-trajectory Tool Definition 测试;
- ui-tool terminal、diff、read、search、web、row、tree 与 details 测试;
- ui-deliverables produced-files 测试;
- connection fixture 与 Client runtime 测试;
- 受影响 Host/Client TypeScript face;
- lint 与 duplication;
- 受影响源文件 per-file 100% coverage;
DSH_SNAPSHOT=replay pnpm run test:web,不得 refresh 现有展示 golden;- 手写 Remote 类型与 TypeScript 检查;
pnpm run doc-sync;git diff --check。
已交付不变量
- Session page/follow 不读取 Tools registry 或 presenter scope。
- Session history 不存在 callId backscan、presentation cache 或 view clone。
- Remote Session entry 不携带 view。
- Session 日志与
SESSION_FORMAT_VERSION不变。 - result meta 逐字节通过日志与 Remote 到达 Client。
- Conversation 只从 raw event 组装 ToolCallBlock。
- ToolCallBlock 不含 Host render-intent 字段。
- 五类结构化 card model 只读 raw block、其现有
parentCallId与 Session path facts。 - Generic、Todo、Question、Skill 与 Cordis 行行为不变。
- Deliverables 不依赖 render intent 且保持当前 paths。
- 所有第一方顶层工具的文本、组件、展开内容、状态、链接与排序不变。
- malformed、missing-meta、error、orphan 与 unknown-tool 继续安全 fallback。
- Code Dispatch 子调用保持 Generic/flattened。
- Chat、Details 与 Trajectory 行为不变。
- 现有 Web browser expected 无需刷新即可通过。
- Host presenter API、实现与直接测试不变。
- ACP 输出不变。
- 没有新下行展示字段或第二套 Client registry。
- 分页成本不再随 result 数量乘以页面事件数增长。
- 下行 payload 不再重复 result meta 的 card DTO。
Alternatives considered
只优化 backscanArgs,保留 view
page 前建立一次 callId → {name,args} Map 可以把回扫降为线性,live 已有 openCalls 快路径;但 Host lookup、preset scope、presenter、JSON clone、重复 payload 和双重所有权仍存在,因此拒绝。
在 Client 建 presenter registry
把 presentCall/presentResult 接口复制到浏览器会与 tool.call.toolview slot 重复注册、生命周期、fallback 和覆盖语义;renderer 仍需把 presenter DTO 转成组件 props,因此拒绝。
让 Conversation Tool Definition 生成统一 view
这会把工具名称和 UI card 语义放进 target-neutral Conversation owner,并重建与 Host view 同构的中间 DTO,因此拒绝。
删除 presentationMeta
read 行结构、applied diff、search 分组、web sources 和有效 truncation 无法从模型文本无损恢复;解析自由文本也会把 UI 绑到输出措辞,因此拒绝。
持久化 canonical tool result
这会扩大 Session log、暴露内部结果结构、改变持久格式,并可能保存远超展示所需的大对象;已有 metadata 足够,因此拒绝。
删除 Host presenter API
一并删除可以继续收缩代码,但本决定保留 Host presentCall/presentResult;其 API、实现、测试与类型独立于 Session Remote。
Client 导入 Host 工具实现
工具包包含 Node、filesystem、subprocess 或 provider 依赖,不能进入浏览器 bundle;Client 只消费 raw JSON,并在自己的 renderer 内维护窄解析,因此拒绝。
按结果向 Host 查询 presentation
按需 RPC 会把一页读取变成 N 次网络调用,仍需 Host lookup、scope、callId 查找与错误协调,因此拒绝。
允许展示增强
Client 可以为 Code Dispatch 子调用、缺失 call head 或 Host presenter 不可用的历史生成更多 rich card,但这会混淆 ownership 变化与产品行为,并使快照无法证明对等,因此拒绝。
接受临时 Generic 退化
先停发 view 再逐步补 Client card 会让 terminal、diff、read、search、web 与 Deliverables 在中间版本退化。Client 对等实现与 Host 删除必须在同一可发布变更中完成。
Consequences
本决定从 Session 读取中删除 presentation 工作、重复扫描和重复 view payload;代价是保留的 Host presenter 与 Client card derivation 可以独立演进,因此两侧都需要 owner 专属测试,Web 展示对等仍是明确产品约束。
Client 与 Host 逻辑漂移
同一工具可以有一份 Host render intent 和一份 Client card derivation。两者面向不同消费方,不共享运行路径;不刷新的 browser expected 固定第一方 Web 视觉对等,Host presenter 测试只约束 Host API。
同名 provider 无稳定 identity
raw event 只记录 tool name,不记录具体 ToolDefinition。Client 使用有效事件字段保留普通与 persistent shell 的差异;无法判别的自定义或畸形输入回退 Generic,wire 不为理论扩展性增加 hint。
Metadata 是未知 JSON
旧 Session 可能缺字段,手工修改日志可能带畸形值。每个 Client model 必须局部收窄,不能把未知数组或对象直接传给 UI primitive。
preset-owned projection 可用性
history 不为当前组合中缺失的 projection unit 补偿。需要在冷读中保持可见的 preset-owned unit,必须由共享的 Session preparation/projection 组合在 restore 前提供其定义;history 不得重新增加 preset mount 或 presenter setup 分支。
双 target 同步
Chat 与 Trajectory 各有独立 Tool Definition,两者都携带 raw fields;card derivation 只能留在 ui-tool,不能复制进两个 Definition。
Deliverables 隐性依赖
Deliverables 不是视觉组件,因此 mutation parser 必须与受支持的第一方写工具保持同步;专用测试独立于卡片截图固定 file chips 与 Markdown links。
Fixture 假绿
fixture 下发 raw event/meta,不下发手写 view。真实 Host 组装覆盖仍然必要,因为 fixture-only snapshot 不能证明 transport 路径。
错误刷新快照
本次承诺用户可见输出不变。出现 snapshot diff 时必须修 Client 派生;除非 owner 单独批准具体视觉变化,否则不得 refresh expected。
文档漂移
raw journal 或 Client presentation owner 变化时,Agent Note、package README、cookbook、根规则与 generated reference 必须一起更新;Host API 文档保持独立。
Remote 协议收缩
optional view 的缺失是所有 consumer 共同遵守的预发布 wire 类型决定;没有兼容 shim、双写或版本协商。
与现有决策的关系
本文部分取代 Client 工具展示所有权 中“card model 接收 Host view”的实现事实;ui-tool 拥有展示、业务插件使用 keyed slot、Conversation 只拥有生命周期与拓扑的核心决定保持不变。
本文保留 toolview 溶解 的决定:Client 仍只有 slot 注册模型,不恢复 ToolViewRegistry。
本文收窄 render-intent union 的消费范围:Host API 与类型保留,Session Remote 与 Web Client 不消费它。本文独自规定 transport 拆分,不改写该 presenter 决策。
本文更新 Session 历史与 Remote 事件传输 的 entry 约定:journal 只运输原始 event 与独立 projection baseline,不承载临时 tool view。
本文遵循 Conversation Node 组装:Tool Definition 负责事件配对与调用树,具体 card model 留在 ui-tool。
本文保留 规范工具输出约定 的 result metadata,因为它是无损、可重放 Client 派生的输入。
Deferred
- Host presenter 若长期没有生产消费者,可由另一项明确决策评估删除;本决定不预判。
- Code Dispatch 子调用若要专用卡片,需单独设计并更新可见快照;本决定保持现状。
- 第三方 mutation tool 若要加入 Deliverables,需新增 Client-owned 贡献;本决定不为尚无消费者的扩展性建 registry。
- 同名 provider 若要不同 Client 展示,需先定义稳定、非展示性的 identity;不得恢复按页 Host view。
- Client card model 若需量化性能,可以增加 immutable-block 微基准;已交付架构禁止扫描 Session window。