Skip to content
FunCoding

Search

Search docs, Skills and MCP

Daemon 共享 UI 状态

把 SSE 事件归一化为 transcript blocks,处理排序、订阅与重同步。

This page has not been translated into English yet. The original Chinese version is shown below.

@qwen-code/sdk/daemon 提供与具体界面分离的 UI 原语。它把 daemon 事件转换为语义化 transcript;纯状态层不依赖 React 或 DOM,宿主可选择自己的渲染方式。

三层职责

层入口职责
归一化normalizeDaemonEvent把 wire envelope 转为 DaemonUiEvent 数组
状态reduceDaemonTranscriptEvents / createDaemonTranscriptStore累积消息、工具、审批和状态块
展示daemonBlockToMarkdown / daemonBlockToHtml / daemonBlockToPlainText把语义块投影为展示内容

宿主可只使用归一化层,也可使用完整 store。Web Shell 是实际消费者;共享原语的存在不代表 native TUI、渠道和 IDE 默认路径已全部迁移。

连接 store

以下函数接收已建立的 DaemonSessionClient 与宿主渲染回调:

import {
  createDaemonTranscriptStore,
  normalizeDaemonEvent,
} from '@qwen-code/sdk/daemon';

async function consumeSession(session, signal, render) {
  const store = createDaemonTranscriptStore();
  const unsubscribe = store.subscribe(() => render(store.getSnapshot()));
  try {
    for await (const envelope of session.events({ signal })) {
      store.dispatch(normalizeDaemonEvent(envelope, {
        clientId: session.clientId,
        suppressOwnUserEcho: true,
      }));
    }
  } finally {
    unsubscribe();
  }
  return store.getSnapshot();
}

先注册 subscriber,再消费持续事件流,避免订阅代码被无限循环挡住。当前 store 实现提供 getSnapshot();部分架构说明中的 getState() 示例与该实现不符。

subscribe 返回取消订阅函数;dispatch 接受单个或多个 UI event,并通过 microtask 合并通知。宿主仍负责停止事件流、处理连接错误和生命周期。

排序与时间

使用 selectTranscriptBlocksOrderedByEventId(state) 按 daemon 的 eventId 排序。createdAt 是 clientReceivedAt 的旧别名,不能用客户端接收时间保证多个客户端或重放后的相同顺序。

显示时间优先使用 serverTimestamp,缺失时回退 clientReceivedAt。formatBlockTimestamp 支持 locale、timeZone 等显示选项;SDK 能读取服务器时间不意味着每种旧 daemon envelope 都已写入该字段。

查询状态

selectCurrentTool、selectApprovalMode、selectToolProgress 和 selectPendingPermissionBlocks 分别提取当前工具、审批模式、进度及未解决审批。running、in_progress、pending、confirming 等状态使工具成为进行中;终态清理当前指针,未知状态不擅自清空它。

session、workspace 和 auth 类事件中有不少属于旁路观察信息,不会自动追加聊天正文。宿主应选择需要显示的设置状态、提示条或认证交互,不能假定每个事件都对应一条可见消息。

重同步顺序

session.state_resync_required 会设置 awaitingResync,并暂停处理普通增量事件。宿主要选择恢复策略:reset() 清空本地状态,或 clearAwaitingResync() 保留本地块并重新接受事件。

必须在新 SSE 流或 Last-Event-ID: 0 重放开始传递事件之前解除该状态;等重放结束才解除会丢掉重放内容。重新获取的是有界窗口,不是无限历史。窗口中的 history_truncated 仅显示历史裁剪状态,不能再次触发同一重同步循环。

clearAwaitingResync() 保留上次丢失范围供诊断;要清空这些记录可使用 reset()。完整持久历史的读取与展示仍需结合会话记录接口。