☰
Kun 的 DSH 风格 Composer QueueDock:繁忙轮次待发消息的呈现、编辑与排序管理
2026/10/12 8:17:25 网站建设 项目流程
  • 人工智能
  • AI Agent
  • 自主智能体
  • 桌面应用
  • MCP Clients

【免费下载链接】Kun

Local-first AI agent workspace for coding, writing, design, research, and automation — one runtime for desktop GUI and TUI.

项目地址:https://gitcode.com/gh_mirrors/de/Kun
点击查看免费下载

导读

本篇文章基于 Kun 仓库中的 OpenSpec 变更提案《match-deepseek-harness-composer-queue-ui》展开,讲解 Kun 如何在桌面 GUI 中把"繁忙轮次期间排队等待发送的后续消息"从旧的QueueStrip + hover Portal交互,重构为贴合 DeepSeek Harness 冻结参考实现的Composer QueueDock(依附于输入框的队列停靠坞)。读完本文你将掌握:单行/多行折叠展开的 disclosure 语义、把排队消息"还原到输入框"的安全编辑策略、Guide/Retry/Remove 动作的串行化与失败保留、展开队列内的拖拽与键盘排序,以及 36px/28px/12px/180px 等固定几何参数与 Kun 语义主题令牌的映射方式,并了解对应组件、store 与测试在仓库中的具体位置。

一、为什么改:旧队列呈现的三个痛点

Kun 此前已经在 renderer 侧实现了完整的排队消息机制:线程级持久化、FIFO 顺序排空(drain)、暂停/失败恢复以及显式的"当前轮次引导"(mid-turn guidance)。但它的呈现层存在明显短板,提案 proposal.md 将其归纳为三点:

  1. 信息被压缩隐藏:旧实现只把第一条消息投影到一条脱离输入框的QueueStrip上,其余消息折叠成+N徽标,完整列表要悬停或聚焦触发 Portal 才可见。多条待发输入难以被发现,也更难控制。
  2. 编辑语义是破坏性的:旧交互中的"编辑"(overflow Edit menu)会执行"出队到草稿"(dequeue-to-draft)操作,本质上是把消息从队列里取出,容易让人误以为只是修改。
  3. 失败消息被静默过滤:失败状态的排队消息被直接隐藏,用户无法看到失败原因,也没有重试入口。

而 DeepSeek Harness 的冻结参考实现(master@0a53fb55bea101816fa226bb964ae2bed71c343b)采用依附输入框的 QueueDock:单条直接展示、多条折叠成总数、编辑把消息交回输入框、失败时队列仍然可见。Kun 据此发起本次 UI 对齐改造,设计文档见 design.md,验收需求见 specs/composer-queue-dock/spec.md。

二、目标与非目标:对齐外观,不动运行时语义

设计文档明确划定了本次改造的边界:

Goals(要做)

  • 完全对齐冻结参考 QueueDock 的层级、几何、折叠规则、编辑流程、动作顺序、可访问性、主题行为与窄宽度约束;
  • 保留 Kun 现有的 FIFO 交付、图片/Plan/Graph 引导资格、暂停/失败恢复、幂等性、冻结的 model/provider/routing 快照以及线程级持久化;
  • 让失败消息可见且可恢复;
  • 允许用户在展开的 QueueDock 内通过直接操作把靠后的消息移到更前;
  • 补充确定性的组件测试、store 测试与 Electron 交互/视觉证据。

Non-Goals(明确不做)

  • 不改变运行时 steering 路由、终态密封(terminal sealing)、交付顺序、持久化格式、发送快捷键偏好或普通繁忙轮次的提交行为;
  • 不引入 DSH 的全局队列/steer 快捷键策略,也不会自动 steer 排队消息;
  • 不在列表折叠、行还原或其它队列变更进行中时提供排序或 overflow 菜单。

设计文档还特别强调:历史上存在一个被完全 revert 的提交8fbb9885c(混入了无关的消息时间线行为),因此实现必须从冻结的 Harness 源码按当前 Kun 合约重建 QueueDock,而不是直接 cherry-pick 那个提交。

三、核心交互一:单行直显 / 多行折叠展开(Disclosure)

组件 FloatingComposerQueuedMessages.tsx 实现了 DSH 风格停靠坞。其规则完全对应 spec 中的 Requirement "QueueDock uses reference disclosure behavior":

可见排队消息数呈现方式
0 条不渲染任何 dock(queue.length === 0时组件直接返回null)
1 条直接渲染完整行,无计数头部、无 Portal
2 条及以上默认折叠成"总计数"头部按钮,点击在原位展开/折叠完整的 FIFO 列表

关键实现细节在 FloatingComposerQueuedMessages.tsx:

