聊天轮次视觉规范

消息表面(无头像、用户气泡、编辑框、操作行)+ 执行时间线(去外框、图标竖线串联、行样式统一)+ 手动函数运行 + sub-agent 父子分支导航 + 文件修改摘要与 Review + 左缘消息导航。全部可交互。本文件是聊天轮次视觉的唯一权威;相关实现在 apps/web/components/chat/messages/ 与 app/styles/chat/。
时间线折叠与复制规范 · 已实现 范围:Chat / workflow / sub-agent 展示 圆点连续开合动画与函数列对齐 · 已实现 消息表面:凸起气泡、编辑框与操作行 · 已实现 精确文件归因、Review 与安全 Undo · 待实现

1默认(收起):一轮 = 一行淡文字 + 回复

执行过程不再有框。收起态是一行纯文字摘要 ›——无按钮底色、与正文左对齐,hover 只提亮文字。摘要按发生顺序说明这些调用做了什么("运行了 1 条命令,创建了 pill-shadow.html");有失败时跟一个红色"· N 个失败",改了文件时跟文件卡片的 +N −M 胶囊。句子截成一行,悬停提示显示全文。语法见工具组摘要。点击展开时间线。
你能开一个agent,帮我去看看今天天气吗,我在北京亚洲金融大厦
运行了 1 条命令,运行了子代理 查询北京天气›
思考 需要先核对当前日期,再决定查询口径…
用户在北京亚洲金融大厦,要今天的天气。我先用 bash 核对系统日期,确认"今天"是哪一天,然后开一个子代理去做实时查询,最后汇总成简短建议。
函数调用 bash · date '+%Y-%m-%d %H:%M %Z' → 2026-07-10 16:32 CST
子代理: 查询北京天气 已完成
思考 子代理结果可信,整理成出行建议…
数据来自中国天气网 + Open-Meteo,两源一致。整理成三行:现况、区间、建议。
北京今天小雨转暴雨,现在 25.8°C、湿度 89%;全天 24~28°C,降水概率约 98%。建议带伞,尽量减少非必要外出。
规则:外层无框无底色;收起行 = 纯文字摘要 + ›(无按钮底色,与正文列左对齐);所有收起行固定高 28px,文字与箭头在行内垂直居中,不使用 top、transform 或 translate 做垂直偏移;对话流没有头像列,正文列与输入框左右边缘对齐;回复文本永远直接可见,底部发送时间和复制、重试等操作只在消息 hover 或 footer 键盘 focus 时一起显示。时间契约:用户消息在发送成功时记录本地时间,Agent 回复与独立 function/runtime 在首个可见状态创建时记录开始时间;Agent 内的 thinking 和普通 tool step 共享所属 Agent 消息的开始时间,独立 runtime/sub-agent 消息保留自身开始时间。所有后续流式更新、终态更新和层级迁移均保留该时间,刷新后优先使用持久化的权威时间。发送或重试后、首个回复内容到达前的临时 thinking 状态只占一行,开始时间紧跟状态文字显示,不创建正常消息 footer;首个回复内容到达后再切换为正式消息布局。执行摘要、正文和时间戳采用不同字号与行高,因此正文到 footer 使用 14px 视觉间距,使两段可见文字之间的留白一致;不改变折叠行高度。
Agent 回复发起的 function 及其执行树只属于该回复的执行时间线,不得再生成顶层 runtime 卡片;只有用户直接运行的 function 才作为独立 runtime 消息显示。
展开只向下:高度过渡用 grid 0fr↔1fr(0.18s),展开把下方内容推开、收起平滑抽走;视口不许跳——聊天区关掉原生 scroll anchoring(overflow-anchor:none),且用户点击后的 600ms 内暂停贴底吸附,展开/收起时开关行在屏幕上纹丝不动。收起动画结束后卸载子树,折叠状态不付渲染成本。执行过程始终默认收起:流式与历史消息都只显示摘要行,摘要使用原有文字扫光表示进行中;用户点击后可展开,列表继续向下拼接新步骤(淡入下滑 0.22s),运行行使用原有小点反馈,思考行的 note 显示最新一行。用户手动选择在本轮后续流式更新和落定时保持不变——流式与落定共用同一条时间线渲染路径(伪 blocks 由实时字段合成,finalize 后由真 blocks 接管)。

2时间线行:圆圈同时表示可展开与开合状态

