
人工智能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基于 OpenClaw AI Agent 的桌面客户端中将原生子代理subagent会话嵌入其父级会话这一运行时桥接任务为核心讲解 ACPsession/list作为血统与标题唯一权威的架构设计、分页游标边界、Gateway 精确键目录门控规则以及父子会话导航、删除语义等交互行为。读完本文你将掌握该特性从 Main 进程 ACP 服务到 Renderer 组件投影的完整调用链理解ACP 决定血缘、Gateway 决定运行态与可操作性的双权威模型并能在 ClawX 仓库 中对照源码与测试验证每一项验收标准。特性目标与设计意图该任务见 embed-subagent-sessions-in-parent-chat.md的意图可以概括为三点隐藏原生agent:agentId:subagent:id会话不再以独立行出现在左侧边栏但依然保留在共享的 Gateway 会话目录session catalog中用于状态、路由与既有的删除行为。嵌入在父级会话中直接展示其 ACP 子代理血统与标题——输入区左侧出现本地化的子代理计数控件展开后列出每个直接子代理。保真始终以 Gateway 作为运行状态live run status的唯一权威子代理行在忙时显示加载图标空闲时显示机器人图标与侧边栏使用同一套运行态投影。该特性属于 gateway-backend-communication 场景下的runtime-bridge任务类型并遵循 renderer-main-boundary、backend-communication-boundary、acp-chat-state-and-history、sidebar-session-attention-authority 与 ui-i18n-design-tokens 等规则。权威边界ACP 决定血缘Gateway 决定运行态整个特性的核心是一组精心划分的权威边界理解它们是读懂全部实现的前提语义唯一权威说明血统成员关系与标题ACPsession/listMain 校验SessionInfo._meta优先parentSessionId回退spawnedBy拒绝畸形与自引用当前子代理可见性与可操作性Gateway 会话目录最新精确键存在目录中存在性只门控当前操作不创建血统成员关系或标题运行状态Gatewaysessions.list/sessions.changed的精确键status与hasActiveRunACP prompt 状态与本地发送状态不驱动子代理加载图标血统投影内存态、可重载不是第二份 transcript、状态目录或持久化关系数据库正如任务 Scope 所写Latest exact-key Gateway catalog presence gates current child visibility and actionability plus direct-parent return-target availability; presence never creates lineage membership or titles.最新精确键 Gateway 目录存在性门控当前子代理的可见性、可操作性以及直接父级返回目标的可达性存在性绝不创建血统成员关系或标题。架构参考文档 acp-chat.md 对这一模型的描述是ACP 是 ClawX 暴露的 Chat 语义的首选权威Renderer 将每个 ACP 子代理与 Gateway 会话目录中的同一精确键 join 后才呈现操作入口。Main 进程新增类型化 ACPsession/list操作为什么由 Main 持有该操作血统lineage是 ACP 暴露的 Chat 语义因此该特性在 Main 进程的 ACP 服务中新增了一个类型化操作getSessionFamily。实现位于 electron/services/acp-chat-service.ts关键流程如下async getSessionFamily(payload: unknown): PromiseAcpSessionFamilyResult { if (!isPlainRecord(payload) || !isValidSessionKey(payload.sessionKey)) { return failSessionFamily(Invalid ACP session family payload); } try { const connection await this.ensureConnection(); if (!this.sessionListSupported) { return failSessionFamily(ACP agent does not support session/list); } const sessions: SessionInfo[] []; const seenCursors new Setstring(); let cursor: string | undefined; for (let page 0; page ACP_SESSION_LIST_MAX_PAGES; page 1) { const result readAcpSessionListPage(await connection.listSessions(cursor ? { cursor } : {})); if (!result) return failSessionFamily(Invalid ACP session/list response); sessions.push(...result.sessions); const nextCursor result.nextCursor; if (nextCursor null) { return projectAcpSessionFamily(payload.sessionKey, sessions); } if (typeof nextCursor ! string || !nextCursor.trim() || seenCursors.has(nextCursor)) { return failSessionFamily(Invalid ACP session/list cursor); } seenCursors.add(nextCursor); cursor nextCursor; } return failSessionFamily(ACP session/list exceeded ${ACP_SESSION_LIST_MAX_PAGES} pages); } catch (error) { logger.error([acp-chat] getSessionFamily failed: ${String(error)}); return failSessionFamily(error); } }实现要点能力探测在 ACP 连接初始化initializeConnectionOnce时通过result.agentCapabilities.sessionCapabilities?.list判断是否支持session/list存入sessionListSupported标志acp-chat-service.ts不支持时返回类型化失败而非降级。不动当前会话该操作复用既有 ACP 连接ensureConnection只做列表读取不改变已加载的 ACP 会话。不改变传输层该调用发生在 Main 持有的 ACP 子进程上Renderer 只通过hostApi.chat.getAcpSessionFamily发起请求不直接触碰 ACP 连接。有界历史Bounded History任务文档对历史范围的约束非常明确每页请求100 行最多跟随128 页常量ACP_SESSION_LIST_MAX_PAGES 128见 acp-chat-service.ts。只有当前捆绑的 OpenClaw ACPsession/list实现返回的会话才有资格进入家族投影。归档archived、已删除deleted、已清理cleaned或未列出的历史子代理一律排除。家族查询不扫描transcript、公告announcements、助手文本、Gateway 血统字段或子代理 UUID 来恢复这些历史记录。游标与分页校验Main 对每页响应做严格校验readAcpSessionListPagesessions必须是数组且每个元素满足isAcpSessionInfosessionId与cwd均为非空字符串。分页遵循以下规则nextCursor为空则视为最后一页立即投影。游标必须是非空字符串且不得重复出现seenCursors去重否则返回Invalid ACP session/list cursor。超过 128 页返回ACP session/list exceeded 128 pages的类型化失败绝不返回部分家族数据。重复的会话 ID 保留首次出现的那一条去重发生在投影层projectAcpSessionFamily。血统投影层subagent-lineage.ts共享类型契约投影的核心类型定义在 shared/acp-chat/types.tsexport type AcpSessionFamilyPayload AcpSessionKeyPayload; export type AcpSessionFamilyMember { sessionKey: string; title: string; updatedAt: string | null; parentSessionKey: string | null; }; export type AcpSessionFamilyResult | { success: true; current: AcpSessionFamilyMember | null; children: AcpSessionFamilyMember[]; error?: never; } | { success: false; current: null; children: []; error: string; };这是一个可辨识联合discriminated union失败分支强制current: null、children: []保证 Renderer 不必对部分结果做防御式猜测。父级解析parseAcpSessionParentId实现在 shared/acp-chat/subagent-lineage.ts逻辑为读取SessionInfo._meta必须是普通对象isRecord否则返回null。优先取parentSessionId若缺失、非字符串、空白或等于自身 sessionId自引用回退到spawnedBy。回退同样拒绝空白与自引用候选。注意合法的候选在 trim 后按原样保留readParentCandidate返回 trim 前还是 trim 后的值源码中readParentCandidate对非空字符串直接返回原值。测试 tests/unit/acp-subagent-lineage.test.ts 验证了保留不透明 ID 的精确性同时拒绝空白与自引用候选——例如parentSessionId: agent:main:parent 会被原样保留为 agent:main:parent 含空格而42、false、数组形态的_meta都会得到null。原生子代理 ID 判定isNativeAcpSubagentSessionIdexport function isNativeAcpSubagentSessionId(sessionId: string): boolean { const parts sessionId.split(:); return parts.length 4 parts[0] agent Boolean(parts[1]) parts[2] subagent Boolean(parts[3]); }即只有精确的agent:agentId:subagent:childId四段结构才算原生子代理。测试用例acp-subagent-lineage.test.ts确认agent:main:acp:child-1、agent:main:subagent:child:extra、agent::subagent:child-1、agent:main:subagent:均为false。家族投影projectAcpSessionFamily投影结果仅包含当前会话sessionKey精确匹配请求的会话投影为current。直接原生子代理同时满足原生子代理 ID且父级解析结果 当前会话 key的会话投影为children按updatedAt新到旧排序时间戳缺失或不可解析的排最后时间戳相等时按 sessionKey 的 Unicode 码点code point字典序打破平局。嵌套后代agent:...:subagent:x的子代理不会被打平进祖先的面板——它们属于各自的直接父级见测试 acp-subagent-lineage.test.ts。标题以 ACP 提供的title为准缺失时回退到精确 session key。Renderer 侧通过 src/lib/acp/subagent-lineage.ts 直接再导出这三个函数。Renderer 侧会话家族加载与去抖家族加载的生命周期Chat 页面src/pages/Chat/index.tsx用loadCurrentSessionFamily发起请求并用familyRequestIdRef请求序号 selectedFamilySessionKeyRef当前选中会话双重校验拒绝过期完成const loadCurrentSessionFamily useCallback((sessionKey: string) { const requestId familyRequestIdRef.current; void hostApi.chat.getAcpSessionFamily({ sessionKey }).then((result) { if ( requestId ! familyRequestIdRef.current || selectedFamilySessionKeyRef.current ! sessionKey || !result.success ) return; setLoadedSessionFamily({ sessionKey, result }); }).catch(() undefined); }, []); useEffect(() { familyRequestIdRef.current 1; setLoadedSessionFamily((current) current?.sessionKey currentSessionKey ? current : null); if (currentSessionKey) loadCurrentSessionFamily(currentSessionKey); return () { familyRequestIdRef.current 1; }; }, [currentSessionKey, loadCurrentSessionFamily]);这一设计与验收标准Lineage requests are scoped to the requested current session and reject stale completion after selection changes血统请求限定于当前请求的会话并在切换选择后拒绝过期完成一一对应。失败时隐藏或保留既有家族数据绝不阻塞普通 ACP 会话加载。sessions_spawn结果仅作失效信号验收标准明确指出一个已完成的结构化sessions_spawn工具结果只有在status: accepted且runId、childSessionKey均非空时才作为血统刷新失效信号——真正的成员关系与显示位置仍由 ACPsession/list决定。实现位于 index.tsxconst successfulSpawnSignature useMemo(() { if (visibleAcpTimeline.sessionId ! currentSessionKey) return ; return visibleAcpTimeline.itemOrder .map((itemId) visibleAcpTimeline.itemsById[itemId]) .filter((item): item is ToolCallItem item?.kind tool-call) .map(successfulSessionSpawnIdentity) .filter((identity): identity is string identity ! null) .join(\n); }, [currentSessionKey, visibleAcpTimeline]); useEffect(() { const previous spawnInvalidationRef.current; if (previous.sessionKey ! currentSessionKey) { spawnInvalidationRef.current { sessionKey: currentSessionKey, signature: successfulSpawnSignature }; return; } if (previous.signature successfulSpawnSignature) return; spawnInvalidationRef.current { sessionKey: currentSessionKey, signature: successfulSpawnSignature }; if (successfulSpawnSignature) loadCurrentSessionFamily(currentSessionKey); }, [currentSessionKey, loadCurrentSessionFamily, successfulSpawnSignature]);这段代码通过拼接成功 spawn 的会话标识签名并与之对比检测到新 spawn 后触发loadCurrentSessionFamily刷新。子代理会在活跃父会话期间的这次成功sessions_spawn后出现且会话切换或 Renderer 重载后同一关系仍能恢复因为血统来源于 ACP 权威列表而非本地状态。子代理会话 UIAcpSubagentSessions组件组件职责src/pages/Chat/AcpSubagentSessions.tsx 是聚合控件与展开面板的实现聚合按钮显示子代理计数Dispatched {{count}} subagents等本地化文案与一个图标——任一子代理忙时显示旋转加载图标Loader2否则显示机器人图标Bot。展开面板仅在sessions.length 0时渲染每行只出现于ACP 列出且精确键存在于最新 Gateway 目录的子代理。无障碍聚合状态与每行状态都有rolestatus的屏幕阅读器文本aria-livepolite聚合按钮带aria-expanded与aria-controls。显示标题通过getDisplayTitle处理仅做展示层去除[Subagent Context]前缀/^\[Subagent Context\]\s*/不改变 ACP 提供的标题本体——ACP 依然是标题的唯一权威。function getDisplayTitle(session: AcpSubagentSession): string { return session.title.replace(/^\[Subagent Context\]\s*/, ) || session.sessionKey; } function SessionIcon({ busy }: { busy: boolean }) { if (busy) { return ( Loader2 classNameh-4 w-4 shrink-0 animate-spin text-blue-700 motion-reduce:animate-none dark:text-blue-400 aria-hiddentrue / ); } return Bot classNameh-4 w-4 shrink-0 text-muted-foreground aria-hiddentrue /; }与侧边栏相同的运行态投影子代理的busy标志index.tsx使用与当前侧边栏相同的运行态投影projectSessionRunState数据源是 Gateway 会话目录与sessionAttentionByKeyconst subagentSessions useMemoAcpSubagentSession[](() { if (!visibleSessionFamily) return []; return visibleSessionFamily.children.flatMap((child) { const catalogSession catalogSessionByKey.get(child.sessionKey); if (!catalogSession) return []; const runState catalogSession ? projectSessionRunState(catalogSession) : unknown; return [{ sessionKey: child.sessionKey, title: child.title, busy: runState busy || (runState unknown sessionAttentionByKey[child.sessionKey]?.observedBusy true), }]; }); }, [catalogSessionByKey, sessionAttentionByKey, visibleSessionFamily]);要点ACP 子代理必须同时存在于 Gateway 目录精确键才显示为行catalogSession不存在则flatMap返回空数组busy只来自 Gateway 精确键运行态投影为unknown时才回退到observedBusyACP prompt 状态与本地发送状态完全不参与。会话切换时的面板行为运行态变化不会折叠已展开的子代理列表而切换所选会话会关闭上一个会话的子代理面板expansion状态以sessionKey为键切换后自动重置对应验收标准 Live status changes do not collapse an open child list. Changing the selected conversation closes the prior conversations child panel.运行态变化不折叠已展开的子代理列表切换所选会话关闭上一会话的子代理面板。子代理会话内的返回父级导航当一个会话自身就是原生子代理时index.tsxconst isCurrentSessionSubagent visibleSessionFamily?.current?.sessionKey currentSessionKey isNativeSubagentSessionKey(currentSessionKey); const currentSessionTitle isNativeSubagentSessionKey(currentSessionKey) ? isCurrentSessionSubagent ? formatSubagentSessionTitle(currentSessionKey, visibleSessionFamily.current!.title) : currentSessionKey : catalogSessionTitle; const familyParentSessionKey isCurrentSessionSubagent ? visibleSessionFamily.current?.parentSessionKey : null; const directParentSessionKey familyParentSessionKey catalogSessionByKey.has(familyParentSessionKey) ? familyParentSessionKey : null;子代理会话显示本地化的Subagent标记chat-subagent-marker。明确的返回父级会话操作只在ACP 列出的直接父级同时存在于最新 Gateway 目录精确键时出现directParentSessionKey非空。返回动作的目标是 ACP 中的直接父级而非浏览器历史The explicit return action targets the direct ACP parent rather than browser history。formatSubagentSessionTitle的展示层处理位于 src/stores/chat/session-key-utils.ts同样仅剥离[Subagent Context]前缀。同一文件还定义了shouldIncludeSessionInSidebarList原生子代理保留在目录用于状态、注意力对账、工作区清理、导航与既有删除但不进入侧边栏列表session-key-utils.ts这正是侧边栏过滤仅为展示层的落点——被隐藏的子代理行对精确键注意力对账、工作区清理、导航及既有非级联删除行为仍然可用。删除语义不级联删除父级会话沿用既有精确会话删除行为不会级联删除子代理Out Of Scope 第一条Cascading parent deletion to child sessions。隐藏的子代理行仍保留在共享会话目录中因此现有的删除路径照常覆盖它们——只是删除必须显式发生而不是跟随父级自动发生。本地化与设计令牌任务要求所有可见与可访问文本均以英文、中文、日文、俄文四语本地化并复用既有的 composer、面板、选中态与状态设计令牌。英文文案见 shared/i18n/locales/en/chat.jsonsubagentSessions: { count: Dispatched {{count}} subagents, expand: Expand subagent sessions, collapse: Collapse subagent sessions, toggle: {{action}}, {{count}}, panel: Subagent sessions, open: Open subagent {{title}}, marker: Subagent, returnToParent: Return to parent conversation, busy: Running, settled: Settled, aggregateStatus: Subagents: {{status}}, rowStatus: {{title}}: {{status}} }对应语言文件位于 zh、ja、ru并由 i18n-locale-parity.test.ts 保证四语键位一致。验证从单测到 E2E任务在requiredTests中给出了完整的验证命令可在仓库根目录执行# 1. 校验任务规格本身 pnpm harness validate --spec harness/specs/tasks/embed-subagent-sessions-in-parent-chat.md # 2. 单元测试核心文件 pnpm exec vitest run tests/unit/acp-chat-service.test.ts tests/unit/acp-subagent-lineage.test.ts \ tests/unit/acp-subagent-sessions.test.tsx tests/unit/chat-input.test.tsx \ tests/unit/chat-acp-inline-timeline.test.tsx tests/unit/host-api-facade.test.ts \ tests/unit/session-key-utils.test.ts tests/unit/session-status.test.ts \ tests/unit/session-attention.test.ts tests/unit/harness-specs.test.ts \ tests/unit/i18n-locale-parity.test.ts # 3. E2E pnpm exec playwright test tests/e2e/chat-subagent-sessions.spec.ts \ tests/e2e/chat-sidebar-session-attention.spec.ts # 4. 静态检查与构建 pnpm run typecheck pnpm run lint:check pnpm run build:vite # 5. 通讯回归 pnpm run comms:replay pnpm run comms:compare关键测试锚点tests/unit/acp-subagent-lineage.test.ts覆盖父级解析优先级与回退、畸形/自引用拒绝、精确原生子代理 ID 判定、仅投影当前会话与直接子代理、跨页去重、时间戳排序与平局规则。tests/unit/acp-chat-service.test.ts验证 Main 侧getSessionFamily的游标跟随、页面上限与类型化失败。tests/e2e/chat-subagent-sessions.spec.ts验证侧边栏隐藏、父会话聚合控件、子行展开、子会话标记与返回父级交互。明确不做的事Out Of Scope最后任务明确列出了不属于本特性的边界防止实现蔓延级联删除父会话到子代理会话。恢复非常古老、归档、已删除或已清理的子代理会话。从公告或助手文本推断子代理完成状态。用 ACP prompt 状态替换 Gateway 会话目录的运行态投影。这四条边界与前述权威模型互为表里血统恢复只信任 ACP 当前列表运行态只信任 Gateway 精确键其余一切推断路径都不被允许。小结ClawX 将原生子代理会话嵌入父级会话的实现本质是一次严谨的权威分离工程ACPsession/list通过 Main 进程的类型化操作成为血统成员与标题的唯一权威有界分页、严格校验、类型化失败Gateway 会话目录以精确键存在性门控当前可见性、可操作性与返回目标运行态图标完全由 Gateway 运行态投影驱动。Renderer 只做展示层过滤与过期拒绝不做任何血统推断。理解这套双权威模型你就能举一反三地看懂 ClawX 中所有涉及ACP 语义 Gateway 状态协同的模块——这也是该任务被归入 gateway-backend-communication 场景的根本原因。赞分享人工智能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 ACP Chat 状态与历史权威规则解析会话加载、重放、血统与流式增量的一致性治理ClawX ACP Chat 状态与历史权威规则解析会话加载、重放、血统与流式增量的一致性治理 导读 本文基于 harness/specs/rules/acp人工智能AI 应用桌面应用交互助手ClawX 侧边栏会话注意力机制以 OpenClaw Gateway 会话目录为唯一权威的工程实现解析ClawX 侧边栏会话注意力机制以 OpenClaw Gateway 会话目录为唯一权威的工程实现解析 ClawX 是一款为 OpenClaw AI Agen人工智能AI 应用桌面应用交互助手ClawX 子代理会话内嵌与 announce 增量流ACP 事件账本与 Gateway 目录的呈现层治理ClawX 子代理会话内嵌与 announce 增量流ACP 事件账本与 Gateway 目录的呈现层治理 本篇文章聚焦 ClawX 桌面端将 OpenCla人工智能AI 应用桌面应用交互助手上一篇FastMCP v3 Provider 架构解析FastMCPProvider 与 TransformingProvider 拆分设计下一篇Mojo 2023 年 7 月更新解读循环展开、包模块导入与全局变量等语言特性演进创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考