if (queue.length === 0) return null const interactionActive = busy !== null const expanded = queue.length === 1 || !collapsed || interactionActive
  • visibleQueue负责过滤可见行(L124-L135):pending、paused、failed、starting、in_flight均可见;
  • 起始/在途行保持可见是有意设计:其用户消息已归属一个被受理的 turn,但用户仍可查看状态;一旦 runtime 真正开始执行该 turn(queuedMessageStartedByRuntime),排空逻辑会将其从队列中消费掉,避免与时间线中的用户消息重复(见 queued-message-persistence.ts);
  • 交互期间强制保持展开:当 restore 或队列变更进行中且队列含多条消息时,头部折叠按钮被disabled(disabled={interactionActive}),防止用户在异步动作进行中误折叠;
  • 队列清空后重置折叠状态:useEffect在队列长度归零时setCollapsed(true)(L167-L169),保证下一次多行队列重新以折叠形态出现。

计数头部的文案由queuedMessagesTitle/queuedMessagesConfirming两个 i18n key 提供,并带count参数,多语言文案集中在 phone-composer.json 等 locale 文件中。

停靠坞如何"长"在输入框上

设计上,QueueDock 不再属于绝对定位的 hover 状态栈,而是通过FloatingComposerAboveInputStack新增的attachedDock座位渲染在浮动状态之前(FloatingComposerAboveInputStack.tsx),实际装配点在 FloatingComposerStackView.tsx 的attachedDock={(<FloatingComposerQueuedMessages ... />)}。这样 dock 的底边被 composer 卡片物理封住,不会被 Todo/Graph/Goal 等浮层从中间隔开。组件根节点上的data-composer-stack-item="queue"标记也使其可以参与 composer 栈的布局管理。

四、核心交互二:编辑 = 还原到输入框,且冻结投递设置

这是本次改造中最关键的语义变化:不再提供行内编辑器,编辑动作一律把消息从队列还原回 composer 输入框,随后输入框获得焦点供继续修改。

4.1 安全还原策略(restore eligibility)

不是所有排队消息都能被"忠实重建"回输入框。纯策略函数位于 queued-message-edit.ts,queuedMessageEditBlockReason对以下情况返回阻止原因:

  • 存在steeringRequest(引导请求在途);
  • waitForRuntimeAdmission为 true(暂定受理未定);
  • deliveryState是starting(确认中);
  • 携带 persona、guiPlan、graph 编排等派生结构;
  • agentSurface为write/design;
  • 携带 subagentResume、messageSource、fileReferences、composerContexts、设计画布/设计模式/设计工件、writeContext 等无法重建的负载;
  • 附件中包含document类附件(文档内容已被内联进 prompt,无法还原为输入框附件);
  • 文本与附件均为空。

只有"pending 的纯文本(可选displayText缺失或与text完全一致)或图片附件消息"才允许还原。queuedMessageComposerRestoreText还处理了一个细节:Plan/auto 队列消息会把用户原始请求存在displayText、内部计划 prompt 存在text,还原时取回displayText;纯图片消息的text是合成 prompt,还原时保留空文本、依赖附件恢复。

4.2 store 侧的还原动作

store 动作restoreQueuedMessage(id, accept)实现在 chat-store-thread-queue-actions.ts,流程是:

  1. 通过withQueueMutation加互斥锁(见下文"动作串行化");
  2. 若行处于starting且受理未定,先awaitQueueAdmission等待受理落定;
  3. 用queuedMessageEditBlockReason做二次校验,被拒绝则走现有error通知路径,行保持排队不动;
  4. 通过restoreQueuedMessageFromQueue(queued-message-edit.ts)按稳定 id从数组中移除该消息(不触碰其它记录、不改变顺序);
  5. 若该行已进入 runtime 队列(有deliveryTurnId),先saveQueueEditIntent把行标记为editIntent: 'cancelling'并调用provider.cancelQueuedTurn取消服务端排队 turn,再标记为restoring;
  6. 通过accept(message)回调把文本追加到现有草稿、恢复图片附件、重放冻结的 mode/model/reasoning/permission 设置,然后持久化并从队列中删除该行。

设计文档强调:还原被拒的场景(id 不存在、负载不支持、行已 paused/failed/starting/in-flight)都不会删除原行;还原过程中若队列实时更新导致该行被移除,composer 草稿保持完整,用户继续编辑即可。

五、核心交互三:动作串行化与失败可见(Guide / Retry / Remove)

5.1 动作单飞行(single-flight)

组件维护一个本地busy: { id, kind }状态(FloatingComposerQueuedMessages.tsx),applyAction包装器保证任意时刻只有一个动作在途(L255-L269):busy非空时,折叠头部、所有行的编辑/移除/引导按钮全部禁用,行内图标替换为旋转的Loader2spinner。

