17.3 Canvas 工具

生成模型: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 操作

工具注册与 Schema

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 个独立工具。

Gateway 代理模式

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),支持精确定位

hide:隐藏 Canvas

最简单的操作——无参数,直接隐藏。

在已显示的 Canvas WebView 中导航到新的 URL,不需要先 hide 再 present。

eval:执行 JavaScript

eval 在 Canvas WebView 中执行任意 JavaScript 代码,并返回执行结果。这是 Canvas 最灵活的操作——Agent 可以用它来动态修改页面、读取 DOM 状态、触发事件等。返回值如果存在,会作为文本内容返回给 Agent。

snapshot:截取快照

snapshot 是 Canvas 工具中最复杂的操作:

  1. 发送 canvas.snapshot 命令到节点,节点在 WebView 中截图

  2. 节点返回 base64 编码的图片数据

  3. parseCanvasSnapshotPayload() 解析负载(验证 formatbase64 字段)

  4. writeBase64ToFile() 将 base64 写入临时文件(/tmp/openclaw-canvas-snapshot-{uuid}.{ext}

  5. imageResult 格式返回,Agent 可以"看到"截图内容

快照临时文件的路径格式:

a2ui_push:推送 A2UI 数据

a2ui_push 支持两种输入方式:

  • jsonl——直接在参数中提供 JSONL 字符串(适用于少量 UI 指令)

  • jsonlPath——指定 JSONL 文件路径(适用于大量 UI 指令,Agent 先写文件再推送)

a2ui_reset:重置 A2UI 状态

清除 A2UI 渲染器的所有状态,恢复到初始空白状态。

七个动作总结

动作
Gateway 命令
必要参数
返回

present

canvas.present

无(可选 target、placement)

{ ok: true }

hide

canvas.hide

{ ok: true }

navigate

canvas.navigate

url

{ ok: true }

eval

canvas.eval

javaScript

文本结果或 { ok: true }

snapshot

canvas.snapshot

无(可选 format、maxWidth)

图片(base64 + 文件)

a2ui_push

canvas.a2ui.pushJSONL

jsonljsonlPath

{ ok: true }

a2ui_reset

canvas.a2ui.reset

{ ok: true }


17.3.2 Canvas 在 macOS / iOS / Android 上的表面

节点命令策略

Canvas 命令在不同平台上的可用性由 node-command-policy.ts 中的节点命令策略控制:

Canvas 在 iOS、Android、macOS 三个平台上可用,在 Linux 和 Windows 上不可用。这是因为 Canvas 依赖原生应用的 WebView——Linux 和 Windows 上的 OpenClaw 以无头(headless)模式运行,没有图形界面。

命令访问控制

节点命令策略实现了三重访问控制:

检查层级
说明
示例

平台允许列表

基于节点平台的默认命令

iOS 默认允许 Canvas + Camera

配置扩展

gateway.nodes.allowCommands 可添加额外命令

允许 Linux 节点使用 Canvas

配置禁止

gateway.nodes.denyCommands 可屏蔽命令

禁止 Android 节点截图

节点声明

节点注册时声明自己支持的命令

节点未声明 Canvas 则不可用

各平台的 WebView 实现

Canvas 工具通过 Gateway 向节点发送命令,但实际的 WebView 渲染发生在原生应用侧。不同平台的实现:

平台
WebView 技术
Canvas 实现位置
特性

macOS

WKWebView

apps/macos/ (Swift)

桌面窗口,支持 placement 定位,支持 system.run

iOS

WKWebView

apps/ios/ (Swift)

全屏或分屏,安全区域适配,支持 camera/location

Android

android.webkit.WebView

apps/android/ (Kotlin)

全屏或分屏,高对比度主题,支持 SMS

三个平台的 WebView 都需要:

  1. 加载 Canvas Host URL——通常是 http://<gateway-host>:<canvas-port>/__openclaw__/a2ui

  2. 注入 JS Bridge——iOS 通过 WKScriptMessageHandler,Android 通过 @JavascriptInterface

  3. 处理节点命令——接收 canvas.present/canvas.eval 等命令并在 WebView 中执行

CLI 命令行接口

除了 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 为用户生成数据可视化:


本节小结

  1. Canvas 工具将 7 个动作统一为一个扁平化 Schema,LLM 只需学习一个工具接口

  2. 所有操作都通过 callGatewayTool("node.invoke") 代理到目标节点执行,不直接与 Canvas Host 通信

  3. snapshot 是最复杂的操作,涉及节点截图 → base64 编码 → 解析负载 → 写入临时文件 → 返回图片结果

  4. a2ui_push 支持内联 JSONL 和文件路径两种输入方式,CLI 额外支持 --text 快捷方式

  5. Canvas 在 iOS / Android / macOS 可用,Linux / Windows 因缺乏 WebView 而不支持

  6. 节点命令策略实现四重访问控制:平台默认 → 配置扩展 → 配置禁止 → 节点声明

  7. CLI 和 Agent 工具共享相同的底层调用链,都通过 Gateway 的 node.invoke 到达目标节点

Last updated