- 人工智能
- AI 应用
- 桌面应用
- 交互助手
【免费下载链接】ClawX
ClawX is a desktop app that provides a graphical interface for OpenClaw AI agents. It turns CLI-based AI orchestration into a desktop experience without using the terminal. China website is https://clawx.com.cn.
本篇文章聚焦 ClawX 桌面端将 OpenClaw 原生子代理(subagent)会话从侧边栏独立行「内嵌」进父级对话后,如何保住子代理分类、标题、运行状态与注意力语义,并解决父会话在 ACP prompt 已 settle 之后仍能增量呈现announce:v1助理消息、以及重开会话后完整账本回放与有界尾部补充的兼容性问题。读完本文,你将掌握 ClawX 中 canonical session key 的判定规则、呈现层与数据层解耦的实现方式、补丁化 OpenClaw ACP 适配器的 ambient 流机制,以及对应的单元/端到端测试验证入口。
背景:子代理导航移入父 Chat 后暴露的两类问题
OpenClaw 原生子代理会话使用规范的agent:<agentId>:subagent:<id>会话键。在早期版本中,它们以普通会话行的形式出现在侧边栏中。随着embed-subagent-sessions-in-parent-chat任务落地,ClawX 改变了这一呈现策略:原生子代理不再作为独立侧边栏行展示,而是以 ACP lineage 关系内嵌在父级对话中。这一改动带来了两个必须同时解决的伴生问题:
- 子代理分类保真问题:子代理行被隐藏后,必须仍然在共享会话目录中保留,用于精确键(exact-key)的状态(status)、注意力(attention)、路由、工作区清理与删除行为;同时,分类必须只依据规范的第三段
subagent判定,绝不能把agent:<id>:acp:<id>这类 ACP 会话误判为原生子代理。 - announce 消息增量呈现问题:OpenClaw 可能在父会话原始 ACP 运行结束之后,通过后续合成的
announce:v1运行向同一持久化 transcript 追加助理记录。若 ClawX 只回放事件账本(ledger),这些后置公告文本就会「丢失」——即典型的agent:main:session-1788111066745现场案例。
该任务属于gateway-backend-communication场景(场景定义),其规则边界由 acp-chat-state-and-history 与 sidebar-session-attention-authority 两篇规则共同约束。
子代理分类:只认规范的第四段 canonical key
分类是全部呈现逻辑的地基。ClawX 在 src/stores/chat/session-key-utils.ts 中实现了严格的判定函数:
export function isNativeSubagentSessionKey(sessionKey: string): boolean { const parts = sessionKey.split(':'); return parts.length === 4 && parts[0] === 'agent' && Boolean(parts[1]) && parts[2] === 'subagent' && Boolean(parts[3]); }判定依据是精确的第三段subagent与恰好四段结构,而非 transcript 文本、会话标题或任何散文内容。这意味着:
agent:main:subagent:child-1→ 原生子代理 ✅agent:research:subagent:child-2→ 原生子代理 ✅agent:main:acp:child-1→ 不是原生子代理 ❌(第三段是acp,属于 ACP 会话)subagent:child-1、agent:main:session-subagent-child-1→ 不是 ❌
上述分类在 tests/unit/session-key-utils.test.ts 中有逐一对应的断言,包括「ACP 会话仍应出现在侧边栏、而原生子代理被隐藏」的对照测试:
expect(shouldIncludeSessionInSidebarList(nativeChild)).toBe(false); // agent:main:subagent:child-1 expect(shouldIncludeSessionInWorkspaceDeletion(nativeChild)).toBe(true); expect(shouldIncludeSessionInSidebarList({ key: 'agent:main:acp:child-1', displayName: 'ACP conversation', })).toBe(true);侧边栏隐藏是纯呈现层操作:目录照常保留
侧边栏过滤与目录保留是两套独立的判定逻辑:
export function shouldIncludeSessionInSidebarList(session: ChatSession): boolean { // Native children remain in the catalog for status, attention, and deletion joins. return shouldRetainSessionInCatalog(session) && !isNativeSubagentSessionKey(session.key); } export function shouldIncludeSessionInWorkspaceDeletion(session: ChatSession): boolean { return shouldRetainSessionInCatalog(session); }可见 src/components/layout/Sidebar.tsx 在分组呈现时调用shouldIncludeSessionInSidebarList过滤(group.sessions.filter(({ session }) => shouldIncludeSessionInSidebarList(session))),而工作区级清理仍以shouldIncludeSessionInWorkspaceDeletion为准——隐藏的行并未被剔除出共享目录。这是 sidebar-session-attention-authority 中「presentation-only、与注意力独立」原则的具体实现:
只有精确的 canonical
agent:<agentId>:subagent:<childId>键被隐藏;其行必须保留在共享目录中,用于精确键的注意力、路由、工作区清理与删除。Native children 必须从每一个隐式 fallback 候选集合中排除,但被显式选中的子代理在其精确目录行存在期间可以保持选中状态。
同时,Sidebar.tsx中每个会话行的 busy/unread 状态依然来自共享的projectSessionRunState运行投影(status→hasActiveRun→observedBusy兜底),子代理行的实时运行状态不会因隐藏而丢失。
父 Chat 内嵌子代理:ACP 是 lineage 与标题的唯一权威
在父级对话中呈现直接子代理遵循一套严格的权威分层(详见 embed-subagent-sessions-in-parent-chat 与 acp-chat.md 的「Subagent Lineage And Titles」小节):
- lineage 成员关系与标题的唯一来源是 ACP
session/list:Main 校验SessionInfo._meta,优先parentSessionId、回退spawnedBy,拒绝畸形与自引用 lineage;游标遍历有界(每页 100 行、至多 128 页),重复/空白/畸形/超限游标返回类型化失败而非部分结果。 - Gateway 目录只做「门禁」:最新精确键目录中的存在性(presence)决定子代理当前是否可见、可操作,以及返回直接父级的入口是否可用;但 presence 从不创造 lineage 成员关系或标题。
sessions_spawn结构化结果仅是失效信号:只有status: accepted、非空runId与非空childSessionKey的结果才触发一次全新的 canonical ACPsession/list刷新;工具输出从不提供展示用的 lineage、标题或成员关系。[Subagent Context]前缀只在显示层移除:formatSubagentSessionTitle仅在「该会话确为原生子代理且标题以[Subagent Context]开头」时裁掉该前缀,且只影响 ClawX 的展示状态,绝不改写 OpenClaw 会话数据:
const SUBAGENT_CONTEXT_PREFIX = '[Subagent Context]'; export function formatSubagentSessionTitle(sessionKey: string, title: string): string { if (!isNativeSubagentSessionKey(sessionKey) || !title.startsWith(SUBAGENT_CONTEXT_PREFIX)) { return title; } return title.slice(SUBAGENT_CONTEXT_PREFIX.length).trimStart(); }测试确认:[Subagent Context] You are running as a subagent (depth 1/1).在原生子代理键下会显示为You are running as a subagent (depth 1/1).,而在agent:main:session-1下原样保留。
子代理相关的展示文案在 shared/i18n/locales/en/chat.json 的subagentSessions分组中统一本地化(Dispatched {{count}} subagents、Expand subagent sessions、Open subagent {{title}}等),并在 en/zh/ja/ru 四种语言中保持键位对齐。
announce:v1 增量呈现:被动订阅 + replay 基线 + 有界 delta
当父会话已加载且原始 ACP prompt 已 settle,OpenClaw 可能随后通过announce:v1:<...>运行向同一持久化 transcript 追加公告。补丁化适配器(patches/openclaw@2026.7.1-2.patch)在 ACP SDK 客户端中新增了 ambient(环境常驻)机制,使 ClawX 无需把 Gateway 历史当作替代 Chat 传输,即可增量呈现这些公告。
被动 exact-session 订阅
适配器为当前加载的会话保留一条与 prompt 生命周期无关的被动sessions.messages订阅,并在会话关闭、shutdown、或加载会话被替换时释放或替换它。session/load期间适配器先订阅、再取回放,把匹配的 Chat 与 Agent 事件缓冲到 replay 基线建立后再按到达顺序排空(见 openclaw-acp-stream-patch.test.ts 中buffers exact-session Gateway events while session replay establishes its baseline用例)。
路由条件:announce 与「精确加载的原生子代理」
补丁中的findAmbientSession路由逻辑同时接受两类 no-pending 运行:
normalizedRunId.startsWith("announce:v1:")且 exact session key 匹配;- 普通 no-pending 运行:仅当当前加载键为规范的
agent:<agentId>:subagent:<childId>四段结构时——因为sessions_spawn在 ACP 连接之外启动子代理,子代理没有挂起中的 prompt。
普通父级运行、畸形子代理键、空白 runId 与无关会话一律忽略。测试用agent:main:subagent:child-1与agent:main:main对照验证了只有合法子代理键和announce:v1:前缀会被路由到 ambient 处理器。
快照感知的 delta 算法
ambient 消息以整轮累计快照(cumulative snapshot)形式到达。补丁引入了resolveAcpTextDelta(previousText, nextText)做有界前缀比对:
- 严格扩展(
Hello→Hello world):只发出未见后缀world; - 相同或陈旧前缀(
Hello world→Hello world或Hello):不发任何内容; - 非前缀更新:整段完整发出,避免丢失更短的尾部片段。
该函数在 assistant 文本与 commentary thought 两条流中均被调用(测试断言handleDeltaEvent中出现两处调用)。需要说明的是,按 acp-chat.md 的记录,前缀比对是兼容性启发式而非协议保证——Gateway 协议 v4 已提供deltaText与replace显式操作,待适配器消费这些字段后应替换该启发式。
replay 基线跨工具边界保持
对于精确加载的原生子代理,会话 replay 会先种下累计 assistant 与 thought 基线(resolveAmbientReplayBaseline);同一 assistant 轮次内的工具边界不重置基线,因此后续累计快照只发出未见后缀,只有真实用户消息边界才重置。测试用例continues a partially replayed child message across tool boundaries without repeating its cumulative prefix用A / toolResult / B的回放基线验证:工具后到达的累计文本ABC只发出C。子代理的 commentary Agent 事件被记录为 thought 块,工具 start/update/result 通过同一 run 作用域路由发出,terminal 则记录一次会话快照检查点,避免后续 tail 回放重复已经 live 捕获的文本。
完整账本回放与有界 transcript 尾部:高水位时间戳规则
重新打开父会话时,历史必须保持「完整账本不变」并追加新记录。补丁中的selectPostLedgerTranscript(transcript, ledgerReplay)严格实现:
- 完整账本事件保持原样、按序回放;
- 仅允许追加有限时间戳严格晚于账本最大有限事件时间戳的持久化 transcript 记录(
transcriptReplay = ledgerReplay.complete ? selectPostLedgerTranscript(transcript, ledgerReplay) : transcript); - 时间戳等于或早于高水位(high-water mark)的记录、无有限时间戳的记录(字符串、
Infinity、缺失)以及 transcript 拉取失败,均不替换、不比较、不删除任何账本事件; - 当账本完全没有有限事件时间戳时,bounded 兜底可用。
单元测试以{timestamp:99} / {timestamp:200} / 缺失 / 字符串 / Infinity / {timestamp:201}序列验证:在完整账本at:100, at:200, at:NaN下只返回after(201)一条。
现场证据(surface-subagent-sessions-and-announcements.md 的 Field Evidence):会话agent:main:session-1788111066745的原始 ACP 运行结束于 2026-08-30 17:35 UTC,其完整账本先于两条后来合成的announce:v1运行结束;OpenClaw WebUI 能显示第一次「检查一下」——两个已完成,一个大任务还在跑与最终完成,而 ClawX 此前只回放早期账本。这正是本任务要修复的差异:补丁后的 ambient 流 + post-ledger transcript 尾部机制,让 ClawX 在不改变账本权威性的前提下补齐 live 已捕获与后续追加的公告文本。
验证与回归:测试入口一览
任务规定的验证矩阵(见任务元数据requiredTests)可直接在仓库运行:
pnpm exec vitest run tests/unit/session-key-utils.test.ts tests/unit/openclaw-acp-stream-patch.test.ts pnpm exec playwright test tests/e2e/chat-sidebar-session-attention.spec.ts pnpm run typecheck pnpm run comms:replay pnpm run comms:compare其中 tests/unit/openclaw-acp-stream-patch.test.ts 直接对node_modules/openclaw/dist/acp-cli-*.js与server-chat-*.js打包产物做源码提取(runInNewContext)验证补丁行为,覆盖:post-ledger 尾部选择、announce 流与 terminal 检查点、普通子代理 run 路由、commentary thought 流、replay 基线缓冲、工具生命周期、终态快照核对与 error terminal 拒绝等;tests/e2e/chat-sidebar-session-attention.spec.ts 则从 UI 侧回归侧边栏注意力与隐藏行为。
设计红线小结
- 呈现与数据解耦:隐藏、标题清洗都只发生在 ClawX 展示层;OpenClaw transcript、会话行、标题均不被改写。
- 单一权威:ACP
session/list是 lineage/标题唯一权威;Gateway 目录仅提供存在性与运行状态门禁;ClawX Main/Renderer 只消费 ACP 会话更新作为普通助理历史,绝不把 Gateway 历史或 transcript 文本当作替代 Chat 传输(见 backend-communication-boundary 边界规则)。 - 有界且可回放:ambient 运行表有界(至多 100 个 run)、family 查询有界(128 页)、transcript 尾部有界且受高水位约束,所有状态均为内存态,无第二份持久化账本。
- 删除不级联:删除父会话保留精确、非级联行为,不删除其保留的子代理;选择修复不会合成缺失子代理占位符,也不会隐式选中隐藏子代理。
- 人工智能
- AI 应用
- 桌面应用
- 交互助手
【免费下载链接】ClawX
ClawX is a desktop app that provides a graphical interface for OpenClaw AI agents. It turns CLI-based AI orchestration into a desktop experience without using the terminal. China website is https://clawx.com.cn.
相关推荐
ClawX 聊天工作区与导航完全指南:ACP 会话工作区权威、侧栏排序与子代理导航
ClawX 聊天工作区与导航完全指南:ACP 会话工作区权威、侧栏排序与子代理导航 本文是 ClawX(OpenClaw AI Agents 的桌面图形界面)中
人工智能AI 应用桌面应用交互助手30亿参数改写AI效率范式:Qwen3-30B-A3B如何让企业AI成本降60%?
30亿参数改写AI效率范式:Qwen3 30B A3B如何让企业AI成本降60%? 导语 阿里云通义千问团队推出的Qwen3 30B A3B Instruct
人工智能AI 应用桌面应用交互助手ClawX ACP 会话计划指示器:基于 update_plan 工具调用的纯渲染层实现与数据校验
ClawX ACP 会话计划指示器:基于 update_plan 工具调用的纯渲染层实现与数据校验 导读 ClawX 桌面端为 OpenClaw ACP(Age
人工智能AI 应用桌面应用交互助手
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考