* fix(macos): stop Canvas presents from stealing focus Agent-driven canvas.present/canvas.navigate activated the app with NSApp.activate(ignoringOtherApps:), yanking keyboard focus from whatever the user was doing. The Canvas panel's elevated window level already keeps it visible without app activation, and user entry points (AppNavigationActions) activate explicitly, so presentation now orders the panel front without switching apps. * fix(macos): order Canvas front without taking key focus * fix(macos): drop showWindow from Canvas window presentation
5.3 KiB
summary, read_when, title
| summary | read_when | title | |||
|---|---|---|---|---|---|
| Agent-controlled Canvas panel embedded via WKWebView + custom URL scheme |
|
Canvas |
The macOS app embeds an agent-controlled Canvas panel using WKWebView, a
lightweight visual workspace for HTML/CSS/JS, A2UI, and small interactive UI
surfaces.
Where Canvas lives
Canvas state is stored under Application Support:
~/Library/Application Support/OpenClaw/canvas/<session>/...
The Canvas panel serves those files via a custom URL scheme,
openclaw-canvas://<session>/<path>:
openclaw-canvas://main/-><canvasRoot>/main/index.htmlopenclaw-canvas://main/assets/app.css-><canvasRoot>/main/assets/app.cssopenclaw-canvas://main/widgets/todo/-><canvasRoot>/main/widgets/todo/index.html
If no index.html exists at the root, the app shows a built-in scaffold page.
Panel behavior
- Borderless, resizable panel anchored near the menu bar (or mouse cursor).
- Presenting Canvas does not switch apps or steal keyboard focus.
- Remembers size/position per session.
- Auto-reloads when local canvas files change.
- Only one Canvas panel is visible at a time (session switches as needed).
Canvas can be disabled from Settings -> Allow Canvas. When disabled,
canvas node commands return CANVAS_DISABLED.
Agent API surface
Canvas is exposed via the Gateway WebSocket, so the agent can show/hide the panel, navigate to a path or URL, evaluate JavaScript, and capture a snapshot image:
openclaw nodes canvas present --node <id>
openclaw nodes canvas navigate --node <id> "/"
openclaw nodes canvas eval --node <id> --js "document.title"
openclaw nodes canvas snapshot --node <id>
eval and a2ui.* update content without opening or revealing the panel. Only
present, navigate, or a user action shows it; after a hide, content updates
continue to apply to the hidden panel. snapshot needs a visible panel and
returns CANVAS_HIDDEN otherwise; run present first.
canvas.navigate accepts local canvas paths, http(s) URLs, and file://
URLs. Passing "/" shows the local scaffold or index.html.
Gateway-hosted targets under /__openclaw__/canvas/ and
/__openclaw__/a2ui/ are resolved through the node session's current scoped
Canvas URL. The app refreshes that short-lived capability before navigation;
you do not need to construct or copy a capability URL yourself.
A2UI in Canvas
A2UI is hosted by the Gateway canvas host and rendered inside the Canvas panel. When the Gateway advertises a Canvas host, the macOS app auto-navigates to the A2UI host page on first open.
The advertised URL is capability-scoped, for example
http://<gateway-host>:18789/__openclaw__/cap/<token>/__openclaw__/a2ui/?platform=macos.
Treat it as ephemeral credentials, not a stable link.
A2UI commands (v0.8)
Canvas accepts A2UI v0.8 server-to-client messages: beginRendering,
surfaceUpdate, dataModelUpdate, deleteSurface. createSurface (v0.9) is
not supported yet.
cat > /tmp/a2ui-v0.8.jsonl <<'EOFA2'
{"surfaceUpdate":{"surfaceId":"main","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","content"]}}}},{"id":"title","component":{"Text":{"text":{"literalString":"Canvas (A2UI v0.8)"},"usageHint":"h1"}}},{"id":"content","component":{"Text":{"text":{"literalString":"If you can read this, A2UI push works."},"usageHint":"body"}}}]}}
{"beginRendering":{"surfaceId":"main","root":"root"}}
EOFA2
openclaw nodes canvas a2ui push --jsonl /tmp/a2ui-v0.8.jsonl --node <id>
Quick smoke test:
openclaw nodes canvas a2ui push --node <id> --text "Hello from A2UI"
Triggering agent runs from Canvas
Canvas can trigger new agent runs via openclaw://agent?... deep links:
window.location.href = "openclaw://agent?message=Review%20this%20design";
Supported query parameters:
| Parameter | Meaning |
|---|---|
message |
Prefilled agent prompt. |
sessionKey |
Stable session identifier. |
thinking |
Optional thinking profile. |
deliver, to, channel |
Delivery target. |
timeoutSeconds |
Optional run timeout. |
key |
App-generated safety token for trusted local callers. |
The app prompts for confirmation unless a valid key is provided. Unkeyed links show the message and URL before approval, and ignore delivery routing fields; keyed links use the normal Gateway run path.
Security notes
- Canvas scheme blocks directory traversal; files must live under the session root.
- Local Canvas content uses a custom scheme (no loopback server required).
- External
http(s)URLs are allowed only when explicitly navigated. - Ordinary web pages are render-only. Agent actions are accepted only from the app-owned Canvas scheme or the exact capability-scoped Gateway A2UI document selected by the app; subframes, redirects, stale capabilities, and changed queries cannot dispatch actions.