生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~190k tokens,输出 ~5k tokens(本节)
前两节分别介绍了 Canvas/A2UI 的概念和 Canvas Host 的实现。本节将分析 Agent 如何通过 Canvas 工具(src/agents/tools/canvas-tool.ts)操控 Canvas,以及 Canvas 在不同平台上的表面实现。
17.3.1 push / reset / eval / snapshot 操作
Canvas 工具定义了 7 个动作(action),统一在一个扁平的 TypeBox Schema 中:
// src/agents/tools/canvas-tool.ts
const CANVAS_ACTIONS = [
"present", // 显示 Canvas
"hide", // 隐藏 Canvas
"navigate", // 导航到指定 URL
"eval", // 在 Canvas 中执行 JavaScript
"snapshot", // 截取 Canvas 快照
"a2ui_push", // 推送 A2UI JSONL 数据
"a2ui_reset", // 重置 A2UI 渲染状态
] as const;
const CanvasToolSchema = Type.Object({
action: stringEnum(CANVAS_ACTIONS), // 必填:动作类型
node: Type.Optional(Type.String()), // 目标节点
// present 专用
target: Type.Optional(Type.String()), // URL 或路径
x: Type.Optional(Type.Number()), // 放置坐标
y: Type.Optional(Type.Number()),
width: Type.Optional(Type.Number()), // 放置尺寸
height: Type.Optional(Type.Number()),
// navigate 专用
url: Type.Optional(Type.String()),
// eval 专用
javaScript: Type.Optional(Type.String()),
// snapshot 专用
outputFormat: optionalStringEnum(["png", "jpg", "jpeg"]),
maxWidth: Type.Optional(Type.Number()),
quality: Type.Optional(Type.Number()),
delayMs: Type.Optional(Type.Number()),
// a2ui_push 专用
jsonl: Type.Optional(Type.String()), // 内联 JSONL
jsonlPath: Type.Optional(Type.String()), // JSONL 文件路径
});
这是一个扁平化设计——所有动作的参数混在一个 Schema 中,运行时根据 action 字段验证必要参数。这样做的好处是 LLM 只需要学习一个工具的参数结构,而非 7 个独立工具。
Canvas 工具的所有操作都不直接与 Canvas Host 通信,而是通过 Gateway 的 node.invoke 进行转发:
数据流:
每次调用都附带一个 idempotencyKey(幂等键),确保网络重传不会导致操作被重复执行。
present:显示 Canvas
present 可以指定:
target——要加载的 URL(通常指向 Canvas Host 的路径,如 http://localhost:PORT/__openclaw__/canvas/)
placement——Canvas 窗口在屏幕上的位置和大小(x/y/width/height),支持精确定位
最简单的操作——无参数,直接隐藏。
navigate:导航到 URL
在已显示的 Canvas WebView 中导航到新的 URL,不需要先 hide 再 present。
eval:执行 JavaScript
eval 在 Canvas WebView 中执行任意 JavaScript 代码,并返回执行结果。这是 Canvas 最灵活的操作——Agent 可以用它来动态修改页面、读取 DOM 状态、触发事件等。返回值如果存在,会作为文本内容返回给 Agent。
snapshot 是 Canvas 工具中最复杂的操作:
发送 canvas.snapshot 命令到节点,节点在 WebView 中截图
parseCanvasSnapshotPayload() 解析负载(验证 format 和 base64 字段)
writeBase64ToFile() 将 base64 写入临时文件(/tmp/openclaw-canvas-snapshot-{uuid}.{ext})
以 imageResult 格式返回,Agent 可以"看到"截图内容
快照临时文件的路径格式:
a2ui_push:推送 A2UI 数据
a2ui_push 支持两种输入方式:
jsonl——直接在参数中提供 JSONL 字符串(适用于少量 UI 指令)
jsonlPath——指定 JSONL 文件路径(适用于大量 UI 指令,Agent 先写文件再推送)
a2ui_reset:重置 A2UI 状态
清除 A2UI 渲染器的所有状态,恢复到初始空白状态。
17.3.2 Canvas 在 macOS / iOS / Android 上的表面
Canvas 命令在不同平台上的可用性由 node-command-policy.ts 中的节点命令策略控制:
Canvas 在 iOS、Android、macOS 三个平台上可用,在 Linux 和 Windows 上不可用。这是因为 Canvas 依赖原生应用的 WebView——Linux 和 Windows 上的 OpenClaw 以无头(headless)模式运行,没有图形界面。
节点命令策略实现了三重访问控制:
gateway.nodes.allowCommands 可添加额外命令
gateway.nodes.denyCommands 可屏蔽命令
各平台的 WebView 实现
Canvas 工具通过 Gateway 向节点发送命令,但实际的 WebView 渲染发生在原生应用侧。不同平台的实现:
平台
WebView 技术
Canvas 实现位置
特性
桌面窗口,支持 placement 定位,支持 system.run
全屏或分屏,安全区域适配,支持 camera/location
三个平台的 WebView 都需要:
加载 Canvas Host URL——通常是 http://<gateway-host>:<canvas-port>/__openclaw__/a2ui
注入 JS Bridge——iOS 通过 WKScriptMessageHandler,Android 通过 @JavascriptInterface
处理节点命令——接收 canvas.present/canvas.eval 等命令并在 WebView 中执行
除了 Agent 工具,Canvas 操作也可以通过 CLI 直接调用:
CLI 实现在 src/cli/nodes-cli/register.canvas.ts 中,它与 Agent 工具共享相同的 invokeCanvas() 辅助函数,最终都通过 callGatewayCli("node.invoke", ...) 调用 Gateway。
CLI 的 a2ui push 命令还额外支持 --text 参数——快速将纯文本转换为 A2UI JSONL 格式,无需手动编写 JSONL:
注意版本检查:当前 OpenClaw 只支持 A2UI v0.8 协议,如果检测到 v0.9 的 createSurface 指令会报错。
一个完整的 Canvas 使用场景——Agent 为用户生成数据可视化:
Canvas 工具将 7 个动作统一为一个扁平化 Schema,LLM 只需学习一个工具接口
所有操作都通过 callGatewayTool("node.invoke") 代理到目标节点执行,不直接与 Canvas Host 通信
snapshot 是最复杂的操作,涉及节点截图 → base64 编码 → 解析负载 → 写入临时文件 → 返回图片结果
a2ui_push 支持内联 JSONL 和文件路径两种输入方式,CLI 额外支持 --text 快捷方式
Canvas 在 iOS / Android / macOS 可用,Linux / Windows 因缺乏 WebView 而不支持
节点命令策略实现四重访问控制:平台默认 → 配置扩展 → 配置禁止 → 节点声明
CLI 和 Agent 工具共享相同的底层调用链,都通过 Gateway 的 node.invoke 到达目标节点