store 层还有第二道互斥:queue-mutation-fence.ts维护线程级mutations集合,withQueueMutation在动作执行期间占用、结束后释放(queue-mutation-fence.ts)。这与组件层busy配合,防止并发 drain 与用户操作竞争。

5.2 Guide 与 Retry

普通 pending 行的动作顺序为Edit → Remove → Guide(参考顺序)。canGuide判定规则(FloatingComposerQueuedMessages.tsx)很细致:

  • pending 行:仅当有活跃 turn、guidanceEligible !== false且queuedMessageGuidancePayload非空时可引导(引导受steeringRequest/deliveryTurnId等合约约束);
  • paused/failed 行:按钮文案变为Retry,仅当当前没有活跃 turn 且该行没有等待 runtime 受理(!message.waitForRuntimeAdmission)时可用;
  • waitForRuntimeAdmission的失败行保留 Remove 但禁用 Retry——因为其原始受理等待器已落定,重放会导致在 drain 期间消息再次消失。对应 i18n 文案为queuedMessageRetryUnavailable:"This provisional message cannot be retried safely; remove it and submit again"。

store 动作guideQueuedMessage(chat-store-thread-queue-actions.ts)负责真正执行:对 graph turn 走steerSourceTurn,否则调用 provider 的steerUserMessage;对 paused(已 admission)行优先resumeQueuedTurns恢复服务端队列;对 failed 行重新生成clientRequestId并标记为 pending 后重新 drain。引导成功后该行从队列移除并乐观插入用户块;失败时行保留在队列中,error路径负责通知。

5.3 失败消息保持可见

spec 的 Requirement "Queue actions are lossless and serialized" 明确:失败行在 QueueDock 中始终可见,行内显示失败状态与errorMessage/errorCode(36px 行高不变,完整错误通过title属性暴露),并提供 Remove;仅当活跃 turn 空闲且行未被已落定的受理等待器绑定时才启用 Retry。reconcileQueuedMessages(queued-message-persistence.ts)保证终态失败跨 reconciliation 保持failed,只有显式 Retry 或 Remove 才会改变它。

六、核心交互四:展开列表内的拖拽与键盘排序

DSH 参考实现本身不提供排序,这是 Kun 在保持参考几何不变的前提下为用户需求增加的扩展(见 design.md 的 "Reorder expanded queues by stable identity")。

启用条件(FloatingComposerQueuedMessages.tsx):

const reorderEnabled = Boolean(onReorder && expanded && queue.length > 1 && !interactionActive && !queue.some((message) => message.editIntent || message.deliveryState === 'starting'))

即:列表已展开、可见行数 ≥ 2、无进行中的编辑/变更、无 restoring/starting 行时才显示 28px 的拖拽把手(GripVertical)。

拖拽逻辑:HTML5 drag 事件中,dragover时用指针与目标行中点的clientY比较选择before/after(L218-L232),CSS 通过[data-drop-position='before']::before/'after'渲染 2px 插入指示线(FloatingComposerQueuedMessages.module.css);drop时校验源/目标 id 都仍存在,再调用持久化的reorderQueuedMessage(sourceId, targetId, position)。

键盘排序:把手聚焦后按ArrowUp/ArrowDown每次移动一个可见位置,focusReorderHandle保证焦点停留在稳定把手上,同时通过role="status"的aria-live区域播报新位置(L190-L206)。

store 侧排序(chat-store-thread-queue-actions.ts):若两行都已进入 runtime 队列(有deliveryTurnId),先调用provider.moveQueuedTurn同步服务端顺序;随后在本地数组中按稳定 id 重新插入并持久化。queue store 是唯一顺序权威——组件本地拖拽状态从不修改影子数组,queueRevision变化即清空临时拖拽状态,源行/目标行在 drop 前被实时队列移除时不会误排序其它行。

七、几何与主题:固定 Harness 尺寸 + Kun 语义令牌

CSS Module FloatingComposerQueuedMessages.module.css 完整落地了参考几何:

几何参数值CSS 位置
头部/行高36px.header, .row
动作按钮尺寸28px(width/height).action
拖拽把手尺寸28px.dragHandle
动作间距10px(gap).actions
顶部圆角12px(底部直角贴合 composer).panel
内部滚动上限180px(max-height+overflow-y: auto).list
composer 上缘 3px 收进margin: 0 auto -3px.dock

色彩方面不引入任何硬编码的仅亮色颜色或独立的深色分支,全部映射到 Kun 语义令牌:--ds-surface-subtle(面板底)、--ds-border/--ds-border-muted(边框与分隔)、--ds-text/--ds-text-muted/--ds-text-faint(文本层级)、--ds-accent(焦点环与插入指示线)、--ds-danger(失败状态),暂停状态用#d97706与文本色混合。prefers-reduced-motion: reduce下 spinner 动画被禁用,状态传达不依赖动画。

