UI 大改版 · 交互稿 rev4

行尾槽位=在途+未读 · 桌面 1440×900 与 H5 375×812 共用同一套层级

图鉴分区 G1行尾槽位:在途 + 未读 G2消息与分隔线 G3过程表达 G4结果 · 产物 · 子 run G5输入区与附件 G6全局条与系统健康 G7左栏 G8边界与超长 G9其他面板

rev2 的第一条纪律:稿子里不许出现系统给不出的数字。 上一版有 14 类虚构表达(排队位置、已等时长、单步耗时、命中数、上传百分比…), 本轮逐条换成真实字段能支撑的说法,换法写在每条右上角。 另一条纪律:产品界面里不写设计理由——解释只出现在这些图鉴标注里,不出现在框内。

图鉴里哪些能真点、哪些只是外观(rev11b 核查)。 样张是简化 DOM,浮层类组件在这里被摊平成静态外观,按钮没有接线—— 不写明的话,评审者会以为交互坏了。
能真实触发:① 折叠(过程行 / 思考 / 原始事件 / 技术细节 / Agent 行)—— 共 10 处,它们只切换就近祖先的 data-open,不依赖任何浮层载体; ② 长按 / 右键出完整值——rev11b 补了文档级弹层载体后现在全页可用。
仅静态外观(各自旁边已标黄条):心跳浮层 G6-1、三点菜单 G7-2、 删除确认 G7-3、命令面板 G5-3、完整值弹层 G8-2 的那一块。 图鉴里没有任何 data-ov-open / data-menu / data-goto 钩子——那三类交互只在「原型」页可用。

G1 · 行尾槽位:五种条件

用户原话:「跑完之后是绿点,点击进去查看后绿点消失,同理红点。」 —— 这不是「状态」语义,是「未读」语义。所以这些点不能叫「这条会话的状态」: 已读之后活儿依然在,只是不再提示你。

槽位按顺序取第一个成立的条件,不是「几种运行状态视觉」:

  1. active_run_id 指针非空 → 转圈
  2. 未读且 gm_unread_kind === "done" → 绿点
  3. 未读且 gm_unread_kind === "stopped" → 红点(cancelled 归此档)
  4. 未读且 needs_you 或未知 kind → 中性点
  5. 无未读 → 空槽

点进会话即调 POST /sessions/:id/read,槽位回到空白。 绿点 / 红点 / 中性点只用于会话行——它们是未读提示,不是 Run 终态视觉; 其它位置的终态一律用文字徽章。

两条不得越界的措辞:
① 转圈只能说「指针非空」,不能说「有活还没结束」。 on_busy=queue 时后一个 Run 会覆盖指针、前一个终态又无条件清空、 后一个被 claim 时不回写 Session——所以指针为空不等于没有在途的 Run。 要成为完整的在途事实源,需后端从 Session 的非终态 Runs 派生(或终态时重指向下一个非终态 Run)。
② 空槽只能说「无未读提示」——既不能说「无事」,也不得从空槽反推「没有在途 Run」。 已读过的 needs_you 仍然可能待你处理,清掉的只是提示,不是那件事。

本分区共同前提:gmLoopNotifyUser 目前只被 GM 自主循环调用, 普通会话的 run 进入终态时不会点亮未读。在后端补上那一次点亮之前, 本分区画的绿点/红点对普通会话不会真的出现。转圈不受影响。

