**A hand-declared route must not offer a reasoning effort.** The earlier commit read the create card's missing control as drift and added one. It is the other way round: such a model has no reasoning capability — pi-ai's installed catalog is what supplies one, and it ships nothing under the route — so `resolveModel` throws UNSUPPORTED_REASONING_EFFORT for every model on it and the whole provider drops out of the picker. Verified against the adapter, not inferred. The create card no longer offers it and the editor withholds it on the directory's `declared` bit, which is the real bug: that control has always been wrong for these routes. **A blocked composer locked the way out of the block.** Reusing the no-workspace inert posture disabled the model seat along with everything else, so the bar asked for a model while preventing the one control that picks one. A block now rides its own `blocked` owner prop: the textarea, send, commands, plan seat, and access chip all lock, and the model seat alone stays live. **A Provider ID could derive an illegal credential reference.** The card accepted a digit-leading id, whose derived `123_API_KEY` then failed at the credential seam with a raw regular expression the user cannot act on. The id must now start with a letter, and a test pins the relation between the two rules rather than the regex.
30 KiB
@deepseek-ai/dsh-client-ui-conversation
English | 中文
会话领域:骨架(标题栏/标签页/编辑器/空状态)、聊天视图(分组步骤摘要流、流式尾部隔离、带从左到右动态渐变的 Deep diving... 轮次状态、逐工具行 slot 及一个 bash 示例注册方与 todo 行)、编辑器 dock(与输入区一同 sticky 的会话统计行)、输入区 dock(带发丝分界线的队列行加 todo 计划条)、最小详情面板、按 scope 寻址的 ConversationService。契约:api-contracts v3 §7 加 slot 终端设计(store seat/props share)。
压缩(compaction)在检查点自身的消息流位置渲染为一行折叠标记,不替换其上方的 transcript(文本记录)。展开内容来自检查点溯源的 compact/summary;该事件位于已加载窗口之外时,标记仍然可见但不可展开。面向模型的带框检查点载荷绝不渲染。
常驻会话壳会跨无会话与会话状态切换而保留。没有当前会话时,它会渲染禁用输入栏;其根作用域的 conversation.hero.workspace slot 承载 Workspace 选择器。选择 Workspace 会连接或复用由 Host 拥有的空白会话,并在不替换会话壳的情况下打开该会话。根组件始终拥有同一个滚动容器与 Hero/编辑器子树;首个会话到达时,彼此独立的严格会话页头和主体 outlet 只填入各自区域,因此 Workspace 选择器、滚动主体、编辑器 seat 与 textarea 都保留原有 React 和 DOM identity。空白会话与活跃会话渲染相同的输入区主体;InputHub 则在 Workspace 切换间携带草稿,并将草稿镜像到会话 store。活跃阶段,会话标题栏作为普通列 chrome,仅显示当前会话标题和视图标签;fork 谱系仍保留为会话数据,不投影到标题栏。其下滚动容器(data-conversation-scroll)承载流动排版的各视图与 sticky 编辑器栈(统计 dock+输入区 dock+输入栏)。该滚动容器无条件预留自己的滚动条槽,选用编辑器 overlay 的视图也仍把它保留为滚动容器,因此无论对话记录是否滚动、无论展示哪个视图标签,输入卡片都保持同一个横向位置(决策)。textarea 上的滚轮会链式处理:限高草稿先在本地滚动,到达边缘后再转交给该宿主。
别的插件可以经 ctx.conversation.blocks 让某个会话的编辑器变为惰性:它设置一个携带自己本地化理由的 block,输入栏就渲染同一个禁用的 textarea,并把该理由作为 placeholder——复用无 Workspace 时的那套姿态。推送方向是约束而非偏好:知道某会话发不出消息的插件(ui-model,在没有适配器服务其路由时)本就依赖本包,因此本包读不到它们。模型 seat 是 block 唯一保留可用的控件——这份契约里的每个 block 都靠选模型来解除,把它一起锁上会让编辑器索要它自己拦下的那件事。block 只是提示性设计;无论客户端禁用了什么,宿主都会拒绝一个它路由不了的 prompt。两者同时成立时以无 Workspace 姿态为准,因为选 Workspace 是更靠前的前提。
视图环是一个 slot:严格会话主体注册在 children 表中声明 Session scope 的 'conversation.view' 列表,并通过自身的 renderSlot share 渲染活跃配置项(only: <active id>);视图标签页则从注册选项(id/order/label)投影而来。聊天视图是该包自身的配置项;ui-trajectory 等插件通过 ctx.slots.register 贡献标签页,每个视图负责自己的 chrome。
会话页头会在标题旁声明并渲染 Session scope 的 'conversation.session.header.actions' 列表,使功能插件无需进入骨架即可贡献控件。编辑器链的 currency 包含当前对话 session;ui-subagent 会选取 one-shot 或 parent 不可用的已寻址会话,并按原因显示只读文案,而普通 InputBar 会让所有已寻址 child 仅保留 Send,因为继续执行服务不公开逐 Activation 取消操作,session.cancel 也会绕过其所有权。
已记录的非用户消息渲染为默认折叠的展开项,标题栏先给出运行时为该消息投影出的角色——注入为 上下文注入,召回为 跨会话召回——其后是该投影从持久来源读出的生产者名称,因此读者无需展开即可区分 skill(技能)目录、工作区指令文件与被召回的会话。来源未提供生产者名称时只显示角色。标题栏通过包内部的 DisclosureRow 与 ToolRow 共享 Tool calls 的几何与交互,同时保留上下文语义:展开内容区的高度会随内容自适应,最大为 141px,超出后滚动,且不会合成工具状态、摘要或键控 toolview 分发(展开项决策、来源决策)。该内容区按生产方在持久来源上声明的形态渲染:instructions 在正文之上列出它对账过的文件,catalog 列出来源记录的条目而非面向模型的散文,其余取值——未声明、本版本不认识、或字段不可用——一律渲染 opaque 内容区,即按真实换行展示面向模型的文本,并把剩余来源信息列成字段。opaque 不是兜底剩余物而是有文档的默认:恢复的、fork 的、外部写入的日志,无论其生产方是否挂载在此处,都必须渲染得出来。持久或待处理的 steering(中途引导)气泡上方带有 插话 / Interjection 标注,这是把中途插话与共用同一气泡的开轮提示区分开的唯一标识。
Think 行默认保持折叠,并在不展开思维链的情况下暴露实时推理(reasoning)吞吐:当推理块是流式输出尾部时,摘要从结算后的首行切换到最新的非空行,其单行滚动区会随每个 delta 追到行内末端。展开该行会移除移动摘要,让完整推理进入普通页面流,因此页面阅读不会与内部跟随器争夺滚动;结算后恢复左对齐的稳定首行摘要(决策)。
通用工具行把内置的 bash、read、search、write、edit 和 run_code 名称归入专用视觉变体。文件系统变体会渲染 edit 图标和路径摘要;该路径是悬停下划线链接,点击后通过宿主操作系统的默认应用打开文件(host.openPath,相对路径相对会话 cwd 解析)。工具行不再是整行点击目标,也不会打开 details 面板。code 变体以模型撰写的 description 作摘要,展开后显示程序本身;其已记录的子调用经由同一个键控 toolview 空位渲染为始终可见的嵌套行(自定义注册和 GenericToolCard fallback 原样适用于子行)。Cordis 生命周期工具复用这些通用变体,同时以统一的 Cordis 强调色呈现 Inspect、Mount temporary Plugin 和 Unmount temporary Plugin;mount 行保留 code 变体的可展开源码渲染。
声明 terminal 渲染意图的工具调用,会在两个对话渲染点上都通过 ui-primitives 的 TerminalBlock 内联渲染其命令输出。contract/terminal-card-model.ts 是从快照的 callView/resultView 对推导的唯一位置,因此两个渲染点不可能在命令、cwd 或退出状态上产生分歧;对任何其他 card 标签——包括当前客户端版本不认识的标签——它返回 null,落回通用路径。因此两个渲染点也都显示卡片的运行状态点,它与工具行行首图标承载同一套 StateDot 语义,所以一行与其自身的卡片对同一条命令的状态总是一致。多行命令的每一行各占一个提示行,状态点只在第一行为整次调用标记一次——退出状态属于整次调用,因此每行一枚就会声称一个 bash 并不报告的逐行结果。键控的 BashRow 把卡片放在摘要行下方;工具行是摘要 surface,因此卡片的复制与展开控件是该行唯一的交互。渲染点兜底行则保持其既有的展开控件。行的上限是 CHAT_TERMINAL_MAX_LINES(8),面板为 16,因此摘要保持有界;面板仍是单次调用的阅读 surface。内联输出按渲染意图开放——终端卡片与 web 卡片各有自己的上限。若 Bash 执行失败时落在通用路径,则改用同样有界的 IN/OUT 展开区暴露原始参数和完整错误;后台启动确认等成功的通用结果仍只显示摘要(决策)。
声明 web 渲染意图的工具调用,会在两个对话渲染点上都通过 ui-primitives 的 WebBlock 内联渲染其 web 检索。contract/web-card-model.ts 是从快照的 resultView 推导的唯一位置,镜像终端卡片,因此两个渲染点不可能对一次 web 调用的显示产生分歧;对运行中的调用、非 web 的 result view、generic result view、本客户端版本不认识的 card 标签,或本客户端版本不认识 kind 的 web 卡片(更新的 host 发来的值,wire 上不可信其为 search 或 fetch),它返回 null,落回通用路径。键控的 WebRow 把一个组件注册在 web_search 与 web_fetch 两个键下,仅根据工具名判别以选取图标与标题;它组合共享的 ToolRow,把卡片作为 ToolRow 的 web body 传入,因此检索成为该行默认折叠的展开卡片(与每个卡片行相同的统一展开交互)。没有自己键控行的 web 声明工具落到 GenericToolCard 兜底,它以同样方式经 ToolRow 渲染卡片,详情面板渲染它,并在卡片下方渲染摊平的模型可见结果内容——fetch 正文只在此处可读,因为其卡片只携带 URL 和状态。两个渲染点显示同一份完整来源列表——工具返回、模型看到的那一份——仅受卡片自身滚动容器的高度约束,不存在行与面板的两级上限(决策、来源滚动)。
声明 read 渲染意图的 read 调用,会在两个对话渲染点上都通过 ui-primitives 的 ReadBlock 内联渲染返回的文件窗口——工具投影出的带行号、语法高亮的内容。contract/read-card-model.ts 是从快照的 resultView 推导的唯一位置;read 卡片是仅结果侧的(调用在 execute 返回前不携带文件内容),所以运行中的 read 只显示摘要,且对非 read 的 result view 或本客户端版本不认识的 card 标签返回 null,落回通用路径。键控的 ReadRow 组合共享的 ToolRow,把卡片作为 ToolRow 的 read body 传入,因此它是该行默认折叠的展开卡片;摘要仍是一个经 host 打开文件的路径链接。渲染点兜底行与详情面板同样感知 read。行的上限是 CHAT_READ_MAX_LINES(8),面板为 16(决策)。
声明 diff 渲染意图的工具调用(write/edit 工具),通过 ui-primitives 的 DiffBlock 内联渲染其已应用的改动,采用同一套四层结构。contract/diff-card-model.ts 是从 callView/resultView 对推导的唯一位置;已结算 result 的 hunk 替换 call 时 diff,对任何其他 card 标签或 generic result view(write/edit 的执行错误)它返回 null,落回通用路径。键控的 FileMutationRow(在 write 与 edit 下都注册)组合共享的 ToolRow,把 diff 作为 ToolRow 的 diff body 传入,因此它是该行默认折叠的展开卡片;摘要路径链接仍经 host 打开文件,而出错的改动(没有 diff 卡片)经 ToolRow 的 Output 区呈现其错误文本,首行进入折叠摘要。渲染点兜底行与详情面板同样感知 diff。行的上限是 CHAT_DIFF_MAX_LINES(8),面板为 16(决策)。
聊天流会将跨重试轮次连续出现的模型重试节点投影为一个稳定的弱化状态行,并用最新一次尝试更新该行;每个重试事件仍保留在运行时快照与会话日志中。前端倒计时以客户端收到事件的时刻为计划延迟的起点,避免 Host 与浏览器的时钟偏差;剩余时间向上取整到秒,且下限为 1 秒。最近一次尚未完成的重试会显示从左到右的文字渐变动画。后续轮次事实用于区分已开始的尝试与在退避期间取消的尝试,Host 的 running 位只控制实时动画;随后该行会显示静态的已完成或已取消标签。normal 策略行显示有限重试上限;always 策略行显示 ∞。激活该行会显示最近一次重试的精确延迟和失败消息。客户端运行时会在相应重试节点到达前移除每个失败步骤的流式输出尾部;后续某次尝试成功后,该状态仍保持可见。未进入重试的终态失败会在其轮次边界渲染为持久的内联状态,展示适合显示的持久消息与可选错误码,但不会提供 Host 无法兑现的操作;AUTH 文案绝不会回显提供方给出的凭据片段。
声明 search 渲染意图的 grep/glob 调用,会在同样的渲染点上通过 ui-primitives 的 SearchBlock 内联渲染其结果——grep 的匹配按文件分组(每个是一个可折叠的头,下辖 lineNumber: line 行),glob 是扁平路径列表。contract/search-card-model.ts 是从快照的 resultView 推导的唯一位置;与终端卡片不同,它不读 callView,因为搜索在 execute 前没有匹配或路径,所以运行中的搜索只显示摘要。对任何非搜索的结果视图、当前客户端版本无法编译的 card 或 kind、以及(因为这些都与不可信的 wire 帧同行)一个 files/paths 格式错误的已知 kind,它都返回 null,落回通用路径。键控的 SearchRow 因推导出的 kind 决定形态而同时注册在 grep 与 glob 下,组合共享的 ToolRow,把卡片作为 ToolRow 的 search body 传入,因此它是该行默认折叠的展开卡片;渲染点兜底行以同样方式渲染它。两者上限都是 CHAT_SEARCH_MAX_LINES(8),面板为 16。被截断的搜索会从卡片里丢掉一些行,但通往其余部分的定位符——grep/glob 的 Full … stored at … 脚注——只存在于结果文本里,因此推导在(且仅在)结果被截断时把它作为恢复脚注画在卡片下方;一个完全没有卡片的已结算调用(出错的搜索、嵌套 run_code 子派发、旧日志的 generic 结果)则经 ToolRow 的 Output 区呈现其压平后的结果文本,从而不让任何内容丢失在一个光秃秃的摘要之后(决策)。
工具行使用键控、Session scope 的 'conversation.chat.toolview' slot;其渲染点通过 entryKey: toolName 分发,并以 GenericToolCard 作为调用点 fallback。owner 载荷是统一的 ToolRowOwnerProps(callId/toolName/block/openFile),ToolRowProps 将其与 Session 标准工具包组合。注册方是只依赖 slot 服务的普通插件:ctx.slots.inject('conversation.chat.toolview', () => ctx.slots.register({ name: 'conversation.chat.toolview', key: '<tool>', inject? }, Row))。声明本身就是激活与重载依赖;只有调用 ConversationService 操作的注册项才需要该服务。Trajectory 与 waterfall(瀑布式事件)工具视图 slot 共享此形状并使用各自的渲染点;RendersCheck 会拒绝没有任何渲染方的声明。
审批经由本包声明的链接管编辑器:ApprovalPanel 注册为按选择器路由的 'conversation.composer' 配置项(ui-question 模式),在审批等待未决期间取代 InputBar 占据编辑器(琥珀色条、理由标题、来自运行中调用参数的配对命令行、一次性的拒绝/允许)。contract/slots.ts 中的 PendingApproval 领域面在运行时 PendingWait 载体之上拥有 wire 编码——带审计关联的 ApprovalResponsePayload 值;广播的 approval/resolved 帧使等待落定并恢复编辑器。运行时 manager 会将所有审批或问题等待通过 SessionSummary.pendingInteraction 投影出来,未实例化的 Session 也不例外;ui-workspace 负责其侧边栏呈现。未决等待完全离开消息流:问题(ui-question)与审批(ApprovalPanel)都经编辑器接管作答,不再保留只读占位卡。编辑器底行的 Access 席位挂载 PermissionSelect,由 host 计算的 permissions 投影经标准工具包 useProjection 供数(key 缺席即隐藏 chip);chip 打开 Menu 原语下拉,其中 kebab-case 预设名渲染为 Title Case 标签;普通安全预设会立即经输入栏注入的 command 回调提交 /permission <preset>,而 danger-full-access 在界面中显示为 Full access,选择后先打开页面内的 Modal 风险确认。用户勾选确认项前启用按钮始终不可用;取消、Escape、关闭按钮与点击遮罩都不会提交命令。
todo 两个面就是在该形状上的两个注册项,都使用 slot 声明注入,不依赖 ConversationService。TodoRow 占用 'conversation.chat.toolview' 的 todo_write key,摘要该次调用「试图写入」的内容(从其 args 经 toolviews/plan-summary.ts 的 planSummary 解析出 <已完成>/<总数> 已完成 · <进行中条目>,以及「其余活跃项的数量」+<n>;模型 JSON 残缺或形状不对时回落到通用摘要;非 ok 执行状态保留通用状态点,使被取消的调用绝不读成一次已完成的更新)。部署允许并行工作时,可以有多个条目同时处于 in_progress,因此 planSummary 给出第一个活跃条目并计数其余,且刻意不把两者拼成一个字符串:行会对摘要文本做省略号截断,把数量接在任务名末尾时,窄行最先裁掉的正是这个数量。该行把数量交给 ToolRow 的 summarySuffix——共享行在被截断文本旁的不收缩位(出错的行会丢弃它,因为其折叠摘要是失败首行)。TodoDock 以 order: 0 占用 'conversation.input.dock' 列表 slot(位于 Goal 与 Queue 之前),是计划条:它经 useProjection 读取 host 计算的 todos 投影(站立计划:其后没有更晚 turn/start 的最近一次 todo/write)并渲染 TodoPanel,后者接收纯列表,在列表为空时自我隐藏;列表非空时面板初始折叠,表头显示标题加它自行计算的、以 · 连接的各状态计数(本地化,形如 1 已完成 · 2 进行中 · 1 待处理,计数为零的段落省略;状态图标为 figma 的勾选/进行中/虚线未开始一组),因此它无需一个可被截断的任务名即可报告并行数量。选取由 dock 适配器负责,因此面板保持为其 props 的纯函数;站立列表放在此处而非行内,行才能保持单行。输入区 composer 链隐藏的一切(例如 ui-question 对 conversation.composer 的接管)也会隐藏整个 dock,包括这条计划条。
QueueDock 是 order: 20 的末端 input-dock 条目。队列为空时隐藏;只有一个待处理项时直接渲染该行;存在两个或更多待处理项时,默认收起为 "<n> 条排队消息" 表头,其按钮可展开或收起完整列表。表头暴露 aria-expanded 和 aria-controls;展开后的列表以 180px 为高度上限,并可滚动。存在进行中的编辑或变更时,列表行会保持可见;队列清空后,下一次出现队列时会恢复默认收起状态。普通会话中的每条可见行仍是单行预览,并提供针对精确单次入队项的编辑、删除和严格 steering 操作;已寻址 subagent 则保留只读行,因为其继续执行传输不提供 Queue 变更。如果严格 steering 输给已关闭的窗口,原单次入队项会留在 Queue 中正常投递;如果驱动器已经认领该项,正常投递就已开始。这两种已收敛的竞态都不显示失败,传输和未知错误仍会显示。
Host 带 placement 的 session/queue 快照也会携带待处理 steering。QueueDock 会将其过滤掉,ChatView 则把它投影为会话流末尾带复制操作的用户样式气泡;非用户来源的 next-step 项(注入上下文)改以 context placement 广播,领取前不在任何界面渲染。与所有用户样式气泡一样,这里不显示 fork。Host 会等携带该 steering 的持久 user/message 进入 mux 流之后再退役 steering。客户端运行时接纳该实时事件时,会在发布快照前退役第一个匹配的当前 steering 单次入队项;历史事件无法隐藏后来复用同一 MessageId 的单次入队项。气泡交接时因而不会产生空档或重复,会立即从持久节点恢复复制操作与时钟——steering 气泡与 user 气泡一样不带分支操作(决策)——并能在重连后从同一权威恢复。
键盘消息提交会根据所寻址会话的运行状态和 steering 能力解析投递方式。空闲时,Enter 和 Cmd/Ctrl+Enter 都执行普通 Queue 发送。主会话运行期间,浏览器持久化的 General Settings 偏好会把普通 Enter 分配为 Queue(默认值)或 Steer,Cmd/Ctrl+Enter 则执行另一种行为;Shift+Enter 仍然换行。已寻址 subagent 即使正在运行,也会让这两个手势都使用其仅支持 Queue 的继续执行传输。该偏好只影响支持 steering 的繁忙态手势对,发送按钮与非键盘提交操作仍使用 Queue。Composer Steer 复用现有尽力而为的 session.prompt(mode: 'steer') 契约:如果当前 next-step 窗口在接纳前关闭,AgentLoop 会把消息接纳为下一条唤醒 Queue 轮次,不显示失败,也不会丢失草稿事务。
逐 Session UI 状态中的选择与活跃视图位于已声明的聊天 store(stores.ts createChatStore)中;InputHub 拥有输入区状态机,并将草稿镜像到该 store 以便持久化。apply 将同一个 store handle 传给严格限定于会话的子树、聊天视图和详情注册,因此每个会话内共享一个实例,框架拥有其生命周期。组件保持纯粹:框架标准工具包提供 useSession/sessionId、全局 useSessions/useWorkspaces,以及输入状态机的 useInput/inputActions;store 表层与 inject factory 提供其余状态和回调。
输入栏为 'conversation.input.plan'(位于本地 access 模式控件右侧)和 'conversation.input.model'(渲染在 pending 指示器与发送/停止按钮之前)声明会话作用域的单实例 seat,并为 overlay、dock、left 和 right 输入扩展声明列表 slot。各功能包拥有相应控件及其状态;ui-conversation 提供放置位置、locked owner prop 和标准 slot share。前置加号按钮是 Command launcher,而非附件入口:它要求当前会话的 SlashController 基于 textarea 当前 selection,只打开 / trigger 的 command source,同时 ui-slash 既有的 MenuView 仍是唯一的浮层菜单与 pick 路径。不引入 File 行、file input、上传协议或第二套菜单组件。当 plan 投影的有效目标为 plan mode 时,InputBar 将文本框 placeholder 切换为 plan 任务措辞,经本包注册的 conversation locale 命名空间(placeholder.plan / hint.plan 键)本地化,并与已认领 /plan 命令的提示逐字共用同一份文案(经标准套件 useProjection 读取的 host 折叠值;owner 提供的 placeholder 优先)。另一个会话视图活跃时,待处理的 composer 接管仍保持挂载,使被阻塞的 agent(智能体)仍能收到回答;没有待处理交互时,活跃会话的 composer 归 Chat 所有。composer bar slot 本身为 session-maybe:没有当前会话时,同一个 bar 以不可交互状态渲染(machine face 均缺席、disabled owner prop),而不是换入一棵平行的 disabled 树,因此选择 workspace 时 textarea DOM 不会被销毁;严格会话作用域的控件 seat 在会话存在之前保持为空。
聊天统计行的 token 账目来自经标准套件 useProjection 读取的通用 token-meter 投影 tokenUsage:计费输入为未缓存输入、缓存读取与缓存写入之和;缓存命中率以缓存读取除以该总量。可见节点只提供轮次与步骤计数,以及 LLM(大语言模型)和工具的墙钟时间:这些是关于「屏幕上有什么」的窗口作用域事实,而非账目;压缩(compaction)使已加载窗口不再包含 assistant 节点时,持久 token 与上下文分组仍保持可见。同一次窗口折算还会把每个有完整记录的步骤的 TTFT(首 token 延迟)取平均,并用采样到的输出 token 数除以其解码时长之和,得到经 conversation locale 命名空间本地化的延迟/吞吐分组(中文为 首 token 平均 … · … tok/s);缺少某个 timing 边界或 usage 采样的步骤会直接退出这些数字,而不是让它们失真。轮次计数、步骤计数、耗时、缓存与 token 各项的标签也使用同一命名空间。每个已结算轮次还会在其 assistant footer 的 用时 之后追加 hover 才显示的 首 token {s}秒 · {tps} tok/s 标签——即该轮次首个步骤的 TTFT 与轮次聚合的解码吞吐——仅当该轮次的 timing 位于已加载窗口内才显示(窗口是日志的连续后缀,因此窗口内的轮次必然带着它的全部步骤),未记录的数字会各自省略。未组合 token-meter 的部署会整组省略 token 分组;统计行过长时以省略号截断,仅在内容真的被裁切时由延迟 hover tooltip 承载完整文本。上下文占用率从统计行移到了 composer 尾部的 ContextMeter:模型座位之后的一枚 14px 占用圆环,由 contextPressure 供数,仅当分子与路由容量都已知时才渲染;点击弹出的面板把「已用百分比」标题与 ~已用 / 容量 数字,与来自 contextBreakdown 投影、带 ~ 前缀的启发式组成明细行(系统提示词、工具、对话消息)及分色分段进度条并列。圆环与标题读取 projectedTokens——把提供方样本沿此后表层的增减推进到当下——因此压缩会立刻反映出来,而不必再等一整轮;组成明细行仍是纯启发式,因此加起来依然不等于标题数字(原理)。占用率是刻意为之的近似值:分子与容量是两个相互独立的「后写覆盖」投影字段,并非同一次请求的原子观测。
src/client/ 按领域组织。contract/ 是唯一的跨领域共享表层(slots.ts slot 声明与组合后的 props、views.ts 共享原语、tool-call-model.ts);skeleton/、chat/ 和 toolviews/ 目录只导入 contract 文件,彼此之间从不互相导入。apply.ts 是唯一允许导入全部三个领域的组装点。/client 导出表层只包含契约:apply/inject、两个服务类和 contract/ 类型家族;实现组件与 store factory 保持内部,经 apply 的 slot 注册抵达页面。
模型体验
无。会话 UI 在浏览器中渲染会话历史与流;这里没有任何内容进入模型请求。
KV Cache 影响
无;该包既不组装也不发送提供方请求。
已知限制与暂缓事项
- 压缩标记不显示规模:该行尚不报告检查点替换了多少条消息或哪段范围。
- 统计行的耗时与速率只覆盖窗口内消息流:LLM 与工具墙钟时间以及 TTFT 与吞吐平均值由快照的 assistant
timing与工具 call/result 配对折算,落在已加载事件窗口之外的节点(更早的历史)不计入。 - 详情面板没有入口:
ChatViewInjected.openDetails虽已实现却无人调用,因此以原始形式显示已选择调用的那部分在组装后的应用中不可达。没有 Input/Output/Metadata 切换、Prev/Next 步进,也没有 trajectory 深链接。 - assistant 逐消息分页是预留 slot:设计中已有图稿,尚未实现。已定稿的内容 IconActions 行(复制/时钟/分支)只挂在每个已结束轮次中最后一条带 text 内容的 assistant 下;轮次中间的叙述、纯 Think 节点,以及仍在产出步骤的轮次里的所有节点都不带 chrome。除非该消息同时也是已完成轮次的最后一个 transcript 节点,否则分支保持禁用;启用后,它会 fork 到该轮次末尾,在 client 端递增继承标题并打开子会话。fork 或改名失败时源会话保持选中(决策)。
- 已发送的 user 消息无法编辑:user 气泡保留时钟和复制;分支只存在于 assistant 回答之下(决策)。编辑功能要与其背后的能力一起回归:既需要针对已定稿 user 消息的 client 变更,也需要 host 侧对已经消费过它的轮次给出行为(决策)。
- others 工具行的闪光图标是手绘近似版本:无法在本地导出设计字形的矢量几何;等到存在精确导出后再将其提升到 ui-primitives。
- 审批面板的「始终允许此类」暂缓:持久授权需要授权存储设计;今天只能回答允许一次/拒绝。
- TodoPanel 将过长条目截成单行省略号:figma 条没有换行或展开入口,完整文本无法在行内读完。
- Queue 编辑仅支持文本:包含非文本块的行仍显示扁平化预览,但由于内联编辑器无法保留这些块,其编辑控件会被禁用。文本行进入编辑模式后,删除和严格 steering(中途引导)操作会被保存和取消取代;Enter 保存,Escape 取消。
- Queue 严格 steering 会保留完整消息:Agent 运行期间,steering 操作会以原子方式把所寻址的 Queue 单次入队项转移到当前 next-step 窗口。包含混合内容的行仍可使用此操作,因为它会转发不可变消息,而非文本投影。带 placement 的 Host 快照会在会话流末尾渲染待处理 steering,直到已消费的
user/message折叠进持久 transcript(文本记录),因此立即展示、重连和回放共享同一个线性权威。