左列始终是原有类型图标,统一采用与输入区同一套的 Solar Bold Duotone 实心图标(24×24),其中通用函数行使用锤子(sledgehammer);图标按类配色:思考紫 / 函数橙 / LLM 青 / 子代理绿。深色和浅色模式分别使用清晰、鲜艳的类型色。只要一行有直接子内容,圆形状态就始终可见:收起时为同类型的均匀浅色实心圆,展开时为空心圆环,不依赖鼠标悬停。收起态只有一个完整色面,不区分内心和外圈;浅色模式混入 8% 类型色,深色模式混入 14% 类型色,并与页面底色生成不透明颜色,因此会完全遮住背后的时间线。外圈由独立静态层绘制,固定为 2px,外径始终为主层 26px、子层 23px;外圈不参与任何 transition 或 animation,展开和收起只改变内部是同色填充还是页面底色。展开时,内部圆形色面在 220ms 内缩为 1.5px 宽、12px 高的竖段,从圆心向下移动到圆环下缘,并同步渐变为透明,露出原有中性时间线;不在原有时间线上再绘制第二条半透明线。子内容同时以 0fr→1fr、高度 220ms、透明度 140ms 和 -3px→0 的位移显示。折叠使用完全相反的 transition,连续点击时从当前动画进度反向执行。圆心、外径和图标位置不参与动画。叶子行不画圈。不增加任何箭头,也不显示后代步数。交互仍分为两个区域:带下划线的标题点击后打开右侧具体信息;点击图标、摘要或行内空白只展开/折叠。
思考 ×1 · 函数 ×2(进行中)›
思考 点开的行长这样——内容与标题条同宽对齐
展开的内容块只有一层浅底色,没有边框、没有自己的头部——头就是上面那行。内容块左缘与标题条对齐(不再额外缩进)。再点一次行收回去。只有思考行内联展开;函数/子代理的详情在右栏。
函数调用 research_agent · 内部还有子调用 → 展开是缩进一层的子时间线
web_search "北京 朝阳区 今日天气" → 12 results
LLM 两源数据一致,取中国天气网为准…
read_page weather.com.cn/101010300.shtml
函数调用 deep_lookup · 点击非标题区域逐级展开
search_index命中 18 条候选
LLM筛选并合并重复结果
write_report已写入调研文件
函数调用 web_fetch · weather.com.cn(运行中)
函数调用 bash · 退出码 1 — 失败的行图标换红 ✗
每个步骤行以 24px 圆角方块(7px 圆角)开头,用步骤色调着色:页面底色上叠 16% 色调、1px 内描边 26%、图标取满色。色调与图标来自 tool-presentation.ts:命令绿(终端)、文件修改琥珀(笔)、读取蓝(文件)、搜索青(放大镜)、网页与浏览器靛(地球或指针)、子代理绿(机器人)、记忆品红、规划石板灰、媒体玫红、系统暖灰、其他函数橙(锤子);思考保持紫、LLM 保持青。失败 = 红色方块加 ✗;运行中 = 正常图标外加一圈绕方块旋转的锥形扫光。行文字:动词标题(运行、修改、网页搜索…)字重 500,随后是标识参数的等宽小标签,再是结果首行的淡色文字。行内不出现原始 JSON;完整参数与输出仍在右栏。
几何:主层 tl-body 左内边距 34px,24px 方块落在 0..24(中心 12),1px 竖线压在 11.5px,首尾 12px 渐隐。子时间线缩进 6px、左内边距 30px,20px 方块(6px 圆角、11px 图标),竖线在 9.5px。行高至少 30px(嵌套 26px)。
动画:可展开行在摘要后显示一个小箭头,展开时旋转 90°。子内容与子时间线共用一个 220ms cubic-bezier(.2,.7,.2,1) 过渡;整条执行条保持 180ms 折叠。prefers-reduced-motion 下所有过渡缩为 0.01ms。
可展开性只由该小箭头表达,不预留箭头槽,也不显示“⋯ N 步”。右侧只保留 Copy、Switch 等动作,动作右缘不受行是否可展开影响。带下划线的标题始终是详情入口;辅助动作只在鼠标压着的那一行出现,进入和离开均即时切换,不做 opacity 过渡。:hover 必须挂在 step-head 而非整个 step——嵌套树里子行悬停不会同时显示祖先行的动作;键盘操作只由 :focus-visible 保持动作可见,鼠标点击留下的 focus 不会让旧行残留。点击图标、摘要或行内空白只展开/折叠,不打开详情。
嵌套层级:函数内部还有子调用时(Agent method 的执行树、工具的多层 call-tree),展开就是缩进一层的子时间线——同一套行样式递归下去,层数不限。
实现状态:生产组件已使用按工具着色的圆角图标块、动词标题与参数标签、运行中锥形扫光和可展开行的小箭头;所有时间线行均提供复制操作,步骤数提示已移除。

3手动函数运行:一个 FunctionInvocation,两种输入方式

函数表单和完整函数表达式是同一个 FunctionInvocation { name, kwargs } 的两种编辑方式。表单点“运行”直接提交 typed kwargs;普通输入框中的整段文本若精确匹配一个已注册函数及其命名参数,也在创建 user message 之前转换成同一个对象并提交。两者都只产生下面这一条 ProgramRun,不创建 UserBubble,也不经过一次额外的 Agent 文本回合。带前后说明文字、反引号或代码围栏的函数片段仍是普通消息,避免把讨论代码误执行。
输入入口判定持久化与显示
gui_agent(task="…", surface="browser")整段精确匹配、函数已注册、仅命名字面量参数POST /api/function/gui_agent → 单条 ProgramRun
Favorite Programs 的 gui_agent 表单表单 schema 生成同一个 typed kwargsPOST /api/function/gui_agent → 单条 ProgramRun
请解释 gui_agent(...) 或 `gui_agent(...)`不是整段函数表达式普通 UserMessage → Agent
识别器不执行 JavaScript/Python,不支持位置参数、属性访问、变量或任意表达式;它只解析字符串、数值、布尔、null 和函数 schema 允许的平面命名参数。缺少必填项或类型不合法时保留草稿、打开同一个表单并显示参数错误,不降级成聊天消息。运行展示保留左侧 28px 的 ƒ 类型标记和右侧无框时间线;该标记只标识 ProgramRun 类型,不是发言人头像。展开运行摘要后,根行只展示直接子节点;每个子节点的后代仍默认折叠,由用户逐层展开。
weather_report {"city": "北京"} · 3.2s → 今天小雨转暴雨,24~28°C…
web_search "北京 今日天气" → 12 results
LLM 汇总两源数据 · 1.1s
结论

完成3次调用:已取得、核验并汇总天气数据。