G1-1槽位五态 · 视觉重量刻意拉平 转圈不上语义色——在途既不是好也不是坏,只有未读点有颜色。圈 12px 细描边中性色,点 7px,谁也不比谁抢眼。中性点是 needs_you 与未知 kind 的诚实降级:它确实有未读,但既不是成功也不是失败。
指针非空 未读 · done 未读 · stopped 未读 · needs_you / 未知 无未读提示
取值顺序:active_run_id 指针非空就显转圈;否则检查未读;都没有则空槽。未读标记不会因为显示了转圈而丢失。
末态刻意不叫「已读 / 无事」——已读过的 needs_you 仍然可能待你处理, 清掉的只是提示,不是那件事。
G1-2行尾固定槽位 · 点进去就消失 照用户参考图 ref-01a / ref-01b:槽位在行尾不是跟在标题后面。空槽必须真的占位,否则有点的行和没点的行会左右错开,列就断了。
点进去之前
点开「引擎锁 · 回归与文档」之后 —— 只有那一行的绿点消失,其余不动
G1-3「在途」里的四种情况:只用文字区分,不另出视觉 rev4 改名:第一档不叫「在跑」而叫「在途」——queued 是还没开始、waiting_for_worker 是没有可用 Worker、waiting_for_rate_limit 是暂停中,把它们统称「在跑」是不实的。可见文字与 aria-label 分别写清楚。
已排队 等 Mac 接活 · 派出于 15:41 限流暂停 · 计划 15:47 重试 在跑 · 本回合已跑 1m12s
被砍掉的写法:「前面还有 2 个任务」(无排队位置契约)、「已等 40s / 已暂停 2m10s」(无 status_changed_at)。「计划 15:47 重试」只在 available_at 有值时才写。

G2 · 消息类型与分隔线

G2-1用户气泡 / 助手答案(流式 · 终态) 回答卡是每屏唯一主角。答案写结论不写日志——「要把 xx 整段替换…」这类过程叙述属于第二层的过程块,不占对话主体。
把引擎锁扩到所有已绑原生会话。

正在改绑定逻辑

引擎锁已经扩到所有已绑原生会话。一条会话只认它第一次绑定的 runner;请求里带别的 runner 时服务端回正,不再静默切换。

G2-2助手通知三态 / system·import / runner 原始 通知不是对话,所以不进气泡也不进卡——一条带语义底色的横条,读完即走。rev4 改口径:「导入了 24 条记录」已删——adopt-native 只建立绑定,transcript 是临时快照不入 Store,没有 producer 也没有 import_count。只有真实的 system/import 消息存在时才原样显示它;按 transcript 数组计数时只能写「当前快照加载 N 个 turn」并注明可能不完整。rev5 再更正:消息/Transcript 契约不返回真实 CLI argv,所以那行不能叫「runner 原始」并贴一条拼出来的命令——运行时直接渲染真实 message.content,不改写。rev7 再更正:上一版那句「已回正到…codex-cli 未生效」是我自己写的转述,不是 producer 的原文,而我手上没有 producer 的逐字文案——所以这里改成显式的「界面转述示意」,等拿到真实文案再逐字替换。命令形态另起一行标「调用形态示意(非真实 argv)」。
已接入本地 CLI 会话。
Mac 刚刚掉过一次线。这段时间的消息可能没被接走。
派活被拒。这条 backlog 还没过评审,服务端没有产生任何副作用。
— 当前快照加载了 24 个 turn,可能不完整 —
[runner] 界面转述示意 —— 运行时此处直接渲染 message.content,不改写、不拼装
调用形态示意(非真实 argv):claude --print --resume <id> <原话>
G2-3回合分隔线 / 未读分隔线 / 日期分隔线 回合线只写「本回合跑了多久」——这是唯一合法的时长口径(见「事实基线」页)。不做单条已读,系统里没有这个字段。
今天 · 15:41
本回合跑了 1m12s
以下内容晚于上次已读标记

G3 · 过程表达(第二层)

