- 人工智能
- 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 AI Agent 的桌面图形化客户端)中restore-acp-file-activity这一运行时桥接任务的完整设计与实现:如何在 ACP Chat 时间线之上,把 OpenClaw Agent 成功执行的write、edit、apply_patch工具调用,还原为逐轮次(per-turn)的文件按钮、变更摘要与会话级(session-level)Changes 面板。你会了解到这套"纯渲染层投影"的数据模型、三种工具输入的规范语法(Canonical Inputs)、工作区绑定(workspace-scoped)的路径安全模型,以及它与附件(Attachments)管线的严格边界,最终掌握该功能在 openclaw-file-activities.ts 中的核心实现原理与测试验证锚点。
一、任务背景:从"hydration 设计"到"纯投影设计"
restore-acp-file-activity是 harness/specs/tasks/restore-acp-file-activity.md 中定义的一项runtime-bridge类型任务,隶属于gateway-backend-communication场景。其意图(intent)非常明确:
Restore per-turn and session-level OpenClaw file activity in ACP Chat while keeping tool-derived file access inside the bound workspace.
即:在 ACP Chat 中恢复逐轮次与会话级的 OpenClaw 文件活动,同时保证"由工具派生的文件访问"始终被约束在已绑定的工作区内。
任务名称中的 "restore" 对应参考文档 harness/reference/openclaw-file-activity.md 末尾的一句关键说明:
This reference replaces the former OpenClaw file activity hydration design while retaining its protocol grammar, security model, and aggregation semantics.
也就是说,本任务是替代旧的"hydration(水合)"设计——旧方案试图把文件活动"物化"出来,而新方案改为纯渲染层投影(pure Renderer projection):文件活动不是持久化的数据、不是磁盘差异记录,而是 Renderer 基于当前 ACP 时间线实时计算出来的视图。协议语法、安全模型与聚合语义则原样保留。
从源码结构看,这一设计的落地范围横跨渲染层、主进程与共享契约:
- 投影算法:src/lib/acp/openclaw-file-activities.ts(本任务的核心实现,也是权威类型定义处)
- 时间线分组:src/lib/acp/timeline-groups.ts
- 主进程工作区文件 API:electron/services/files-api.ts
- Host API 契约:shared/host-api/contract.ts(
WorkspaceFileRef等类型) - UI 组件:src/pages/Chat/AcpTurnFileActivity.tsx、src/pages/Chat/AcpFileCard.tsx
- 测试锚点:tests/unit/openclaw-file-activities.test.ts、tests/e2e/chat-file-changes.spec.ts
二、语义边界:文件活动"是什么、不是什么"
参考文档 harness/reference/openclaw-file-activity.md 开宗明义地划定了语义边界:
文件活动是:由成功的 OpenClaw 文件编辑类工具调用所声明的文件变更记录,是活跃 ACP 时间线的纯渲染层投影。
文件活动不是:Git diff、经过磁盘验证的差异、或会话开始时的基线快照。
与之配套的"非目标"(negative scope)约束同样严格。ClawX不会:
- 扫描或监听工作区(no workspace scanning/watching);
- 创建快照(no snapshots);
- 推断 shell/脚本副作用(no inferred shell side effects);
- 解析任意自然语言文本来猜测文件操作(no arbitrary prose parsing);
- 调用
sessions.files.list来人为制造差异(no manufactured diffs); - 持久化一份独立的"活动账本"(no persisted activity ledger)。
主进程(Main)不解释工具语义,只执行工作区作用域内的 read/stat 以及明确的原生文件操作。这一点在 tool-derived-file-safety.md 规则中被进一步固化:
File activity remains a record of completed canonical OpenClaw
write,edit, andapply_patchinputs. It must not claim to be a verified disk or Git diff, scan the workspace, infer shell effects, or persist a separate ledger.
同时,"支持的工具恰好是write、edit、apply_patch三种"这一判定规则也有明确的实现依据。在 openclaw-file-activities.ts 中,parseToolName会截取 ACP 工具 title 中第一个冒号之前的部分,做 trim + 小写归一化,只有精确等于三种受支持名称才被接受:
function parseToolName(title: string): OpenClawFileToolName | null { const colon = title.indexOf(':'); if (colon < 0) return null; const name = title.slice(0, colon).trim().toLowerCase(); return name === 'write' || name === 'edit' || name === 'apply_patch' ? name : null; }单元测试 tests/unit/openclaw-file-activities.test.ts 中 "normalizes exact title prefixes and rejects unsupported or malformed near-matches" 用例验证了这一点:' EdIt : b'、'APPLY_PATCH: c'会被归一化后接受,而'WriteFile: x'、'rewrite: x'、'exec: x'、'write file: x'这类"近似匹配"全部被拒绝。状态方面,只有completed状态的调用才产生活动;pending、running、failed、cancelled以及格式异常的调用,仍然作为普通工具卡片展示,但不会产生任何文件活动 UI。
三、Canonical Inputs:三种工具输入的规范语法
文件活动的产出完全由工具调用的**原始输入(canonical raw input)**驱动。参考文档对三种工具的字段解析规则做了精确约定,源码parseWrite/parseEdit/parseApplyPatch与之一一对应。
3.1 write:路径别名优先级与"空到新"片段
write与edit共用的路径字段优先级为:path→file_path→filePath→file(见readPath实现)。write接受字符串content,投影为一个"空到新"(empty-to-new)片段,动作记为created。需要特别说明:created描述的是工具意图,并不断言该文件此前一定不存在。如果只有合法路径而没有字符串 content,则产生一条"仅有路径"的记录,其行数统计标记为不可用(unavailable),而不是臆造为 0。
function parseWrite(input: Record<string, unknown>, context: PathContext): ParsedActivity[] { const candidate = readPath(input); if (!candidate) return []; const relativePath = resolveToolPath(candidate, context); if (!relativePath) return []; const fragments = typeof input.content === 'string' ? [{ oldText: '', newText: input.content }] : []; return [{ relativePath, action: 'created', fragments }]; }对应测试 "uses canonical path alias precedence and retains path-only Writes" 确认:当四个字段同时存在时只采用path,且 path-only 记录的added/removed为null。
3.2 edit:edits 数组 + 顶层兼容形状,拒绝宽泛别名
edit接受两种形态:
- 规范形态
edits: Array<{ oldText, newText }>; - 官方兼容形态:顶层直接给出
oldText/newText。
两类都会解析;数组中的非法条目(缺少字符串类型的oldText/newText)会被跳过;刻意不支持old_string、new_string这类宽泛别名。实现见validEditFragment与parseEdit:先展平edits数组,再把顶层的oldText/newText作为一条追加片段。
测试 "accepts only canonical array and top-level Edit pairs and skips invalid entries" 展示了一个典型输入:edits数组含三条(合法、缺 newText、空对空),外加顶层旧新文本,最终产生三条片段——说明缺 newText 的条目被静默跳过,空对空条目(oldText: ''与newText: ''均为字符串)则被保留。
3.3 apply_patch:信封、包裹器、Hunk 语法与"原子性失败"
apply_patch是语法最复杂的工具,解析器(parseApplyPatch/parsePatch/parsePatchHunk/parseUpdateChunk)实现了 OpenClaw patch 信封的完整文法:
- 信封:必须以
*** Begin Patch开头、*** End Patch结尾;可选包裹器为<<EOF、<<'EOF'、<<"EOF"(首行出现包裹器时,末行必须以EOF结尾并整行剥除,见unwrapAndValidatePatch)。 - 支持的节(sections):
*** Add File: <path>(+前缀行作为内容)、*** Update File: <path>、*** Delete File: <path>,以及 Update 之后可紧跟的*** Move to: <path>。 - Update 块语法:上下文行用空格前缀(同时计入 old/new 两侧),
-行只进 old 侧,+行只进 new 侧;第一个 Update chunk 允许省略@@上下文标记,后续 chunk 必须携带;*** End of File属于语法标记而非内容。 - 原子失败:语法错误会整体拒绝整个 tool payload——即使前面有本可通过的 hunk,也不产生任何部分活动(见测试 "atomically discards malformed apply-patch payloads":包含
'not-prefixed'非法行的 patch 使整个投影为空)。 - Move 语义:真实移动(归一化后源路径 ≠ 目标路径)产生"源删除 + 目标创建",且更新片段挂在目标上;归一化后相同(如
./same.txt移动到nested/../same.txt)则折叠为一条修改记录。
测试 "parses Add, Update, Delete, Move, CRLF, wrappers, chunks, empty context, and End of File" 用一个带 CRLF、单引号包裹器、多 chunk、空上下文、Move 的完整 patch 验证了全部语法分支;"collapses a same-normalized-path Move and splits a real Move" 验证折叠与拆分两条路径。
四、数据模型与聚合:从碎片到摘要再到会话分组
4.1 权威类型定义
openclaw-file-activities.ts 是数据模型的权威来源(参考文档明确指出 "The implementation types ... are authoritative"):
export type OpenClawFileToolName = 'write' | 'edit' | 'apply_patch'; export type AcpFileChangeFragment = { oldText: string; newText: string; sequence: number; }; export type AcpFileActivity = { turnId: string; toolCallId: string; toolName: OpenClawFileToolName; relativePath: string; action: 'created' | 'modified' | 'deleted'; fragments: AcpFileChangeFragment[]; sequence: number; }; export type AcpTurnFileSummary = { turnId: string; relativePath: string; action: 'created' | 'modified' | 'deleted'; activities: AcpFileActivity[]; added: number | null; removed: number | null; }; export type AcpSessionFileGroup = { relativePath: string; activities: AcpFileActivity[]; }; export type AcpFileActivityProjection = { activities: AcpFileActivity[]; turnSummariesByTurnId: Record<string, AcpTurnFileSummary[]>; fileGroups: AcpSessionFileGroup[]; uniqueFileCount: number; };要点:sequence是派生的展示顺序,不是持久化身份;turnId复用 ACP 展示分组算法(groupAcpTimelineItems,支持纯工具轮次,即没有用户消息的 assistant-turn),见 timeline-groups.ts。
4.2 三层聚合
投影入口projectOpenClawFileActivities的完整流程为:
- 用
createPathContext校验工作区上下文(见下节路径安全); - 遍历分组后的时间线,只处理
assistant-turn组中的tool-call项,过滤completed状态; - 用
Set<string>按toolCallId去重——同一工具调用的状态更新(in_progress → completed)不会产生重复活动(测试 "uses assistant group IDs for prose and tool-only turns and deduplicates toolCallId updates" 验证); - 按三种工具解析出
AcpFileActivity,随后构建三层输出:- 轮次摘要(
buildSummaries):同一轮次、同一相对路径折叠为一个摘要,动作按foldAction折叠(任一新活动为 deleted → deleted;有 created → created;否则 modified);added/removed由diffLines(来自diff库)按行统计,且先做 CRLF → LF 归一化(normalizeEol);缺失可统计片段时保持null,绝不臆造 0。 - 会话文件组(
buildFileGroups):按相对路径以首次活动出现顺序分组,组内轮次记录保持时间顺序。 - 轮次差异(
buildAcpTurnFileChanges):同一轮次同一文件的多个片段,先做去重(完全相同的 old/new 对只保留一次),再尝试安全组合——当"前一片段的新文本 == 后一片段的旧文本"时直接拼接;否则尝试在"整文档"片段上唯一替换(replaceUnique,要求旧文本唯一出现,否则不合并);仍无法合并的独立片段共享一个展示 diff,但不声称自己是累计补丁(cumulative patch)。测试 "folds same-turn same-path actions, sums counts, and preserves chronological file groups" 验证了created→edit→delete→recreate链条下动作折叠与行数求和的结果。
- 轮次摘要(
五、路径安全:WorkspaceFileRef 与双层校验
工具路径被视作不可信输入(untrusted)。这是整个功能的安全核心,规则层由 tool-derived-file-safety.md 定义,实现分渲染层与主进程两层。
5.1 渲染层:词法包含校验
渲染层先做词法(lexical)校验。workspaceRoot是包含边界(containment boundary),executionCwd是 ACP 工作目录。createPathContext要求:
- 根与 cwd 使用同一路径家族(posix / windows),且都是绝对路径;
- cwd 解析后必须落在 root 内部(
escapesRoot拒绝..、../x前缀以及绝对路径逃逸)。
resolveToolPath把相对路径相对executionCwd解析、把绝对路径直接解析,然后计算相对 root 的相对路径,一旦逃逸(包括跨家族的混用,如在 POSIX 上下文收到C:\...或C:relative)即返回null,该活动不产生。单元测试覆盖了大量对抗性用例:
- POSIX 下接受
src/a.txt与反斜杠src\b.txt,接受位于 root 内的绝对路径/workspace/c.txt,拒绝../../outside.txt、/workspace-collision/x.txt(词法前缀碰撞不算包含)、C:\workspace\x.txt、C:workspace\x.txt; - Windows 下正确处理盘符相对/绝对、跨盘符拒绝、UNC(
\\server\share)语义("uses win32 drive and UNC semantics cross-platform"); - 上下文本身非法(非绝对、混家族、cwd 在 root 之外)时整体不产生任何投影("rejects non-absolute, mixed-family, or out-of-root context before projection")。
参考文档还强调:没有权威 root 与 cwd 的 replay 不产生投影;渲染层的词法拒绝只是"明显的越界路径不出现活动 UI"。
5.2 主进程:独立规范校验 + WorkspaceFileRef
预览与显式原生动作自始至终使用相对引用(relative reference end to end):
type WorkspaceFileRef = { workspaceRoot: string; relativePath: string; };主进程(electron/services/files-api.ts)为每一次 read/stat/原生动作独立地重新做规范校验:
resolveWorkspaceTarget:拒绝绝对路径与含..的相对路径;对 root 做realpath并确认是目录;对候选路径realpath后再次确认在 root 内;对不存在的文件逐级上溯父目录,realpath最近的已存在父目录仍必须在 root 内;openWorkspaceTarget:以O_RDONLY | O_NOFOLLOW(非 Windows)打开,fstat后调用revalidateWorkspaceTarget核对 dev/ino,拒绝符号链接逃逸与"打开后目标被重定向"的 TOCTOU 场景;- 处理器发现(
listWorkspaceOpenHandlers)、选定处理器打开(openWorkspaceWith)、reveal(revealWorkspaceFile)都会重新解析WorkspaceFileRef;其中选定处理器打开在真正调用原生打开前还有一次额外的回调校验(() => resolveWorkspaceRegularFile(payload.ref, fsP))。
渲染层永远不会向主进程发送"主进程规范化后的裸路径、可执行路径、命令或命令模板";主进程在后一次的拒绝只会让历史活动保留,而拒绝对应的文件操作本身。
5.3 动作可见性规则
- 工具派生的目标(tool-derived targets)一律是只读的应用内预览,绝不使用裸路径 shell API;
created/modified活动可以暴露独立的Open with菜单(原生动作只由工作区作用域的 Host API 操作支撑),以及 Linux 上可行的 reveal;deleted活动两者都不暴露;- HTML 活动:Open with 菜单首先提供"浏览器导航到由有效工作区 root 与受包含的相对路径构造的本地文件 URL",这是预览导航而非原生处理器动作。
参考文档确认 src/pages/Chat/AcpFileCard.tsx 提供附件/文件活动共用的展示外壳与感知目标的菜单(不共享授权);限制大小内的 DOCX/PPTX 活动通过其WorkspaceFileRef进入 Office 查看器,解析与单查看器约束记录在 harness/reference/office-document-preview.md。
六、与附件的严格分离:两条互不串扰的管线
文件活动与用户可见的附件是两个独立的投影与安全边界:
- 工具输入/输出中偶然出现的路径,仍只是"工具派生的证据":不能变成附件卡片、不能解析到工作区之外、不能使用附件作用域的授权;
- 附件证据只能来自:标准 ACP 资源内容、主进程持有的用户暂存记录(staging record),或受约束的显式助手
MEDIA:兼容性例外(详见 harness/reference/acp-generated-media-and-diagnostics.md 的 bounded-transcript-exceptions 一节)。
主进程只在 ACP 会话加载/创建成功后才建立附件会话与相对路径上下文;每一次附件解析、预览读取、系统或外部打开都要重新校验确切的会话、generation、引用与规范目标——附件证据可以解析到工作区之外。而文件活动永远不进入附件管线:其显式原生动作通过WorkspaceFileRef被严格限制在规范工作区内。完整的附件边界见 harness/reference/acp-attachment-access-control.md。
七、用户体验与 Replay 行为
场景文档 harness/specs/scenarios/acp-file-activity.md 与参考文档定义了用户可见行为,expectedUserBehavior与acceptance字段(见任务规范)进一步固化了验收口径:
- 轮次级:每个 assistant turn 对每个符合条件的路径显示一个文件按钮与一条摘要。
created/modified按钮打开当前文件的 Preview 并带 Open with;deleted按钮打开 Changes 且没有 Open with。实现见 src/pages/Chat/AcpTurnFileActivity.tsx:摘要行内嵌+added/-removed(绿色/红色)计数,仅当两者都非null时渲染。 - 会话级 Changes:按文件分组、按首次活动顺序排列、每组轮次记录按时间顺序;每轮次每文件最多一个 diff 编辑器(
buildAcpTurnFileChanges的输出即此约束的实现)。 - 空会话提示:没有任何合格活动的新会话明确显示"该会话尚无文件变更"。
- 图标一致性:Changes 中的文件头部使用与 Workspace 文件树一致的、按扩展名感知的 Material 文件图标,而非通用变更图标(任务规范
acceptance明确 "use the shared Material file icon instead of a generic change icon")。 - 多视图预览:支持多视图的预览,其分段切换器共享文件头(名称/路径)的尾部一侧,不独占一行;HTML 文件暴露
Preview与Source两个视图,默认沙箱化渲染预览,切换视图时保持同一作用域读取结果。 - Replay 语义:完整的 ACP 结构化 replay 通过同一投影恢复全部可用活动;仅凭 transcript 或不完整的 replay不会推断缺失的记录;切换会话时投影随活跃时间线一起清空。
任务规范同时列出验证锚点:单元测试 tests/unit/openclaw-file-activities.test.ts、tests/unit/files-api-workspace.test.ts 与文件预览组件测试套件,以及端到端测试 tests/e2e/chat-file-changes.spec.ts(后者通过 IPC mock 与录制的主进程调用断言,在 tests/e2e/fixtures/electron.ts 的辅助下验证"工具派生活动绝不触发无界 fallback 读取/打开")。
八、验收标准速览与工程约束
任务规范acceptance字段汇总了最终可验证的工程口径,可直接作为回归清单:
| 编号 | 验收点 |
|---|---|
| 1 | 只有completed且为规范原始输入(canonical raw inputs)的write/edit/apply_patch才产生文件活动 |
| 2 | 失败与不支持的工具仍显示为普通工具卡片,但不产生任何文件活动 UI |
| 3 | 工具派生预览只使用工作区作用域的 read/stat Host API,无无界 fallback;后续工作区作用域原生动作独立重新校验WorkspaceFileRef,绝不接受渲染层发来的裸规范路径 |
| 4 | 功能不扫描工作区、不使用 Git、不创建源快照、不推断 shell 副作用 |
| 5 | 完整 ACP replay 恢复可用活动,不完整 replay 不虚构活动 |
| 6 | Changes 文件头使用共享 Material 文件图标而非通用变更图标 |
| 7 | 同一轮次同一文件的碎片在安全时组合为一个展示 diff,否则拼接为一个展示 diff |
配合这些验收点,仓库还要求 docs-sync.md、ui-i18n-design-tokens.md(对应 shared/i18n/locales 下 en/zh/ja/ru 的chat.json中fileActivity词条)等规则生效,并通过pnpm run typecheck、pnpm test、pnpm run test:e2e -- tests/e2e/chat-file-changes.spec.ts、pnpm run comms:replay、pnpm run comms:compare持续回归(comms相关脚本位于 scripts/comms)。
结语
restore-acp-file-activity代表了 ClawX 在 ACP 文件活动上的一次设计收敛:用"纯渲染层投影 + 双进程分层校验"替代了旧的 hydration 思路,既恢复了用户在 Chat 中直观查看 Agent 文件操作的能力(轮次按钮、摘要、会话 Changes、应用内只读预览与受约束的 Open with),又把"工具派生的文件访问"牢牢锁进WorkspaceFileRef定义的工作区边界。对于希望在 ACP 协议之上构建文件可视化与安全访问层的开发者,本文所梳理的规范语法、聚合模型与双层路径校验,正是可直接对照 openclaw-file-activities.ts 与 files-api.ts 落地的完整蓝图。
- 人工智能
- 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 文件活动机制:OpenClaw 工具调用到工作区预览的安全投影与实操解析
ClawX 的 ACP 文件活动机制:OpenClaw 工具调用到工作区预览的安全投影与实操解析 本指南围绕 ClawX 仓库中的 acp file activ
人工智能AI 应用桌面应用交互助手ClawX ACP 媒体附件渲染与 OpenClaw 有界转录兼容投影实战指南
ClawX ACP 媒体附件渲染与 OpenClaw 有界转录兼容投影实战指南 本文导读 :ClawX 在 ACP 原生聊天界面中提供完整的媒体附件体验——标准
人工智能AI 应用桌面应用交互助手ClawX 文件活动机制深度解析:基于 ACP 时间线的 OpenClaw 文件变更投影与工作区安全边界
ClawX 文件活动机制深度解析:基于 ACP 时间线的 OpenClaw 文件变更投影与工作区安全边界 导读 本文以 openclaw file activi
人工智能AI 应用桌面应用交互助手
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考