入口合同:FunctionForm 和精确函数表达式共用一个 kwargs 类型转换器和一个 HTTP 提交器;聊天提交只负责在其前面做精确识别,不能复制函数运行的占位、Project、channel、session 导航或错误回滚逻辑。识别成功后不得调用 sendChatMessage,不得写 pending user text。附件、运行中排队和系统 decision 不隐式变成函数参数。
顶部折叠标题、Conclusion 和 footer 共享同一个内容列起点,不使用各自的 left、padding-left、margin-left 或 transform 做视觉补偿。footer 与聊天消息的 footer 同款(同一套图标按钮 + 时间戳,悬停显现),justify-content:flex-start;Conclusion 或直接结果到 footer 的垂直间距固定复用 assistant 正文到 footer 的 14px 视觉段落间距,不另设紧凑间距;版本切换复用聊天的 .message-nav 组件(◀ N/M ▶),不另造样式。footer 的复制复制根调用的实际结果(error ?? output),不是参数 JSON。
分支语义:重试 = 参数不变的兄弟分支;修改(Edit)= 把上一次的参数预填回表单、提交后作为可改参数的兄弟分支运行(fork_of_node → 锚到原调用的前驱)。两者都在原位替换显示、旧运行经 ◀ N/M ▶ 仍可回看——绝不在会话尾部追加新调用。运行中占位 = 单条状态动画行;嵌套在别的消息里(nested)时不渲染 footer。
实现状态:生产函数 footer 已移除额外左内边距,时间行、折叠标题和 Conclusion 严格共享同一左缘。精确函数表达式、FunctionForm、共享 HTTP dispatcher、Advanced 参数分组和 bound 会话隔离均已实现;function-invocation.test.mjs 覆盖直接调用、普通文本边界、typed kwargs、成功绑定、导航与失败回滚,GUI 不可执行结果和 metadata 由对应 Python 回归覆盖,并已通过独立规格审查。

4Sub-agent 实时显示现状:记录保留,执行区默认收起

Sub-agent 行会实时写入执行树,但外层执行区在流式和终态都默认收起。数据保留在消息的 attachCards 或 contextTree 中;用户展开后可查看同一条持续更新的时间线记录。
T0 · 父级 RUNNING
执行区默认收起
const [open, setOpen] = useState(false),流式状态不参与初始展开值。
T1 · 子代理开始
记录“Sub-agent · 运行中”
时间线数据更新,摘要保持收起;用户已展开时新行按正常动画出现。
T2 · 子代理结束
同一行原位更新
稳定 card_id 对应的行改为“已完成/出错”,不追加副本,也不删除。
T3 · 父级 TERMINAL
执行区保持收起
终态更新不改变默认状态,也不覆盖用户的手动展开选择。
T4 · 用户重新展开
仍是同一条时间线行
记录没有变成卡片;悬停或聚焦该行时,行尾继续显示 切换 ↗。

慢速重放当前行为

演示刻意放慢,便于确认运行状态变化不会自动展开执行区。T4 模拟用户手动展开,用于确认收起期间持续更新的记录仍是同一条时间线行。
等待重放
Sub-agent: 检索近期论文 运行中…
状态边界:父级 workflow 是否仍为 running 只影响摘要状态动画 和步骤状态,不决定外层展开状态。收起不会删除记录;用户展开后,原记录仍按时间线行显示。
调用路径实时数据页面节点共同的可见性控制
聊天中的 agent()sub_agent 事件按稳定 card_id 更新 attachCardsSubAgentStepExecutionStrip
const [open, setOpen] = useState(false)
auto_workflow 内部 selection agenttree_update 更新 contextTreeTreeStep 子节点
实现合同:完成事件只更新同一条子代理记录;ExecutionStrip 以 false 作为流式和历史消息的共同默认状态,手动切换优先。相关入口:execution-strip.tsx、chat-stream.ts、runtime-block.tsx、check-spawn-cards.mjs。

5Sub-agent 父子分支导航:一个进入口,两个返回落点

这里直接画现有的两套返回交互。父对话从时间线里的 切换 ↗ 进入子分支;子分支顶部的 返回主对话 回到父分支最新位置,首条消息前的 创建自 / 切回调用处 回到发起 sub-agent 的那条消息。点样例内的三个按钮查看落点。
A · callerId
父分支 · 原始调用处
父分支与子分支之间的双向路径Switch 向右进入子分支,切回调用处向左返回。 切换 →
← 调用处
B · attach.head_id
Sub-agent 子分支
子分支返回父分支最新位置顶部返回主对话按钮跳转到父分支最新消息。 返回主对话 →
C · backTarget
父分支 · 最新位置
交互预览 父分支 · 最新位置
已返回父分支最新位置,并展开、高亮发起 sub-agent 的执行行。
已切回原始调用处;此落点之后的父分支消息不在当前 transcript 中。
请检索 2025–2026 年的相关论文,整理研究脉络。
我会让一个 sub-agent 独立检索和核对论文。
思考 ×1 · 子代理 ×1›
Sub-agent: 检索近期论文 已完成
已继续处理父对话:对 sub-agent 返回的论文清单去重,并将研究脉络写入项目文件。
顶部“返回主对话”回到父分支最新位置;重新加载后定位发起回合,展开执行区并高亮 sub-agent 行。
首条消息前“切回调用处”直接回到 callerId;父分支在该消息之后的继续内容不显示。
共同边界:三个动作都是切换同一 session 的活动 HEAD 并重新加载 transcript,不是打开新标签页。运行中后端禁止移动 HEAD;跨 session 的旧 AttachCard 只负责打开目标 session,不属于这个同 session 交互样例。auto_workflow 内只有 TreeStep 而没有 attach 指针的节点,仍只能打开右栏详情,不显示这组分支导航。

6左缘消息导航:横线即消息,悬停展开列表

