这一版有两条线。一条是窗口本身:那条什么读数都往里塞的运行带拆成了面板,窗口不再从一张白纸开始,设置变成盖在工作区上的一张纸而不是第二个全屏窗口,而侧栏里列出的一处改动现在能给出它背后的 diff。另一条是几处「界面报的不是真在跑的那件事」——沙箱面板报的是配置文件里的词而不是生效的那一档,停在提问上的一轮把问题从屏幕上擦掉了,输入框下面那排控件把自己的标签拆成了两行。自 2.8.0 起 13 个提交。
新增
运行带拆成面板
RunStrip是一条带子,每一项读数都往里挤。它拆成 Overview、Runtime、Cache、Cost、Context、Agents、Files、Jobs 八块,derive.ts在面板里把那趟遍历只走一次,kit.tsx收着它们共用的形状——于是一项读数变长的时候,不必把每一项都得住的那一行撑宽。- 同一批里:Sky 画的是主题包要求的背景而不是摆一张图;Nav 接手图标栏;Timeline 和 Task 分别给轨迹和一个子代理自己的视图;AccountRow 管账号;prefs/keys/trailing 收起各个面板此前各自私藏的状态、按键绑定和尾巴。
窗口在自己身上打开
- 窗口此前开在一张白纸上。
index.html现在在它本来要等的那份 bundle 存在之前就先画出开场:字标一个字母一个字母以金写出、落定为墨,主题包画的天光,以及一行说正在接内核。main.tsx在开场演完之后才把窗口交出去,而不是数着一个固定的延时——所以内核快的时候不会闪一下,慢的时候这段开场正是为它准备的。它自带一套配色,因为tokens.css是打包进来的,在那个时刻还不存在。 - 设置变成盖在工作区上的一张纸,不再是第二个全屏窗口。这去掉了一处重复:
.prefs-hd此前也是一条标题栏,有自己的拖拽区、自己的红绿灯内缩和自己的窗口按钮。
列出的改动能给出它背后的 diff
- 侧栏一直在列这棵树差在哪儿,而它此前只能说出一个路径和一个 porcelain 字母。文件到底差了什么只活在写它那一轮的工具事件里,于是这次会话之前做的改动——或者某条没有任何事件描述的 shell 命令做的改动——只能被点名,不能被读。
gitstatus.Diff回答单个路径。它对着 HEAD 量,所以一处暂存的编辑和一处未暂存的编辑,读起来就是它们对人而言的那一处改动。未跟踪的文件在 HEAD 里没有可比的东西,改成对空设备做 diff:那是让 git 打印出它、又不必先写 index 的唯一办法,而一次读取不该写 index。- 路径是客户端送来的,所以
safeRel把三种形状挡在 git 之外。
cloudsea-gilt 成为内置主题包
- Studio 的设计是照着这套配色、这片不用图片的天光和这组光带/云的色调画的。现在它由内核直接供给,而不是要用户先装一个包才能看到这个应用被设计成的样子。
远程主机的 bootstrap 收成一条路径
- 探测、安装、启动、以及 Windows 那个 shell 的特例,收进一个 bootstrap 之下;配上加固测试和纯函数测试,让它不必真有一台机器在另一头也能被跑;hub 和 CLI 两个入口都接到它。
一台机器可以有多个项目(#9459)
- 底下每一层其实早就支持了:attach 池按 workspace 给 space 建索引,转发也是按 workspace 命名的;远端内核记得每一个被打开过的文件夹,
/tree会把它们全都答回来。只有主机簿说得出一个目录,于是没有任何入口能说出第二个——而在冷启动、还没有面板可以借道去问的时候,侧栏就只剩那一行。 RemoteHostEntry在workspace旁边多了workspaces,由WorkspaceList折成一份默认在前的列表:只写过旧字段的那一行是长度为一的列表,而不是每个读者都要重写一遍的特例;一次 upsert 会把列表的头写成默认,也就是 CLI 仍然连过去的那一个。- 侧栏把主机簿和远端的 tree 一起画:在这边加了、还没在那边打开过的文件夹留着,在那边打开过、这边没记过的也会出现。
修复
- 沙箱面板报的是真在跑的那一档,不是配置文件里的词(#9454)。
SandboxSettings在effectiveWriteRoots旁边多带一个effectiveBash,于是面板画的是约束器将要做的事:一个没写的值在除 Windows 之外的每台机器上都是 enforce,而 Windows 无论文件怎么写都不受限地跑。这台机器跑不了的那一档现在看得出来是死的,并且自己带着原因——只有一个disabled的时候,它保留着活控件的 hover 亮度,然后把点击吞掉,那正是被报成「坏了」的东西。在没有后端的机器上存 enforce,现在按代码拒绝而不是按一句手写的话。 - 一个运行正停在上面的问题,挺得过转录重建。内核会把挂起的提问/审批重放给每一个接上来的客户端,而窗口随后用
/history重建的内容覆盖了自己的条目——而 ask 这次工具调用在转录里是被故意画成空的,因为携带 id 的是ask_request。于是面板停在「等你决定」,屏幕上却没有任何可以决定的东西,排在它后面的那一轮永远发不出去。一个提问不属于记录,它是运行停下来在等一个回答,所以它跟着重建一起过去。同时提问按内核给的 id 认领:重放来的那一帧是同一张卡片而不是第二张可答的,已经答过的那张也不会被帧日志重新打开。 - 输入框下面那排控件不再拆断自己的标签(#9455)。这排 pill 是普通的 flex 子项,于是一行只差一点点放不下的时候,它压缩它们而不是换行——而中文任意两字之间都可断行,「计划」就此断成两行,把整排的基线一起带歪。现在一颗 pill 要么是它标签的宽度、要么就不存在:这排改为换行(
.row本来就允许),发送按钮的自动外边距让它始终待在所在那行的右端。从 980px 一路核到 320px:一行、两行、再三行,每一档都没有任何东西被拆断。模型那颗是这排里唯一没有自己宽度的控件——中转站可以发布比整排还长的 id——所以它先让出字符,整排才让出一行,被缩掉的完整 ref 留在悬停提示里。 - 断掉的推理流被重放而不是丢掉。OpenAI 与 Anthropic 两侧的
reasoning_replay、h2 reset 那个情形、stream_failure以及围着它们的重试路径;doctor 会报出供应商实际做了什么,于是一条坏掉的流是可诊断的,而不只是慢。 - 一次拒绝会说明自己是什么。
errmsg和那条 refusal-hint 测试、instruction 的 resolver,以及这两者共同读的那几张 i18n 消息表。 - 连接提示的遮罩只有模糊、没有压暗。它引用的
--bg从来没有存在过;CSS 遇到一个解析不出来的var()会把整条声明丢掉,属性于是回落到继承来的值,而那读起来像是设计。用量面板的--fg是同一回事,它的文字颜色从来没落地过。
内部
- perf 多了两道守卫,各自对应一种在其它门全绿时保持沉默的失败。
tokens.mjs:一个没有回退值的var(--x)必须指向某处真的有人赋值的东西;两份清单都从源码里读——样式表自己的--x:,以及 JSX 写进style的那些逐元素变量——因为手抄的清单会烂。shipped.mjs:dist必须比src下的一切都新;一次构建可以成功、打出对勾、然后编译的是别的东西。 - perf 另外多了三个脚本:
selfcheck往每一道守卫里塞一个假的断言,证明这道守卫还能变红;sourcemap把一个压缩过的栈帧翻回源码位置;switchprof采样打开一个长会话的开销。look和verify补上了各自缺的断言。 - wire-parity 现在也盯着
port/boundary.ts:wireMirror带上它对应的 TS 文件,control.SandboxSettings与桌面那份手抄的副本被声明成一对。这是一份画安全边界的类型,它读不到的字段就是一种它会画错的姿态。 - 一处符号链接用例改成问文件系统,而不是问操作系统的名字。它此前问
runtime.GOOS,而且是在断言resp.OK之后才问——于是一台建不了符号链接的机器,是在工具正确报出的那次拒绝上失败,而不是跳过。Windows 只在开发者模式或提权下允许符号链接,平台名分不出这跟一个普通账户的区别;现在用的是这份文件里另外三个用例早就在用的读法:试一下,按文件系统给回来的错误跳过。一台建得了的 Windows 机器现在会真的跑它,而不是被跳过。 [remote]那一段的渲染搬进render_remote.go。这一段是一件事,而它原先所在的那个函数已经是这个包里最长的一个——顺带把它欠下的一部分预算还了回去。
macOS 已用 Developer ID 签名并公证,打开时不需要清除隔离属性。Windows 包未签名 —— Authenticode 签名绑定在受保护的 main-v2 上,本分支拿不到它,所以 SmartScreen 会告警:选「更多信息」,再选「仍要运行」。Linux 从 .deb 安装。
三个平台都能自更新:Linux 走自己的包,Windows 运行下一个安装器,macOS 替换自身 bundle。