窄布局约束:dock 宽度为calc(100% - 1rem)并居中,预览文本text-overflow: ellipsis单行省略,状态徽标限宽 30%,不再有固定 Portal 溢出视口。

八、可访问性与本地化

spec 的 Requirement "QueueDock matches reference geometry and accessibility" 对无指针用户有明确要求,实现要点:

  • 折叠头部按钮带aria-controls={listId}与aria-expanded={expanded};
  • 每个动作按钮有可访问名称(aria-label)与禁用说明(title);编辑按钮不可用时,label 切换为queuedMessageEditUnsupported等阻止原因文案;
  • 还原成功后 composer 输入框获得焦点;
  • 排序把手带aria-label(含位置序号与消息摘要)与aria-describedby提示,位置变更通过role="status"+aria-live="polite"播报;
  • 主题行为:语义表面/边框/文本令牌保证亮色、深色与自定义主题下状态对比度可读。

九、兼容性与迁移:持久化与交付合约不变

spec 的最后一个 Requirement "Existing queue delivery contract remains compatible" 与 design 的 Migration Plan 呼应:

  • 无持久化迁移:排队记录仍存于kun.queuedMessages.v1注册表(queued-message-persistence.ts),旧的 pending/paused/failed/starting/in_flight 记录由新 dock 的可见性规则直接投影,不重写存储;
  • 普通繁忙轮次提交(未激活 Guide)仍按原 FIFO 语义进入下一轮次交付,drainQueuedMessages循环(chat-store-thread-queue-actions.ts)行为不变;
  • 回滚只需还原旧 renderer 组件,store/runtime 队列数据保持兼容。

十、验证与测试:从组件到 Electron 冒烟

任务清单 tasks.md 记录了四轮验证工作:

  1. Store/契约:新增并持久化restoreQueuedMessage(id),纯函数安全还原策略,restore 经 Workbench 与 composer props 接通;
  2. Renderer:attachedDock座位、单/多行 disclosure、restore-to-composer 编辑、动作单飞行、失败保留与 Retry、CSS Module 几何与多语言文案;
  3. 组件测试:FloatingComposerQueuedMessages.dock.test.ts 覆盖单/多行 disclosure、实时更新、还原编辑、动作锁定、失败状态与可访问性,以及拖拽/键盘排序与竞态安全 no-op;
  4. Electron 冒烟:FloatingComposerQueueDockSmokeFixture.tsx 提供single/multi/long/failed四种确定性场景,覆盖 36/28/12/180 几何、与 composer 的贴合、窄布局以及亮/暗双主题截图;最后跑npm run typecheck、npm run build:kun、npm run build、ESLint、文件行数/OpenSpec/diff 检查,并基于独立 worktree 从本地developHEAD 以 fast-forward 合入、验证改动范围仅限 queue/composer/OpenSpec/测试文件。

十一、风险与权衡(来自 design.md)

风险缓解
还原派生 prompt 导致可见文本与 runtime 文本失同步仅允许 composer 可忠实重建的消息(纯文本镜像或图片附件),拒绝文档/结构化负载
实时队列更新移除了正在还原的行行离开队列,composer 草稿完整,编辑在输入框继续
还原期间第二条消息到达交互状态强制新多行 dock 展开
长队列盖过 composer列表上限 180px,仅列表内部滚动
拖拽与队列投递竞争以稳定 id 寻址、变更期间禁拖、实时退役时清空临时拖拽态、store 空操作安全收敛
失败项包含超长错误文本行保持 36px,显示紧凑失败指示,完整错误经 title 属性暴露
暂定受理失败被重试(waiter 已落定)禁用不安全 Retry,保留行,引导用户 Remove 后重新提交

结语

本次改造把 Kun 的排队消息呈现从"隐藏 + Portal"升级为"依附输入框的停靠坞",用一套精确到像素的几何参数对齐 DeepSeek Harness 参考实现,同时把编辑语义从破坏性出队改为安全的"还原到输入框",让失败消息可见可恢复,并以"store 为唯一顺序权威"的方式增加了拖拽/键盘排序。整套设计围绕一个不变式展开:UI 可以重做,但 FIFO 交付、持久化格式与运行时语义必须原样保留。如果你需要在 Kun 桌面端定位排队消息相关的进一步开发,可以从 FloatingComposerQueuedMessages.tsx(呈现)、chat-store-thread-queue-actions.ts(store 动作)与 queued-message-persistence.ts(持久化与调和)三条主线入手。

  • 人工智能
  • AI Agent
  • 自主智能体
  • 桌面应用
  • MCP Clients

【免费下载链接】Kun

Local-first AI agent workspace for coding, writing, design, research, and automation — one runtime for desktop GUI and TUI.

项目地址:https://gitcode.com/gh_mirrors/de/Kun
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询