聊天列左缘一列小横线,一条横线 = 你发过的一条消息(当前可视的那条加亮加长)。悬停整列 → 展开消息列表面板,点击跳到那条消息。把鼠标移到左边那几条横线上试试。
你好
你好!有什么我可以帮你的吗?
你能开一个agent,帮我去看看今天天气吗
北京今天小雨转暴雨,现在 25.8°C……建议带伞。
那明天呢?
明天多云转晴,26~31°C,无降水……
你好
你能开一个agent,帮我去看看今天天气吗
那明天呢?
横线只数用户消息(回复不占位);整列贴左缘——紧靠左侧栏分割线(left: calc((100% - 100cqw)/2 + 8px),chat-area 是 inline-size 容器),不悬在正文中间。面板从刻度右侧展开,行内文字单行截断;点击 = 平滑滚到那条消息并闪一下。面板出现时横线隐去,二者不叠。列表滚动时高亮跟随当前可视消息。

7文件修改:精确归因、分层 Review、安全 Undo

本节只定义聊天卡与 Changes center tab 的视觉表面。文件归因、Undo、历史 Revert、多轮 Restore、Branch、checkpoint、并发和保留策略的唯一权威是 文件修改、Review 与历史恢复。聊天卡只陈述本轮精确修改;Review 使用 This turn、Current branch 和 Workspace。
后台进程逻辑与测试已更新。
3 个文件已修改+27−6当前文件状态与记录的来源不匹配
playbook.json+1−1
src/runtime/player.py+16−4
tests/test_player.py+10−1
变更 ×
本轮当前分支工作区+27 −6统一视图并排视图打开文件
代码
player.py
playbook.json
测试
test_player.py
src/runtime/player.py+16−4
38 def start_process(config): 39 - process = launch(config.command) 39 + process = launch(config.command, cwd=config.cwd) 40 + process.wait_until_ready(timeout=config.timeout) 41 return process
文件行与溢出:每一行以 Files 面板相同的按类型文件图标开头(FileTypeIcon:@pierre/trees 内置图标集,以内联 SVG 渲染,15px 图标置于 16px 框内),按文件类型着色,浅色与深色主题各有一套配色,.ts、.md、.json、.py 一眼可辨;标题区保留铅笔图标。标题计数取本轮 file_count,列表不得与它无声地不一致。折叠时显示三行,并用“再显示 N 个文件”展开其余已加载行(最多 20 行)。计数中卡片未列出的文件——超过 20 个,或有界摘要尚未加载完整列表——在列表末尾合并为一行“还有 N 个文件 — 在审阅中查看”,点击与 Review 按钮相同,打开 Review 标签。持久化摘要最多保存 20 行;摘要计数多于所列行时,卡片通过 review_scope 请求完整列表,服务端此时读取完整变更日志而不是有界摘要。
显示状态:只有存在持久化的本轮文件记录,或该轮包含直接文件写入工具时才挂载聊天卡;普通文字回复与确认无记录的轮次完全不显示卡片,也不显示 Review。旧文件写入轮次缺少内嵌摘要时进入视口后按需查询;查询期间只保留不可见 probe,空结果隐藏,查询失败仅显示紧凑的“无法加载文件修改 / 重试”行内状态,不显示无数据依据的 Review。该状态实现已通过独立 specification review、quality review、真实组件测试和完整 Web gate。
视觉边界:保留完整外边框、圆角、文件列表和 footer;整张卡片统一使用产品 --font-sans,标题、路径、统计、状态与按钮不切换独立 monospace 字体,数字只保留 tabular alignment。header 固定 40px,左侧内边距为 12px,FeatherIcon—标题与标题—统计均使用 12px 间距;左侧使用用户指定的 19px FeatherIcon,并以一条横线分隔摘要与文件列表。深色主题中,header 和 footer 使用 --bg-tertiary,文件列表使用 --bg-primary;浅色主题中,整张卡、header、Review 按钮和 footer 全部使用 --bg-primary,不使用阴影或深色上下色块,只由外框和分隔线建立边界。所有主题的文件行默认透明,只有 hover 时叠加 --bg-hover;footer hover 同样只叠加 --bg-hover。FeatherIcon 表示本轮包含直接文件编辑,不使用网站 logo;hover 使用组件定义的 1.6 秒位移与旋转序列,reduced motion 下停用。header 的 FeatherIcon、14px 标题、统计区、13px Undo/Redo 与 Review 按钮必须按同一垂直中心线对齐,不使用 baseline 对齐;方向图标使用 1em 与按钮文字等高。总增删数字始终同时显示,包括 +0 或 −0;统计字号为 12px,其下 2.5px 双色比例条只编码 additions / deletions 的真实占比,不增加分类 chips 或装饰性数字。卡片以自身 inline size 建立 container;卡片宽度 ≤420px 时标题从 “3 files changed” 缩写为 “3 files”,FeatherIcon、统计和操作保持可见,并恢复 6px / 5px 的紧凑间距;viewport media query 只作为兼容路径。历史操作预检未通过时,header 不显示 Undo/Revert/Redo/Reapply,而是在 Review 左侧显示一条 12px 主题强调色状态提示;提示单行省略,title 保留全文,窄卡片限制为 92px。该状态不再在文件列表或 footer 下追加独立提示行。成功 Undo 后显示 重做;历史 Revert 的反向操作显示 重新应用。两者都调用同一安全 reapply transaction 并重新预检,不维护独立线性 redo 栈。Review 使用中性色描边按钮;Undo / Revert 使用文字加回退图标,不使用强调色。Code / Tests / Docs / Large 分类只在 Review 中出现。文件列表到文件行的四边间距统一为 4px;文件行固定 28px,行内左右内边距为 6px,文件路径使用 13px 产品 sans,增删统计使用 12px 产品 sans 并固定列宽;整行导航,不放展开箭头或 hover 操作组。唯一的展开控制属于 Show more footer;展开后 footer 只显示 13px 折叠,完整 Review 始终由 header 的 Review 按钮进入,不再追加 “Review all” footer。

官方参考与可确认边界

