dsh-input-queue 使用与实现说明

August 14, 2026 · View on GitHub

1. 目标

让"在 agent 忙碌时排队输入补充信息"变得可见、可管理、可发现

  • 排队消息一目了然(顺序、数量、内容);
  • 每条可编辑 / 删除 / 立即插话 / 调整顺序;
  • 忙碌且队列为空时给出可发现的提示;
  • 设置(General)提供「队列面板默认展开」开关(localStorage 持久化,客户端共享 store)。

2. 与 DSH 原生排队机制的关系

DSH 0.1.0-rc.6 已经内置了基础排队/steering 管线,本插件复用而非重造

原生能力位置
忙碌时回车默认排队busyEnter: 'queue'ui-conversation 会话设置,`BusyEnterBehavior = 'queue'
瞬态收件箱快照ConversationSnapshot.queuesession/queue mux frame,QueuedInboxItem[]
队列操作`updateQueue(itemId, { kind: 'edit'
队列发送conversation.send(text)(queued turn)、`prompt(content, 'queue'
极小队列条内置 QueueDockconversation.input.dock id queue,order 20,多条折叠为计数头)

本插件在 conversation.input.dockid: input-queueorder: 30 注册一个富面板,与内置 QueueDock 并存(排在它之后),不替换任何内置 seat。

3. 组件与数据流

ConversationSnapshot.queue  ──(useSession 选择器)──▶  QueuePanel
  └─ filter placement==='queued'  ──▶  queued: QueuedMessage[]

QueuePanel 操作:
  edit   ─▶ updateQueue(id, { kind:'edit',   content:[{type:'text',text}] })
  remove ─▶ updateQueue(id, { kind:'remove' })
  steer  ─▶ updateQueue(id, { kind:'steer' })        (仅 running 时可用)
  reorder─▶ for each queued: updateQueue(id,{kind:'remove'})
            for each reordered:  conversation.send(rowText)

3.1 注册(src/client/index.tsx

export const inject = ['slots', 'conversation', 'sessions']

export function apply(ctx) {
  ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
    name: 'conversation.input.dock',
    id: 'input-queue',
    order: 30,
    inject: (sessionId) => {
      const actx = ctx.sessions.scope(sessionId)
      const conversation = actx.get('conversation')
      return {
        updateQueue: (id, a) => conversation.updateQueue(id, a),
        send: (text) => conversation.send(text),
        notify: (level, text) => conversation.input.for(actx).notify(level, text),
      }
    },
  }, QueuePanel))
}
  • ctx.sessions.scope(sessionId) 取得会话作用域 Context,再 actx.get('conversation') 拿会话作用域的 IConversation
  • 面板通过 useSession(会话标准 kit)读快照,通过注入面拿动词,不 import 任何 conversation 实现。

3.2 面板状态(src/client/QueuePanel.tsx

  • queuedinbox.filter(row => row.placement === 'queued')
  • runninguseSession(s => s.running) —— steer 按钮仅在 running 时可用。
  • queueMutableuseSession(s => s.subagent === null) —— 子代理会话的队列不可改(与内置 QueueDock 同判断)。
  • busyId:单飞互斥,任何动作进行中禁用其余动作。
  • revealed:长文本展开/收起(预览截断 PREVIEW_LENGTH = 120)。

3.3 顺序调整(src/client/queue-model.ts

FIFO 队列没有原生 reorder 动词,采用"移除后重排":

  1. 校验所有排队消息 text !== null(非文本消息不可重排,避免丢附件)。
  2. moveItem(queued, from, to) 计算目标顺序(纯函数)。
  3. 逐条 remove 原队列,再按新顺序 send(rowText)row.text ?? row.preview)。

纯函数 moveItem / rowText / reorderableRows 独立于 React,供 scripts/verify.mjs 离线断言。

4. 已知限制与风险

  • 非文本消息不可重排QueuedMessage.text === null(含图片/附件块)时,上移/下移会提示失败;编辑按钮同样禁用。
  • 重排竞争remove + send 之间,若 host 恰好正把某条消息从 queued 认领为 steering/context,可能产生一条重复消息,删除即可。概率低、无数据丢失。
  • 瞬态队列session/queue 不落盘,刷新页面后排队消息丢失(DSH 原生行为)。
  • 顺序order: 30,排在内置 QueueDock(20)之后;如想隐藏内置 QueueDock,可在 profile 组合里移除 conversation-queue-dock 行(超出本插件范围)。

5. 验证

pnpm typecheck   # host + client 两个 program
pnpm build       # tsc → tsc → tsdown
pnpm verify      # 纯逻辑 + 产物/补丁形态冒烟(离线)

更完整的验证金字塔(独立 profile、真实 GUI)参见 agent-teams 的 docs/verification-guide.md;本插件为纯 client 面板,离线验证覆盖纯逻辑与产物形态,GUI 行为需在独立 web profile 里人工确认(安装后刷新页面,忙碌时回车排队,观察面板逐条操作)。