拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ClawX 修复 ACP 侧边栏标题竞态:让本地新建会话在首条提示词就绪前保持隐藏

人工智能AI 应用桌面应用交互助手【免费下载链接】ClawXClawX 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.项目地址https://gitcode.com/gh_mirrors/cl/ClawX点击查看免费下载导读本篇文章围绕 ClawX 中一项名为fix-acp-sidebar-title-race的运行时桥接修复展开核心解决一个非常具体的用户可见问题当用户点击新建会话时OpenClaw ACP 桥接会以ACP作为其 Gateway 客户端显示名出现如果侧边栏在会话标题从首条用户提示词推导出来之前就渲染 Gateway 返回的行用户就会看到新会话瞬间闪现一个名为ACP的占位标题。本文从任务规范harness/specs/tasks/fix-acp-sidebar-title-race.md出发结合仓库源码与测试完整讲解createdLocally本地占位符机制、创建确认acknowledgement的原子状态切换、Gateway 目录对账的保留规则以及冷启动心跳替换会话如何复用同一生命周期。读完本文你将掌握 ClawX 侧边栏会话标题的权威来源判定原则以及如何用本地标记 单点确认的模式避免传输层身份污染对话层展示。一、问题本质ACP是传输来源不是会话标题OpenClaw ACP 桥接Agent Client Protocol bridge在向 Gateway 注册时会合法地把自己的客户端显示名display name上报为ACP。这一点在任务文档中写得非常明确The OpenClaw ACP bridge legitimately reportsACPas its Gateway client display name.在 ClawX 的架构里这条信息被严格当作传输层来源标记transport provenance而不是一段对话的标题。任务文档为此定义了清晰的边界ClawX treats that value as transport provenance and keeps it behind the local new-chat placeholder until the user-authored title is ready.也就是说ACP只说明这条 Gateway 行来自 ACP 桥接通道它既不能代表对话内容也不能成为侧边栏可见的会话标题。而竞态的根源在于时序用户点击新建后ClawX 先在本地创建一个占位会话随后 ACP 会话才真正在 OpenClaw 侧创建成功在此期间 Gateway 可能已经把同一个 key 的行携带displayName: ACP推送过来。若侧边栏直接消费这条 Gateway 行就会在标题水合title hydration完成前的窗口期里闪现一个名为ACP的新会话。这项任务对应的预期用户行为expectedUserBehavior是四条硬性约束新会话在 ACP 会话创建成功之前完全不出现在侧边栏中冷启动时为替换隐藏心跳历史而生成的会话必须走同样的本地占位生命周期首次可见的标题必须来自第一条用户提示词绝不能是 ACP 桥接客户端显示名显式设置的标签与已缓存的标签始终保持权威。二、核心机制createdLocally本地占位标记实现这一切的锚点是ChatSession类型上的createdLocally可选字段定义于 shared/chat/types.tsexport interface ChatSession { key: string; /** OpenClaw transcript session UUID, used to identify synthetic fallback titles. */ sessionId?: string; label?: string; displayName?: string; derivedTitle?: string; lastMessagePreview?: string; thinkingLevel?: string; model?: string; updatedAt?: number; status?: string; hasActiveRun?: boolean; /** Channel provider that last delivered to this session (e.g. webchat, feishu, discord). */ channel?: string; /** OpenClaw ACP session cwd, mirrored for display and routing. OpenClaw is the source of truth. */ workspacePath?: string; /** Renderer-local placeholder created by New Chat before ACP has created the backing session. */ createdLocally?: boolean; }字段注释直接点明了语义这是渲染进程本地的占位标记在 ACP 尚未创建底层会话之前由新建会话动作创建。值得注意的是注释同时强调workspacePath以 OpenClaw 为准OpenClaw is the source of truth而createdLocally则是纯 ClawX 侧的状态用于把尚未确认创建成功的会话挡在侧边栏之外。newSession()生成带占位标记的本地行在聊天状态仓库 src/stores/chat.ts 中newSession()按如下步骤建立本地占位newSession: () { const { currentSessionKey, sessions } get(); const prefix getCanonicalPrefixFromSessionKey(currentSessionKey) ?? getCanonicalPrefixFromSessions(sessions) ?? DEFAULT_CANONICAL_PREFIX; const sessionKey ${prefix}:session-${Date.now()}; if (sessions.some((session) session.key currentSessionKey session.createdLocally)) { localDraftSessionKeys.delete(currentSessionKey); useComposerDraftStore.getState().clearDraft(currentSessionKey); } localDraftSessionKeys.add(sessionKey); markLocalSessionCatalogMutation(); set((state) buildSessionSwitchPatch(state, sessionKey, { createdLocally: true })); },要点有三会话 key 采用agent:prefix:session-时间戳形式key 被登记进localDraftSessionKeys本地草稿集合状态补丁通过buildSessionSwitchPatch写入createdLocally: true。之后 Chat 页面切换到该会话时由于createdLocally为真会被识别为尚未绑定 ACP 的本地新会话。三、生命周期三阶段准备 → 创建确认 → 原子可见阶段一本地准备prepareLocalSessionChat 页面在 src/pages/Chat/index.tsx 中监听当前会话useEffect(() { if (!currentSessionKey || !cwd || !currentSession?.createdLocally) return; acpLoadInFlightKeyRef.current null; const hasStaleTimeline acpTimeline.sessionId ! currentSessionKey || acpTimeline.itemOrder.length 0; if (acpActiveSessionKey currentSessionKey acpWorkspaceRoot cwd acpCwd cwd !hasStaleTimeline) return; prepareLocalAcpSession({ sessionKey: currentSessionKey, workspaceRoot: cwd, cwd }); }, [/* ... */]);当且仅当当前会话带createdLocally标记时才调用 ACP 会话仓库的prepareLocalSession用当前有效工作区cwd发起本地准备。这保证了占位会话在拿到 ACP 加载上下文前不会提前进入普通会话的加载路径。阶段二创建确认acknowledgeAcpSessionCreated创建成功的确认由acknowledgeAcpSessionCreated(key, workspacePath, initialPrompt)完成见 src/stores/chat.tsacknowledgeAcpSessionCreated: (key, workspacePath, initialPrompt) { const normalizedWorkspacePath workspacePath?.trim(); const initialLabel key DEFAULT_SESSION_KEY ? : toSessionLabel(initialPrompt || ); localDraftSessionKeys.delete(key); pendingCatalogConfirmationSessionKeys.add(key); markLocalSessionCatalogMutation(); set((state) { const sessionEntry state.sessions.find((session) session.key key); return { sessions: sessionEntry ? state.sessions.map((session) ( session.key key session.createdLocally ? (() { const acknowledgedSession { ...session }; delete acknowledgedSession.createdLocally; return { ...acknowledgedSession, ...(normalizedWorkspacePath ? { workspacePath: normalizedWorkspacePath } : {}), }; })() : session )) : [...state.sessions, { key, displayName: key, ...(normalizedWorkspacePath ? { workspacePath: normalizedWorkspacePath } : {}) }], ...(initialLabel !hasExplicitSessionLabel(sessionEntry) !state.sessionLabels[key] ? { sessionLabels: { ...state.sessionLabels, [key]: initialLabel } } : {}), }; }); },这段实现同时兑现了验收标准中的三个承诺原子恢复被竞态赶走的占位行如果state.sessions里已经找不到该 key例如 Gateway 对账曾把它从目录中移走会走else分支把行重新插回即restores a raced-away placeholder清除标记并写入工作区对仍带createdLocally的行执行delete acknowledgedSession.createdLocally并写入规范化后的workspacePath用首条提示词播种标题initialLabel toSessionLabel(initialPrompt || )写入sessionLabels但有两个权威性保护——DEFAULT_SESSION_KEY即agent:main:main不播种且存在显式标签hasExplicitSessionLabel或已有缓存标签时不覆盖。于是清除标记、播种首条提示词标题、暴露该行发生在同一次 Renderer 状态切换中从用户视角看新会话要么不存在要么一出现就带着正确的标题永远不会以ACP身份闪现。阶段三从首条提示词推导标题普通会话的 ACP 加载路径同样受控。在 Chat 页面的加载 effectsrc/pages/Chat/index.tsx中currentSession?.createdLocally会直接跳过 ACP 加载只有createIfMissing即目录中不存在该会话时loadAcpSession成功后才调用if (loaded createIfMissing) { acknowledgeAcpSessionCreated(currentSessionKey, cwd); }而在发送首条提示词的分支src/pages/Chat/index.tsx中createIfMissing的判定被扩展为!existingSession || !!existingSession.createdLocally——即本地占位会话首次发送时同样视为缺失走创建路径并在成功后调用acknowledgeAcpSessionCreated(sessionKey, promptCwd, text);这里第三个参数text就是用户输入的首条提示词原文它成为会话在侧边栏的第一个可见标题。这正是任务文档要求的Its first visible title is derived from the first user prompt, never the ACP bridge client display name.四、Gateway 对账如何保护占位标记竞态修复的关键防线在目录对账层Gateway 事件补丁与规范化的会话列表刷新都必须保留createdLocally标记直到创建确认发生。这由两条规则保证。字段级隔离补丁无法覆盖标记在会话目录实现 src/stores/chat/session-catalog.ts 中type SessionField Excludekeyof ChatSession, key | createdLocally;createdLocally与key一样被排除在可打补丁的字段集合之外。也就是说无论 Gateway 推送什么displayName、derivedTitle或status都无法通过补丁合并路径改写或抹掉createdLocally标记。保留规则标记本身就是保留权在 src/stores/chat/session-catalog.ts 的合并逻辑中const nextSessions [...sessions]; if (merged.createdLocally || shouldRetainSessionInCatalog(merged)) { nextSessions[index] merged; } else { nextSessions.splice(index, 1); }而shouldRetainSessionInCatalogsrc/stores/chat/session-key-utils.ts对createdLocally会话返回falseexport function shouldRetainSessionInCatalog(session: ChatSession): boolean { if (!session.key) return false; if (session.createdLocally) return false; if (isOpenClawHeartbeatOnlySession(session)) return false; if (isChannelSessionKey(session.key)) { return !isPlaceholderChannelSession(session); } return true; }综合来看createdLocally会话不会因为值得保留而被普通目录逻辑收编例如不会被当作正式会话行参与侧边栏展示但当 Gateway 事件确实把该 key 的行与本地占位合并时merged.createdLocally为真又保证该行不会被对账逻辑从目录中摘除。这一进一出恰好实现验收标准第一条事件补丁和规范化刷新都保留标记行既不出现在用户界面也不会因为 Gateway 数据到达而丢失。单测锚点Gateway 上报ACP时占位仍隐藏测试/单元/session-catalog.test.ts 中有一条直接针对本任务的用例it(keeps a local new-chat placeholder hidden when the Gateway reports its ACP display name, () { const result applyGatewaySessionsChanged( [{ key: SESSION_KEY, displayName: SESSION_KEY, createdLocally: true }], { sessionKey: SESSION_KEY, ts: 10, displayName: ACP, updatedAt: 10, // ... }, ); expect(result).toMatchObject({ applied: true, requiresReload: true }); expect(result.sessions).toEqual([{ key: SESSION_KEY, displayName: ACP, createdLocally: true, updatedAt: 10_000, }]); });测试名称本身就是验收标准的复述当 Gateway 上报其 ACP 显示名时本地新建占位保持隐藏。断言显示Gateway 的displayName: ACP可以进入合并结果但createdLocally: true必须原样保留侧边栏依据该标记继续隐藏此行。五、冷启动心跳替换会话同一个本地生命周期任务文档特别强调了一种容易被忽略的场景冷启动时为替换隐藏心跳历史而生成的会话a cold-start session created to replace hidden heartbeat history必须与用户手动新建的会话走完全相同的createdLocally生命周期。背景在于 OpenClaw 会在会话行里注入心跳轮询哨兵内容heartbeat poll sentinel。src/stores/chat/session-key-utils.ts 中的isOpenClawHeartbeatOnlySession会检查label、displayName、derivedTitle、lastMessagePreview是否只包含心跳哨兵且无用户撰写文本从而把这类行识别为隐藏的心跳会话并通过findHiddenOpenClawHeartbeatSession定位它。如果冷启动对账发现当前 key 的历史行只是心跳噪声需要创建一个新会话去替换它这个新会话不能以普通空会话的身份出现在侧边栏也不能绕过首条提示词播种标题的规则。规则文档 harness/specs/rules/session-workspace-authority.md 最后一段对此给出权威表述A fresh session generated during cold-start heartbeat replacement is also a local placeholder and must use the same lifecycle. Gateway client identity such as the ACP bridge display name is transport provenance, not a conversation title, and must not become briefly visible while transcript-derived title hydration catches up.也就是说即使 OpenClaw 侧已经用同一个 key 上报了 ACP 桥接显示名只要创建确认尚未发生Gateway 事件对账和规范化列表刷新都必须继续保留createdLocally标记只有确认动作本身acknowledgement才被允许让该行可见。这直接呼应验收标准的第二条冷启动心跳替换会话被标记为createdLocally且无法绕过首条提示词标题播种。六、渲染层不特判ACP字面量本任务还有一个容易被误解的验收点Renderer does not special-case the literalACPtitle or add a competing backend title source.渲染层不得对字面量ACP做特判也不得引入第二个与后端竞争的标题来源。这意味着修复不是在渲染层写一个如果标题是ACP就忽略的补丁而是从数据流源头把ACP挡在可见标题之外。Chat 页面在 src/pages/Chat/index.tsx 中处理标题展示const catalogSessionTitle currentSession?.createdLocally ? t(newSession) : currentSession ? getSessionDisplayTitle(currentSession, sessionLabels) : currentSessionKey;当会话仍带createdLocally时页面显示本地化的新会话文案t(newSession)此时displayName: ACP永远不会被当作标题渲染当确认完成后createdLocally已被清除标题走统一的getSessionDisplayTitle推导路径该路径以sessionLabels首条提示词播种与显式/缓存标签为权威。标题的权威顺序在参考文档 harness/reference/chat-workspace-and-navigation.md 的 First Send And Titles 一节中也有明确约定确认动作会原子地恢复被竞态赶走的占位行、清除标记、并在自动标题缺失时用原始首条提示词播种新可见的行因此绝不会在标题水合期间回退到桥接客户端身份The newly visible row therefore never falls back to the bridge client identity while transcript title hydration catches up。显式标签与已缓存标签始终优先Existing explicit or cached labels win。此外createdLocally还参与工作区解析在 src/lib/workspace-context.ts 中createdLocally会话被视为未绑定unbound其有效工作区回退到全局工作区或默认工作区且readOnly: false可编辑。这说明占位标记同时驱动着会话绑定binding语义只有创建确认后ACP 的 cwd 才成为权威工作区。七、验收标准与测试矩阵任务文档的acceptance部分定义了五条可验证的接受条件可与上述实现一一对应验收标准实现落点Gateway 事件补丁与规范化列表刷新在成功创建确认前保留createdLocallysrc/stores/chat/session-catalog.ts 的merged.createdLocally保留分支SessionField排除createdLocally冷启动心跳替换会话标记createdLocally且不能绕过首条提示词播种src/stores/chat/session-key-utils.ts 心跳识别规则文档 session-workspace-authority.md创建确认原子恢复占位、播种首条提示词标题、暴露该行src/stores/chat.ts 的acknowledgeAcpSessionCreated渲染层不特判ACP字面量src/pages/Chat/index.tsx 的createdLocally → t(newSession)分支Comms 回放与对比通过见下方测试命令中的 harness 校验任务文档requiredTests给出了三组必须通过的验证命令也是复现与回归本修复的标准操作pnpm exec vitest run tests/unit/session-catalog.test.ts tests/unit/chat-load-sessions-startup.test.ts tests/unit/chat-store-session-label-fetch.test.ts tests/unit/chat-acp-page.test.tsx pnpm run build:vite pnpm exec playwright test tests/e2e/chat-new-session-date.spec.ts pnpm run typecheck第一组覆盖目录对账保留标记session-catalog.test.ts、冷启动加载chat-load-sessions-startup.test.ts、标签获取chat-store-session-label-fetch.test.ts与 ACP 页面集成chat-acp-page.test.tsx第二组用真实浏览器验证chat-new-session-date端到端场景第三组保证类型安全。此外场景规范 harness/specs/scenarios/chat-workspace-and-navigation.md 将本任务归入gateway-backend-communication场景关联规则还包括sidebar-session-attention-authority即 Gateway 会话行是侧边栏运行状态的唯一权威源。八、工程边界与设计约束综合任务规范与关联规则可以把本修复背后的设计约束归纳为四条可复用的工程原则传输身份与对话身份分层Gateway 客户端显示名如ACP是传输来源信息永不进入会话标题标题只允许来自首条用户提示词、显式标签或缓存标签单一权威源避免了渲染层与后端的标题竞争。可见性由单一确认点控制createdLocally是是否已确认创建成功的唯一开关Gateway 事件、目录刷新都无法提前解锁可见性解锁只能由acknowledgeAcpSessionCreated完成且解锁、播种标题、暴露行必须在一次状态切换内原子完成。占位符可被恢复目录对账即使在确认前把行移走确认动作也会把被赶走的占位行重新插回并完成标题播种从而覆盖事件先到、确认后到的任意交错时序。权威标签永不被覆盖hasExplicitSessionLabel与已缓存sessionLabels构成保护闸自动播种仅在标题缺失时生效用户手动重命名或已存在缓存值始终保持权威。这套机制在 ClawX 中同时服务于用户手动新建会话与冷启动心跳替换会话两类路径是本地准备 单点确认模式在桌面 AI 编排客户端中的一次完整落地。后续若需深入可继续阅读 harness/reference/chat-workspace-and-navigation.md 的 First Send And Titles 章节、harness/specs/rules/session-workspace-authority.md 的完整规则文本以及 tests/unit/session-catalog.test.ts 中对 ACP 显示名竞态的回归用例。赞分享人工智能AI 应用桌面应用交互助手【免费下载链接】ClawXClawX 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.项目地址https://gitcode.com/gh_mirrors/cl/ClawX点击查看免费下载相关推荐Wazuh 5.0 Agent Groups 迁移指南从 4.x 手动迁移组配置与成员归属Wazuh 5.0 Agent Groups 迁移指南从 4.x 手动迁移组配置与成员归属 本篇技术指南面向需要从 Wazuh 4.x 升级到 5.0 的管理人工智能AI 应用桌面应用交互助手30亿参数改写AI效率范式Qwen3-30B-A3B如何让企业AI成本降60%30亿参数改写AI效率范式Qwen3 30B A3B如何让企业AI成本降60% 导语 阿里云通义千问团队推出的Qwen3 30B A3B Instruct人工智能AI 应用桌面应用交互助手ClawX Chat 工作区缺失检测与恢复指南从 ACP 会话绑定到本地化提示的完整实现ClawX Chat 工作区缺失检测与恢复指南从 ACP 会话绑定到本地化提示的完整实现 导读 本文聚焦 ClawXOpenClaw AI Agent 的桌人工智能AI 应用桌面应用交互助手上一篇终极Nexe指南如何将Node.js应用打包为独立可执行文件2025最新版下一篇如何快速配置黑苹果OpCore Simplify一键自动化工具完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门