系统本轮 / checkpoint审查撤销 / 回退
Codex开源 TurnDiffTracker 只消费 committed apply_patch 精确 delta,不重读 workspace;delta 不精确时清空本轮 diff。/diff 单独使用 Git:tracked 走 git diff,untracked 走 git ls-files --others --exclude-standard。开源 thread/revert 明确只回退对话历史;Codex App 文件 Undo 的客户端实现未在当前开源仓库中确认,不推断。
Claude Code每个 user prompt 建 checkpoint;只跟踪 Write、Edit、NotebookEdit 等直接文件编辑工具。保留最近 100 个 checkpoint。Desktop/Web 使用 session 级增删统计入口;打开后左侧文件列表、右侧单文件 diff,支持行级评论和再次交给 Agent 修改。代码与对话可分别恢复;Bash、一般后台 sub-agent、外部并发修改、symlink/hardlink 不保证恢复,没有 tracked edits 时不显示代码恢复选项。
OpenProgram 当前实现直接工具 checkpoint 之外,还在每次 Bash 前后递归扫描最多 20,000 个文件并预复制不超过 5 MiB 的候选内容。聊天卡内请求完整 diff;历史消息逐条请求文件列表;shadow Git 和当前磁盘 fallback 混用。大文件没有 baseline 时被记录为 pre_existing=false;Undo 可能删除原有文件,且部分失败后仍可写入 reverted 状态。

采用、调整与拒绝

结论设计项OpenProgram 处理
采用Codex 的精确增量归因本轮统计只来自 committed file mutation event;没有精确 before/after 就不进入本轮卡。
采用Claude/Codex 的独立审查面聊天只保留摘要;Review 使用左侧文件导航、右侧单文件内容,并支持行级评论。
调整Claude 的 session diff 与 Codex 的 turn/repo diff放进同一个 Review tab 的三个 scope:This turn、Current branch、Workspace;切换 scope 不改变术语含义。
调整Claude 的 checkpoint 恢复保留 turn 级快速 Undo,但必须先完成全量冲突预检;恢复记录作为新的不可变 journal transaction 保存。
拒绝Bash 目录扫描归因删除 Bash 前后 cwd 遍历与候选文件预复制。普通 Bash、后台进程和外部修改不计入本轮,也不能由本轮 Undo。
拒绝近似本轮 diff 与静默部分恢复无法证明来源时只进入 Workspace Review;Undo 预检失败时不修改任何文件,并列出冲突路径。
调整参考产品的 header 操作层级采用“文字 Undo + 中性描边 Review”的主次关系;左侧使用 FeatherIcon 表示直接文件编辑,尺寸、间距、颜色和 Review 页面继续沿用 OpenProgram 视觉系统。

三个 Review scope

范围权威数据源包含撤销
This turn当前 turn 的精确 mutation journal主 change set;关联 child change set 按真实 producer 保留,并通过 origin 分组,不复制 mutation。仅当全部记录为 recoverable 且 current digest 等于记录的 after digest。
Current branch当前 active lineage 的 mutation projection所有已完成 turn 与显式 revert/restore/reapply;不包含 sibling branch、普通 Bash 和外部写入。从具体 turn/checkpoint 发起,不提供无范围的一键 branch Undo。
WorkspaceGit 跟踪差异 + ls-files --others --exclude-standard当前未提交 tracked/untracked 文件;遵循 .gitignore。非 Git 工作区显示 unavailable,不使用目录扫描替代。无。Workspace 变化可能来自用户、其他 session 或后台进程。

目标 mutation journal

1 · 修改前

直接编辑工具在写入前记录规范路径、文件类型、before_digest 与可恢复 baseline。无法安全保存 baseline 时标记 recoverability=unavailable,绝不写成“原文件不存在”。

2 · 提交修改

写入成功后记录 after_digest、operation、行数、binary/large、turn_id 和 actor_id;失败不产生 committed mutation。

3 · 持久化摘要

turn finalize 只归并本轮 mutation,生成净文件列表和增删统计并随 transcript 持久化。复杂度只与 changed paths 有关,不扫描 cwd。

4 · 按需审查

Review 先读取摘要;选择文件后才请求一个有界 diff。切换文件取消旧请求;聊天 DOM 永远不挂载 diff。

必须删除的当前逻辑:agent_loop._snapshot_cwd、_walk_scan 与 _checkpoint_changed_files 不能继续参与文件归因。特别是“超过 5 MiB 未暂存 → 使用不存在路径 → pre_existing=false”必须消失;baseline 缺失只能表示不可恢复,不能表示新建文件。

Undo 安全合同

阶段 / 状态规则可见结果
适用条件每个文件必须有精确 operation、before/after digest;普通文件才可恢复,symlink/hardlink 与缺失 baseline 标记 unavailable。任一文件 unavailable,卡片不显示可执行 Undo;Review 解释具体原因。
预检恢复前重新读取所有 current digest。新建文件只有仍等于 after digest 才可删除;修改/删除/rename 同样要求未被后来编辑。任一冲突则整次 Undo 不写磁盘,状态为 blocked-conflict,列出冲突路径。
应用全部预检通过后,将恢复内容写入同目录临时文件并原子替换;多文件操作保留回滚 staging。意外 I/O 失败时回滚已应用项,不能把部分成功标为 Reverted。
记录成功恢复追加 revert_of=<turn_id> transaction;原 turn 状态由 journal 推导。聊天与 Review 显示 Reverted,历史文件列表仍可审查;不修改原对话内容。

性能与内容上限