rev2 改动:八种情况一律默认折叠,包括在跑(上一版在跑时默认展开,用户 #5 要求改掉)。 过程无卡无影,只有一条左缘线;只有 diff 摘录块与代码块允许有边界。

G3-1过程行 · 默认折叠 摘要行只放能算出来的:已记录的活动条数、本回合耗时、有没有失败活动。rev4 改口径:不写「6 步」而写「已记录 6 步」——那是已经收到的活动条数,不是这轮的总步数,总步数系统给不出来。
第三行的「本 Run 为重试」只在 retry_of 有值时出现——那是真字段;上一版的「1 次失败重试」是把 error activity 当成了 retry。
G3-2展开后:步骤三态 / 工具调用 / 叙述行 / 聚合折叠 CLI 单行拉平不回退:一行 = 一个调用,结果缩排在下面。给每个步骤套白卡会立刻变成卡中卡。
先确认绑定逻辑落在哪一段。
✓Readservices/orchestrator/server.mjs
842 lines
✗Bashnpm run cc:test:runner-default
exit code 1
◐Editservices/orchestrator/server.mjs
✓Grepnative_session_runner_id
G3-3思考折叠 / 联网搜索 / 原始事件 L3 「思考 12 秒」已删——跨 runner 没有单步耗时。rev7 更正两处:① 连 query 也不显示——规范化事件里没有跨 runner 的 WebSearch/WebFetch query(Claude parser 分类为 search 但不把 query 写进 target/args_summary,Codex tool call 同样没有),所以缺值时只显示真实工具名。② 原始事件不再自己拼 JSON——渲染已存储、已脱敏的 data(来自 workbench.runner.raw)。rev8 再更正:上一版把 runner_type 写成 codex-cli | claude-code 是错的——那两个是 runner ID,真实 runner_type 是事件子类型(形如 codex:… / claude:… / worker:…)。拿不到真实 fixture 时只写占位、不给枚举。
绑定逻辑有两处入口,先确认 adopt-native 那条是不是也走同一个分支。
◍WebSearch
runner_type: <事件子类型> data: <已存储、已脱敏的 data,原样渲染> (此处应贴真实脱敏 fixture。各 runner 的 raw 结构互不相同—— 例如 Codex 的数据在嵌套 item 里,没有顶层 tool/path—— 所以不在稿子里手写 JSON 冒充,也不给 runner_type 的枚举。)
G3-4file-diff 卡 / 代码块 时间线里只有这两种允许有边界。删行左缘用斜纹,色盲下也能与增行区分。rev7 更正:卡里是单次 Edit 的摘录,不是整文件 Git 统计——增删数按 result_excerpt 实际行数算(本例就是 +1/−1),没有 hunk header 就不补行号。Claude 的 args_summary 即使有也只是这一次 Edit 的 old/new 行数;Codex 没有 excerpt,降级成普通工具行,不画这张卡。
services/orchestrator/server.mjs本次 Edit 摘录+1−1
−if (!session.native_session_id) return requested;
+const locked = session.native_session_runner_id;
$ npm run cc:test:runner-default
exit code 0

G4 · 结果、产物与子 run

G4-1改动文件:原样 porcelain 状态 + 路径 删掉了每个文件后面的 +19 / −6——changed_files 里没有逐文件增删数。状态域远不止 M/A/D:porcelain 的 XY 还包括 R/C/U/?/! 与双列组合。rev9 更正一处越权断言:上一版写「worker 原样上报 XY、解析器已经支持」是不成立的——worker 在 worker.mjs:675 对行做了 .trim(),会丢前导列位;现有前端 apps/web/index.html:9964 只取 code.trim()[0],AM 会显示成 A;测试也只覆盖单字符。所以这张样张画的是 producer / parser 的新增能力:保留精确两列、渲染完整状态,并补 AM/MM/UU/?? 等测试。补完之前不得写「已经支持」。未知值中性降级为「Changed」。
改动文件 · 7
Mservices/orchestrator/server.mjs
Ascripts/cc-test-runner-default.mjs
Dscripts/legacy-runner-probe.mjs
Rdocs/78-engine-lock.md
Uapps/web/index.html
??design/mockups/wb-redesign/app.css
AMregistry/agents/dev-agent.yaml
diff_stat(原样)
 services/orchestrator/server.mjs      | 25 +++++++++++++------
 scripts/cc-test-runner-default.mjs   | 133 ++++++++++++++++++++
 scripts/legacy-runner-probe.mjs      |  41 ------------
 3 files changed, 158 insertions(+), 47 deletions(-)
改动文件
没有改动文件
改动文件
未上报
最后两行是同一个字段的两种情况:changed_files === null 只能写「未上报」,只有明确 [] 才能写「没有改动文件」。混成一句就是替后端下结论。
G4-2产物:只列当前 producer 真能产出的 非 http(s) 前缀一律不渲染 <a>。rev7 大改:上一版那组样例当前 producer 产不出来——tests_run 在 Mac Worker 里固定初始化为 [],没有任何 collector 会生成「13 passing」;自动产物最多 document 12 + HTML 3,凑不出「共 23 个」;GitHub PR、/tmp/last-message.txt、design 目录 HTML 预览也都没有当前 collector。所以:空 tests_run 整块隐藏,产物只留 artifact_refs producer 真实产出的形态,外链等能力列为后端新增项。
产物 · 2
▤docs/78-engine-lock.mddocument
▤report.htmlHTML
tests_run 这一块在样张里不存在,因为当前它恒为空数组—— 摆一个采集不到的样子,就是给下游一个做不出来的预期。要展示先补真实采集器。
公网链接 / 外链 / 上限 20 的截断行同理:等对应 producer 真的存在了再画。
G4-3派发记录:只列能与真实 Run 对上的那几条 rev4 删掉了「派出 N 个 · 完成 M 个」——Run 没有 parent_run_id / child_run_ids,Dispatched run… 消息不能证明归属,/tasks 默认 40 条也不能穷举。数不出来的数就不写。
这轮对话里出现过的派发记录
转圈在这里与会话行同义(还没结束),可以复用; 绿点/红点不能复用——它们在会话行是「未读」,搬到这里会变成两个意思。 所以终态的派发记录用文字徽章,不用点。 这一段只在对话里真出现过、且能与真实 Run 对上的派发记录时才渲染;对不上就整块不显示。
G4-4Markdown 全谱 · task-list · 表格 表格外套横滚容器——375px 下三列表会撑破卡片(上一版没做,是横向溢出的来源之一)。task-list 是文本符号不是可点控件:后端没有回写能力。

三处改动

服务端不再切换 runner,而是回正到 native_session_runner_id。

  • [✓]服务端回正逻辑
  • [ ]写进 docs
场景旧行为新行为
已绑会话 + 异引擎静默切换,记忆串回正 + runner 消息
新会话按请求按请求(不变)
换引擎 = 新记忆。

细节见 docs/78-engine-lock.md。

G5 · 输入区:实体容器

按用户参考图 ref-21:一个实体圆角容器,控件全部在容器内的底边一行, 容器上方没有任何分隔线(容器自身的边界就是分隔),发送与停止共用同一个圆形按钮位。 左侧 + 同时是上传文件入口,粘贴图片直接进附件区。 「@ 上下文 3」已整个删掉——产品里不存在这个功能。

G5-1空 / 有内容 / IME 组字中 / 发送中(停止同位) 第 4 个是发送中:同一个按钮位换成停止,不是旁边多一个按钮。IME 组字用虚下划线标出,避免回车误发。
也可以直接粘贴图片
把引擎锁扩到所有已绑yuansheng
G5-2附件生命周期 · 粘贴 / 拖入 · 三类超限 上传中是不定量往复条不是百分比——接口不返回进度。rev7 更正类型白名单:后端校验的是 MIME 不是扩展名,真实允许集是 PNG / JPEG / GIF / WebP / text/plain / text/markdown——上一版写的 png/jpg/txt/md/patch/log 既漏了 GIF 与 WebP,又不能保证 .patch / .log 会被浏览器识别成允许的 MIME。样例文件名也一并换成能确定落在允许集里的。
待上传.png screenshot.png big.txt notes.md
4/5
最多 5 个 · 单个 ≤2MB(big.txt 超了)· 只收图片(PNG / JPEG / GIF / WebP)与纯文本、Markdown(demo.mov 不收)
拖入 / 粘贴都可以
G5-3会话忙 / CLI 占用 · 命令面板 用户 #12:删掉「会话忙」标签——它不是可点击对象,忙时只让占位文字变。rev4 更正:接入的会话不是永久只读——不可编辑的是那份记录;只有 CLI 正占着的时候才禁用输入,且要写明「退出后恢复」;没被占用时 composer 照常可用。rev5 更正 /goal:它不是「改这条会话的目标」——Session 没有可修改的目标字段;实际行为是以该目标新建一条 GM 会话并发送首条消息,文案照实写。要改当前会话的目标,需另立后端能力。
此处仅示意静态外观,交互见原型页
/

G6 · 全局条与系统健康

Mac 心跳灯常驻在左栏页脚。它只代表 worker 心跳,不代表 agent 在线——系统里没有后者。

G6-1心跳灯五档 + 三条自我约束 浮层必须写死三句:这是 worker 心跳不是 agent 在线 / 判定窗口 45s 有滞后 / 没有「重启」按钮(后端没这个能力)。
此处仅示意静态外观,交互见原型页
Mac Worker

最后一次心跳 12 秒前,在在线窗口内。

  • 这是 worker 的心跳,不是 agent 在线状态——系统里没有后者。
  • 判定窗口 45 秒在线 / 300 秒离线,所以这盏灯有滞后。
  • 这里没有「重启」按钮:web 叫不醒 Mac,后端也没有这个能力。
G6-2掉线条三态 · CLI 占用 · 错误 banner · 新鲜度 6 态 掉线条贴 composer 上方,无按钮——web 叫不醒 Mac。超时后锁死自动重试,缓存必须标「上次成功快照,不是当前在线证据」。
没连上服务端。刚才那条没发出去,你打的字还在。
本地 CLI 正占着这条会话。它退出前,web 这边发的话不会被接走。
已经派出去了,Mac 正在领活…
Mac 刚刚失联(最后心跳 1 分 12 秒前)。现在发的话可能要等它回来才有人接。
Mac 离线超过 5 分钟。现在发的话会一直排着,直到那台 Mac 回来。
还没收到过这台 Mac 的心跳,状态不确定 —— 不是「离线」,是「不知道」。
刚刚同步 · 12 秒前
正在读本地会话记录…
比预期久了(已等 15 秒)。还在读,可以先做别的。
读了 60 秒还没回来,已停止自动重试。
上次成功快照(8 分钟前)· 不是当前在线证据
这份记录不完整;未显示的部分不等于没发生。

G7 · 左栏

rev2 改动:删掉注意力区四行、删掉 workspace 名、删掉 Agent 行的 ▾ 箭头、 AGENTS 从弱眉标升成可折叠的大目录、新对话入口移进 Agent 行的三点菜单。

G7-1大目录 + Agent 行 + 会话行(含缩进) 照 Notion 侧栏:AGENTS 是弱化小字,折叠语义由那枚很轻的箭头承担,不靠色块和边框做强。Agent 行没有箭头,缩进是唯一的父子信号,会话文字左缘精确对齐 Agent 名左缘(28px)。
6
2
9
G7-2三点菜单:Agent 行 / 会话行 rev6:agent 行的三点与计数占同一格——hover / 键盘聚焦时计数隐藏、三点显示,用 visibility 不用 display,所以 agent 名、缩进、右边界一个像素都不动。会话行同理,让位的是状态点。触屏没有 hover,≤640 改成两格并列常显。
以下三个菜单仅示意静态外观,交互见原型页
agent 行的三点 —— 只有「新建会话」这一个真实动作(POST /sessions)
会话行的三点 —— 该行没有转圈时
该行有转圈时 —— DELETE /sessions/:id 遇非终态 Run 固定回 409,所以禁用并写明原因
预禁用挡不住全部情况:它只看 active_run_id,而 on_busy=queue 会让指针漏报; 后端扫描全部 Runs 后仍可能回 409。所以确认框里必须有可见的失败反馈——
删不掉:这条会话还有没结束的 Run。它结束后再删。
治本做法是后端派生可靠的 has_non_terminal_run / deletable,前端据此禁用。
AGENTS 顶层标题没有三点——系统没有 Agent 创建 / 编辑 / 删除端点, 放了就是画一个不能用的按钮。那一行只放真实计数。
G7-3删除二次确认 措辞按真实语义改过:删除是软删除,「消息和 Run 会一起消失」是错的。H5 上确认键移到左侧,避开拇指落点。
此处仅示意静态外观,交互见原型页

删掉「wb-ui 组件库对齐」?

会话会从列表隐藏。消息与执行记录留作审计,目前没有恢复入口。

G8 · 空态、错误态、边界态

G8-1加载骨架 / 加载失败(错误必答三问) 发生了什么、下一步是什么,最关键的一句是「你刚才打的字还在,没丢」。rev5 更正:不写「不是你的网络」——502 只能证明收到了网关错误,证明不了根因,所以改写「根因尚未确认」;重试次数只报客户端实际计数,不写退避时刻表。

这条会话的历史没读出来

服务返回 502(网关错误),根因尚未确认。客户端已自动重试 3 次。你刚才打的字还在输入框里,没丢。

G8-2超长文字:非对话区截断,对话正文换行 非对话区——左栏会话名与 Agent 名、身份头 Session 名与 Path、原生会话 ID、接入 cwd、附件文件名、改动与产物路径、任务与子 run 摘要——一律单行 text-overflow: ellipsis(flex/grid 子项必须显式 min-width: 0)。对话正文不截断,长了换行。rev9 改方案(用户 2026-08-17 拍板 R-2 → 方案 B):上一版说「H5 完整值在详情浮层里出现」,但那个详情层根本不存在——会话名、Session 标题、附件名、路径都没有全文入口,触屏用户没有任何办法看到全文。现在:桌面 hover 出全文;H5 长按被截断的元素弹出完整值(可关闭、可复制、值自身换行)。完整原值一律存在 data-full 里,显示层才截断——原生会话 ID 之前直接写成 8f1c…,完整 ID 就再也取不回来了。仍禁止中间省略、direction: rtl 头部省略、动态放宽、跑马灯。
把引擎锁扩到所有已绑原生会话,检测到不一致时强制回正,并在会话头给用户一条看得见的提示,同时保证旧会话的记忆不被污染。
M/Users/linoyeung/Documents/codexworkspace/personal-workbench-wt/sess_256d7fa6bfc0/services/orchestrator/server.mjs
const locked = session.native_session_id ? session.native_session_runner_id : null; // 故意写很长一行,用来验证内滚而不是换行撑破卡片
确定性验收入口:下面两行在 240px 窄容器里,宽度已确保截断—— 桌面 hover 必出 title,H5 长按必出弹层。 没截断时不弹层是正确行为,所以验收要用这里,不要用不一定截断的行。
原生会话
8f1c2a4e-7b03-4d61-9f28-6ac5e1d0b937
建议的自动测试:长按后弹层保持、底层动作未触发—— 断言弹层仍可见,且被长按那一行没有变成选中态、其折叠状态未改变。 这一条正是 rev10 修掉的缺陷(兼容 click 把弹层当场关掉),需要回归守住。
H5 长按被截断的元素 → 弹出完整值(桌面走查时可用右键触发同一个弹层)
下面这块是静态外观;真的长按上面那两行会弹出文档级弹层
完整值
8f1c2a4e-7b03-4d61-9f28-6ac5e1d0b937
G8-3新会话无消息 / 原生对话无轮次 / 未选中会话 空态给下一步不给插画。三个空态措辞各不相同——「还没开始」和「读不到」是两件事。

还没说过话

这条会话绑的是 design-agent。之后每一句:存在有效的同-runner binding 时续接原来那条原生会话,否则新开一条。

这条本地会话还没有轮次

接进来了,但那边还没发生过对话。

从左边选一条会话

或者在某个 Agent 上开一条新的。

G9 · 其他面板

G9-1Agents 抽屉(无在线态)· Connectors · Settings Connector 措辞:「3 小时前用过」→「上次检查于 X」(无 last_used_at);「断开」→「从 Workbench 断开」并说明本地授权不变。rev7 大改:上一版列了 5 个 provider 并给它们都配了动作——超出真实 catalog。严格按 GET /connectors 渲染:当前唯一 connectable 的是飞书;GitHub / Notion / Google Drive 是 coming_soon,无动作(对它们调 connect/refresh/disconnect 会返回 400);Vercel / PostHog 不在 registry,已整行删除。
DV
dev-agent
默认引擎 codex-cli · 6 条会话
这里没有在线灯:agent 没有在线状态,只有那台 Mac 有心跳。默认引擎与权限一律从 registry 派生,不在稿子里写死。
飞
飞书
已连接 · 上次检查于 14:02
GH
GitHub
即将支持
No
Notion
即将支持
GD
Google Drive
即将支持
「即将支持」的三个不给任何动作按钮——现在点了只会拿到 400。「从 Workbench 断开」只解除本产品的连接,不会撤销你在对方那边给的授权。

逐条对照 design/wb-ui-redesign/scenario-inventory.md 第 2 节。 每一行要么指向原型某屏(P-*)/ 图鉴某条(G*-*),要么写明不做 + 为什么。 全表共 — 行,其中不做 — 条。 rev2 新增标记 ▲:本轮相对 rev1 被删除或改写的行,共 — 条。

rev5 收尾:设计侧只剩一条悬而未决,且它不是设计问题,是环境问题。 每条按角色约定给出:业务背景 → 各选项利弊与业务影响 → 我的推荐。

已答复,已落实,不再重列: rev1 的 Q1–Q7(用户批注 #15–#19 + Tech Lead 查证)· A-1 失败红点的横杠 → 去掉 · A-2 cancelled → 归红点,文字保持「已取消」(Tech Lead 确认成立)· A-3 composer → 不照搬深色,用阴影/边框/更实的底色做立体感 · A-4 「⇢ 接入本地会话」→ 不做 · B-1 会话子项加 icon → 不加(Session 没有 icon/avatar 字段)· B-2 AGENTS 行右侧 → 只放真实计数,不放 ⋯(当前没有 Agent 创建/编辑/删除端点)· C-1 未读缺口标注 → 在 G1 分区标题下加一句总说明,已落地 · C-2 Tech Lead 完整报告 → 已解决,剩余 3 条已逐字收到并在 rev5 修掉。

这一页是写进稿子的事实约束,不是设计说明。上一版正是因为没有这一页, 才在界面里编出了排队位置、等待时长、单步耗时这些系统给不出的数字。

session 与 run 的真实关系

rev7 重写:上一版这一节把它写成了绝对事实,那是错的。 「Session 就是一条连续原生 CLI 会话」「第一句必创建、之后每句必 resume、上下文完全连续」 三句都过度绝对。下面是条件事实版本。

  • Workbench Session 是消息与 Run 的容器,不等于一条原生 CLI 会话。 Session 初始没有 binding。
  • resume 是有条件的:Run 创建时必须同时满足存在 native ID、runner 相同、在 allowlist 内 三项,才会 --resume 续上;否则新开一条原生会话。 绑定失效会被清空;绑定窗口内排队的 Run 也可能被冻结为 fresh。 所以不能说「上下文完全连续」。
  • run.prompt 是 HTTP 请求内容经 .trim() 后的持久化值 (run.prompt === trim 后的 message.content),不是请求的逐字节副本。不拼历史。
  • 代码中写死禁令:--continue(会 fork 原生会话状态)与 --dangerously-skip-permissions 一律禁止。
  • 时长只有一个合法口径:当前这个回合跑了多久(started_at → completed_at,在跑时 started_at → now)。 稿子里所有时长都写成「本回合…」,不出现「整个 session 累计」这类表达。
调用形态示意(非真实 argv)

Run 创建时:有 native ID && 同 runner && 在 allowlist 内
  ├── 是 → claude --print --resume <id> <prompt>      续上同一条原生会话
  └── 否 → claude --print <prompt>                     新开一条原生会话

行尾那两个点是「未读」,不是「状态」

用户 2026-08-16 定案:「跑完之后是绿点,点击进去查看后绿点消失,同理红点。」 已读之后活儿依然在,只是不再提示你——所以稿子里任何地方都不把这两个点说成「这条会话的状态」。

总控代码级确认(services/orchestrator/server.mjs):系统已有完整的未读骨架,不需要新造字段—— session.gm_notified_at + session.gm_unread_kind(写入见 gmLoopNotifyUser,1213–1230 行)、 session.gm_read_at、POST /sessions/:id/read 清零、 sessionView 据此派生 gm_unread 布尔(905 行)。 前端切会话即 fire-and-forget 调 /read,「点进去就消失」这半边已经现成。

当前唯一缺口:gmLoopNotifyUser 目前只被 GM 自主循环调用, 普通会话的 run 进入终态时不会点亮未读。这需要后端在 run 终态处理里补一次点亮, 已记入需求,不在设计范围内。在补上之前,稿子里画的绿点/红点对普通会话 还不会真的出现——这一点在此写明,不越权断言后端已支持。

转圈是另一回事:它只表示 active_run_id 指针非空,与已读未读无关。 注意指针为空不等于没有在途 Run(见上方 G1 分区的说明)。 取值顺序:active_run_id 指针非空就显转圈;否则检查未读;都没有则空槽。

14 类虚构表达 · 本轮的换法

rev1 的写法为什么不成立rev2 改成

Tech Lead 在 rev3 上查出的 12 条事实阻断项

rev4 修掉前 9 条;末尾 3 条(标 (rev5))是 rev4 交付时 Tech Lead 报告不在本工作树、只拿到前 9 条造成的漏网,rev5 逐条补掉。

rev3 的写法为什么不成立rev4 改成

Tech Lead 第二次复审在 rev6 上查出的 21 条

其中第 5、6、9、12 条是我在 rev4/rev5 声明已改、实际只改了主路径的—— 同一串字还留在场景说明、README 与 DESIGN 里。 本轮改完每条都做了全文件搜索验证,不再只看 index.html。

rev6 的写法为什么不成立rev7 改成

Tech Lead 第三次复审:21 项中 11 项通过,8 个阻断级

其中 #1 / #3 / #5 是同一个根因:rev7 补上了中性点,但旧的 「三种运行状态视觉」「四态」定义还留在 CSS 注释、图鉴文案、覆盖表和 DESIGN 里,新旧并存。 rev8 把槽位统一表述为五种条件,并在五个文件里清掉了全部旧说法。

rev7 的写法为什么不成立rev8 改成

Tech Lead 第四次复审:5 个阻断级

本轮同时并入用户拍板的 R-2 → 方案 B:上一版把 H5 的全文入口指向「详情浮层」, 而那个浮层并不存在——触屏用户当时没有任何办法看到被截断的完整值。 现在改成长按弹出,且完整原值一律存在 data-full 里。

rev8 的写法为什么不成立rev9 改成

Tech Lead 第五次复审:8 条(前 3 条是功能不工作)

#6 / #7 / #8 不是措辞问题——它们让本轮的核心交付「长按看全文」实际是个空壳: 长按弹出来立刻被兼容 click 关掉、桌面 hover 因为隐藏时尺寸为 0 而根本不存在、 复制失败还报「已复制」。三条都已修。

rev9b 的状态为什么不成立rev10 改成

移交实现的约束

由 Tech Lead 提出、本轮记录在案。本稿不实现这些,但产品实现必须满足。 前两条是为了消掉 data-full 重复存储带来的漂移风险—— 稿子里 62 处 data-full 与可见文本是两份人工维护的副本, 产品里不许这样做。

  1. canonical value 只能有一个数据源。
  2. 同一渲染路径同时生成可见文本与 data-full, 禁止人工维护两份。
  3. 自动测试验证 hover、长按、复制三者的结果均等于原始字段。
  4. porcelain 测试补 " M path"、" D path" 两个用例,守住前导列位(那正是 .trim() 会吃掉的东西)。
  5. Path 取 session.project_root || "—", 不与 native_cwd 混用——两者是不同的东西。

两条补充硬规则

  • changed_files 为 null 只能写「未上报」;只有明确 [] 才能写「没有改动文件」。见 G4-1。
  • tests_run 只能原样呈现真实上报,不得自行总结成「13/13 绿」。见 G4-2。

本轮范围外

  • 任务中心不做,保持现状不动。稿子里一切「去任务中心…」的跳转与验收动作已删除。
  • 「等你拍板」整屏删除:当前没有任何正常路径产生 waiting_for_approval,也没有同一 Run 的放行通道,普通消息只会新建另一条 Run。
  • 不做「自动弹出最终结果」;派发记录也不做聚合计数——Run 没有 parent_run_id / child_run_ids,只逐条列出能由真实 dispatch marker 的 Run ID 对上 TaskView 的那几条。
完整值