路径目标上限降级
轮次收尾不遍历 workspace;只归并 mutation journal 中的 changed paths。单条 mutation 缺少精确信息就排除并记录诊断,不生成近似统计。
对话记录摘要随消息返回;首屏最多 3 个文件,不再为每条历史消息发送 list_turn_files。legacy 消息只在进入视口或主动 Review 时查询。
文件列表服务端每页 100 个文件;前端只保留相邻页并支持键盘导航。分类统计来自 summary,不要求先加载全部行。
选定差异同时只挂载一个 diff;每批最多 200 行或 256 KiB,按 hunk/page 继续加载。binary 或超大单 hunk 显示统计、原因与 Open file,不把不完整内容冒充完整 diff。
渲染预算diff 计算有明确时间预算;超预算返回 metadata,Review 不阻塞聊天和 Agent 完成。允许稍后重试或打开原文件,绝不回退到“before 与当前磁盘”的跨轮近似 diff。

Review tab 与交互合同

动作或状态结果边界
Review / 文件行打开同一确定性 r:<sessionId>:<assistantMsgId> center tab;文件行同时选择路径。不加载聊天内 diff;重复点击只聚焦或更新 selection。
布局未分屏时复用现有 group,把 Review 放到聊天右侧;满员 split 时作为普通 active tab。不替换已有 peer,不增加第三 pane,不新增第二套 tab store。
Scope 切换This turn、Current branch、Workspace 保持同一左 rail / 右 diff 构图,标题和来源标签同步变化。Workspace 不显示 Undo;ignored 文件不出现;非 Git 明确 unavailable。
行级评论点击 diff 行添加评论;多条评论在下一条 prompt 中作为结构化文件位置一起发送。评论不直接修改文件,不假装 code review 已执行。
大文件 / 二进制显示精确统计、diff unavailable 原因和 Open file。不渲染完整大文件,不把日志自动归类为 Agent 修改。
撤销冲突卡片显示 Review conflict;Review 定位冲突文件。没有 Force undo 快捷按钮,避免覆盖用户或其他 session 的后续编辑。

实施边界与验收

范围首选位置明确排除
精确 mutation 与恢复programs/tools/files/、store/snapshot/checkpoint/、turn context/finalize 与 agent/internals/_revert.py。不再给 Bash 增加目录 watcher,不更改用户 Git,不从当前磁盘反推本轮。
Review 协议ws_actions/turn_files.py、消息 metadata、现有 Git helper;新增分页 summary/diff 请求。不返回无界字符串,不保留 approximate fallback。
中心审查标签页center-tab-ids.ts、center-tabs-store.ts、center-tab-groups.ts 与最小 Review pane。不实现新窗口、modal、第三 pane 或参考产品品牌元素。
聊天卡turn-files-chips.tsx、用户提供的 FeatherIcon 与现有 CSS。FeatherIcon 只标识直接文件编辑,不放网站 logo;不保留 inline diff、文件行箭头、hover 操作组或参考产品品牌元素。
视觉验收条件:聊天卡最多显示 3 个初始文件名,不加载 diff;最多扩展到 20 个路径,其余通过末尾“还有 N 个文件 — 在审阅中查看”一行进入 Review;Changes 同时只挂载一个有界 diff;This turn / Current branch / Workspace 的来源标签始终可见;历史 Restore 使用独立 impact sheet,不把 Undo/Revert/Restore/Branch 堆在卡片 header。完整数据与恢复验收见权威运行时设计。
实现状态:生产聊天卡保留外框与 header 分隔线,顶部高度为 40px,使用 19px Feather 文件修改语义图标;Undo/Redo/Revert/Reapply 只显示当前可执行的一项,Review 使用中性色描边按钮。center Review 已实现 This turn / Current branch / Workspace、有界文件与 diff 分页、actor/producer/linked impact;branch checkout mismatch banner 会在文件修改前要求 Keep current files 或 Restore branch code。运行时与视觉实现均已通过 Task A–E 的独立审查。字体、三层背景、8ch 固定统计列、仅 Collapse footer、紧凑间距和浅色主背景修订已通过独立审查;历史预检提示移入 header 的实现也已通过本轮独立 specification review 与 quality review。

8消息表面:无头像、凸起的用户气泡、编辑框与操作行

对话流没有头像,也没有发言人名字行:右侧有气泡、左侧无气泡就足以区分谁在说话,正文列与输入框左右边缘对齐。仅保留两种标记,都不是发言人头像:多智能体会话中,非 main 的具名智能体回复在正文上方保留一行 18px 的紧凑身份行;手动函数运行保留 ƒ 类型标记(第 3 节)。用户气泡、输入框与文件修改卡片是同一种凸起表面:--bg-input 底色(浅色主题为白色,深色主题按主题取值)加 --composer-shadow。助手回复平铺在页面上,没有气泡。把鼠标移到下面的消息上查看操作行,按钮停留后显示提示。
把 README 里的安装步骤改成 uv,顺便检查一下 Python 版本要求。
已改为 uv sync,并把最低版本写成 Python 3.11,与 pyproject.toml 一致。
用户气泡:右对齐,宽度随内容收缩,最大 75%(窄屏与分屏 88%);16px 圆角,内边距 10px 16px,无边框。底色 --user-msg-bg,每个主题都等于 var(--bg-input);阴影是静止的 --composer-shadow——已发送的消息不是控件,悬停不加深阴影。气泡内的代码块和粘贴折叠块用 --bg-primary 内嵌底色,与气泡表面拉开。输入框上方的排队消息是尚未发送的用户消息,使用同一表面(10px 圆角)。
编辑态:点击“编辑消息”后气泡变成与主输入框同款的输入框:--bg-input 底色、23px 圆角,静止时 --composer-shadow,悬停或内部聚焦时过渡(180ms)到 --composer-shadow-focus;宽度占满整列。框内依次是保留的附件(可移除)、新加附件条、与主输入框相同的输入行(支持 @ 文件提及),底部一行为:左侧官方 ghost 圆形图标按钮(icon-sm)中的 Solar 回形针,提示“添加图片或文件”;右侧“取消”(elevated sm)与“保存并重新发送”(默认主按钮 sm)。Esc 取消,⌘/Ctrl+Enter 保存;可把文件拖到框内。编辑期间隐藏操作行。
操作行:位于每条消息下方——用户消息右对齐在气泡下,助手消息左对齐、与正文间隔 14px。顺序:时间戳、引用、复制、重试、编辑(仅用户)、分支(仅助手)、回退到这里(仅用户),有兄弟版本时追加 ‹ N / M ›。每个按钮都是官方 ghost 圆形图标按钮,尺寸取最接近 26px 的 icon-xs(24px),内含 16px 动画线条图标;静止为 muted 色,悬停变为 primary 色并出现圆形浅底。整个按钮(悬停与键盘聚焦)驱动图标动画。标签用 HoverTip + TipBody 显示(停留 1.5s 后出现),不使用原生 title;同一文字作为 aria-label。操作行默认透明,消息悬停或行内聚焦时整行显示,触屏设备上常显;无论是否显示,行高都固定为 24px。处理中的按钮置灰(40% 不透明度);复制成功后图标变为绿色对勾 1.2s。

助手消息在各生命周期状态下的渲染

流式、已完成与恢复后的助手消息共用 ExecutionStrip、ThinkingStep 和 FunctionStep 组件。缺少有序内容块时,按已有思考与工具记录的存储顺序适配展示;此回退不重建缺失的交错顺序,也不编造工具记录。工具结果、错误状态、标识符和完整输出引用保持完整。完成后不切换到旧版带边框的 ToolsBlock 表格。

实现验收:完成包含工具调用的一轮,展开执行摘要,再重新加载对话;仍可使用相同的共享行与详情交互。

聊天数学公式分隔符

聊天 Markdown 已在 $$/$/\[/\( 之前暂存完整内容块;处理顺序对应 marked 并在清洗后将其还原为转义文本。React 消息气泡仍需运行 KaTeX: MarkdownText 在绘制后排版,聊天区域固定路径则在观察到的消息列上调用 typesetMath 。未闭合的流式公式不构成完整暂存对;收到闭合符后的下一次绘制才排版。清洗、代码、链接和普通转义行为保持不变,不引入新数学库。

验收:示例 \(d_t\) / \(v_t = (d_t - d_{t+1}) / d_t\) 在助手气泡中生成 KaTeX HTML;块级 \[/$$ 以及行内 $ 也如此。无公式正文和未闭合起始符不排版;无效公式不抛出异常。

助手回复不显示 token 与服务档位用量页脚。当前执行片段使用原有文字扫光与运行小点;正文页脚不添加加载标识。

有界对话历史

对话记录使用双向、带字节预算的分页。每条连接持有一个小型 LRU,存储带消息 ID 索引、磁盘支持的不可变快照。分页请求只读取选定展示组,保留工具后代节点和压缩元数据。快照 token 限定于连接、会话及捕获的分支 head。替换快照或断开连接时释放临时文件。

浏览器目标缓存四个对话,允许正在显示的分栏和在途请求暂时超过限制;删除对话时释放分页引用。每个已加载对话记录最多保留六个普通分页,内容预算为 8 MiB;为保留单个大轮次,允许一个不可拆分分页。淘汰从阅读位置的相反方向进行,绝不删除持久化记录,并保留活跃更新。向任一方向回滚时自动取回被淘汰分页。视口外已测量行卸载但保留高度;被淘汰行的高度记录删除。

预取距离随滚动方向与速度调整,并有最大值。同一时刻只运行一个分页请求,失败时指数退避重试。不可见分栏停止请求,过期响应不能覆盖新代次。插入或淘汰分页时,在绘制前恢复可见消息及其偏移。恢复依据消息 ID,而非旧窗口中的像素偏移。“跳到最新”直接获取最新窗口。

验收使用30万条消息的合成来源:反复双向分页时,缓存分页、渲染行与测量记录保持有界;延迟下快速滚动仍保留可见消息;过期响应、重连、分支变化与实时更新不能破坏窗口。索引建立后,后端分页读取不得扫描或解析完整快照。冷启动快照构建单独测量。

协议 v2 将完整图延迟到视图可见时请求;普通分支元数据请求不包含图正文。冷启动快照构建只读取并补全权威分支一次;之后的索引分页避免完整恢复。

模块职责
message-list.tsx消息组合与已测量行渲染
use-chat-area-stick / use-message-viewport / use-history-window跟随、视口测量与活跃历史生命周期
history-window / history-viewport有界分页存储与消息锚点操作
session-history-loader网络请求、代次检查与状态协调
session_history.py磁盘快照索引与有界分页读取

交互契约:中间历史窗口的底部不是对话最新位置。主分栏与拆分分栏共用跟随、自适应渲染和跳转行为。存在更新历史或请求失败后,跳转仍可使用;重复点击共享一个待处理意图。滚轮、指针和键盘交互在待处理跳转或已保存位置恢复改变窗口前取消它们。普通分页仍补偿当前阅读位置新增的行。分栏卸载保存最终阅读锚点。连接恢复后继续自动加载,不保留过期重试延迟。

交互验收:浏览器测试覆盖中间窗口底部、失败与重复跳转、被打断的延迟恢复、独立拆分分栏及卸载/重新挂载。这些交互优化已实现并通过独立规格与质量审查。浏览器验收还检查卸载后排队动画帧不会破坏已保存锚点。

性能契约:捕获消息既无压缩覆盖、也无需要图注解的 spawn/attach 关系时,有界聊天加载跳过图构建。需要注解时,共享图构建器执行相同语义过滤但不计算视口几何;打开 DAG 仍请求完整布局。首次持久化恢复的复杂度仍随存储节点数线性增长。Markdown 缓存使用256项 LRU 和 8 MiB 预算,按 UTF-16 源文与清洗输出长度估算;单项超预算时仍渲染但不缓存。缓存策略绝不绕过清洗。

性能验收通过公开加载处理器与生产浏览器组件使用已持久化合成消息,分别记录耗时、帧间隔与保留堆内存。稳定回归检查断言跳过的工作、语义等价、缓存淘汰和有界保留,不断言特定机器计时。性能优化已实现并通过独立规格与质量审查。公开处理器回归验证跳过图工作与压缩等价;浏览器回归验证字节预算淘汰、LRU 提升和超大条目不缓存。耗时与保留堆比较属于合成测量,不保证完整 App 工作负载的性能。

实现状态:已实现。30万条消息合成来源的浏览器测试验证有界双向分页、延迟传输下快速反向滚动、已保存消息恢复、实时行保留和加载期间跳转。后端索引读取、快照清理与 Web 回归测试集通过。冷构建仍读取权威分支一次;合成测量不能证明真实大型附件或工具输出的延迟。

对话加载标识

仅将原有空回复 thinking… 提示旁的加载标识替换为新版 18px 开口双环。双环反向旋转并交替改变半径和线宽,颜色跟随主题;减少动态效果时保持静止,强制颜色模式使用系统文字色。

执行摘要、思考步骤、函数步骤、嵌套调用、分栏标题、重试提示和正文页脚不使用此标识。不再按照工具、思考或正文阶段增加标识位置。函数时间线恢复原有布局、文字扫光、运行小点、展开结构与详情交互。

验收:空回复加载提示显示一个新版标识;函数调用、思考内容和正文到达后,不在时间线或页脚添加双环。审批与终态不显示加载标识。保留独立的历史加载、嵌套进度和消息稳定性修复。实现与验收证据记录在 generation-motion-layout 任务报告中。

聊天更新的归属

消息 ID 和有序 blocks 决定聊天内容。执行树补充所属回复,不通过状态树重新生成聊天。历史快照按同一消息合并已收到的进度:运行中的旧快照不能删除函数调用、缩短文字或将已结束回复恢复为运行中。终态快照更新状态与元数据,未携带执行记录时保留已收到的记录。切换分支和历史窗口仍删除窗口以外的行。

调用树保留持久化 path。同一函数的多次调用保持独立,在所有被文字分隔的执行组之间,每棵调用树只匹配一次。函数结束后保留原来的执行摘要;直接执行函数继续显示独立卡片。验收通过实际 reducer 和组件覆盖流式更新、部分历史、结束、迟到的调用树、错误、取消与重新加载。

实现状态:待验证。

排队与当前轮补充

排队消息在当前轮结束后按顺序发送;补充消息在执行器确认接收后,显示在接收前后的回复内容之间。补充记录保留独立用户节点,回复的有序内容保存边界引用,实时流、取消和历史加载使用相同边界。旧记录缺少边界时只按已持久化的前驱关系把补充放到所属回复前,不猜测文本分割点。

轮询只确认同一命令,不改变可见等待提示。等待确认期间不能重复发送、编辑或移除。明确拒绝后保留为排队消息;连接异常保持待确认,不作为新一轮重发。附件保持普通排队语义。每个会话独立,排队中编辑保持顺序。

流式 steer:普通文本或思考生成期间接收的补充指令会结束当前模型请求,保留已生成内容,在已有的模型请求完成边界持久化应用指令,然后启动下一次请求。旧请求关闭后才继续。工具参数生成、运行中的工具和结构化输出继续使用原有安全点;steer 不取消或重复工具调用。定时检查也覆盖模型暂时没有输出的情况。

等待 steer 时停止:保留部分回复及其当前历史位置。因停止而被拒绝的补充消息保留为未发送草稿,通过明确的发送按钮重试,不自动转成下一轮。取消状态投影仅以比较并交换方式将当前顶层轮次从自身用户节点推进到已有回复节点,不改变后续消息或用户选择的其他分支。

实现状态:流式中断与停止保留已通过独立规格及质量审查、相关测试和完整验证;已安装 App 验收独立记录于发布证据。

失败回复

失败回复保留已生成正文及有序的思考和函数记录。终止提示位于这些内容之后、消息操作之前。简短标题说明失败原因,技术详情默认折叠并按纯文本显示。不增加自动重试。

正文块与错误详情分别存储。节点 output 保留旧有错误文本语义,供模型上下文及非 Web 调用方读取;Web 映射从有序正文块中恢复已生成内容。失败节点在最终事件发出前保存详情、分类、可重试性和重试等待时间。刷新后恢复相同提示。兼容旧记录中的错误正文,不修改历史,不在错误提示中重复回复内容。

范围为主聊天错误、共享助手气泡及历史映射。取消、审批、嵌套函数详情、重试分支和 Goal 报告保持现有语义。沿用既有提供商错误分类设计,只补充持久化展示,不新增错误存储或第三方 UI 依赖。

验收:通过真实回合入口先产生正文和工具再失败;流式和冷启动历史均显示正文、记录及一条末尾错误。空回复和旧失败记录可见,成功和取消状态不新增错误提示。窄屏和深色主题中可展开详情。

发布回归继续验证浏览器权限继承和高层 agent 委派。已绑定页面的测试显式提供绑定,并验证现有的同一观察连续失败三次后停止及具体失败原因。这些测试不改变浏览器工作流和权限策略。

实现状态:实现、独立规格与质量审查及完整声明验证集均通过。浏览器回归测试与现有绑定页面路径及失败次数限制一致。已安装 App 验收独立记录于发布证据。