--- name: winnode description: Invoke and troubleshoot OpenClaw Windows-node commands through the local winnode CLI and MCP endpoint, including command argument schemas and A2UI JSONL calls. --- # winnode skill reference `winnode.exe` invokes OpenClaw Windows-node commands on the local tray over a loopback MCP HTTP endpoint (default `http://127.0.0.1:8765/`). Enable **Local MCP Server** in Settings > Permissions > Integrations before calling. A connection refused on that port means the tray is not listening. Do not reinstall the CLI. Start the tray with `EnableMcpServer` true, then point `OPENCLAW_TRAY_DATA_DIR` at that tray's data directory so the CLI loads `mcp-token.txt`. `browser.proxy` stays off `tools/list` until the app has a gateway client, a shared token, and a verified browser endpoint. The call then returns `Unknown tool: browser.proxy`. This document is the agent-facing reference: every supported command, its argument shape, and the A2UI v0.8 JSONL grammar. It is shipped alongside `winnode.exe` so an agent can read it once and emit token-efficient calls. --- ## Invocation shape ```text winnode --command [--params ''] [--invoke-timeout ] [--identity release|dev] winnode --list-tools [--mcp-url |--mcp-port ] [--identity release|dev] ``` - `--command` (required) - node command (e.g. `system.which`, `canvas.a2ui.push`). - `--list-tools` - query the live MCP server's `tools/list` method and print the advertised tools. Useful when settings-gated capabilities differ from this static reference. - `--params` - single JSON **object** string, default `{}`. Must be a JSON object, not an array or scalar. **`--params @`** loads the JSON object from a file on disk (useful for big A2UI payloads / `canvas.eval` scripts). - `--invoke-timeout` - milliseconds, default 15000, max 600000 (10 min). HTTP timeout adds a 5s buffer. - `--node` - accepted for parity with `openclaw nodes invoke`; **ignored** locally. Safe to copy/paste from gateway-side commands. - `--idempotency-key` - accepted for parity; **ignored**, and the CLI emits a `[winnode] WARN` to stderr because local MCP does *not* dedupe retries - re-running a command after a transient failure can double-execute side effects. If you need idempotency, target the gateway, not winnode. - `--mcp-url ` / `--mcp-port ` - override the endpoint. Falls back to `OPENCLAW_MCP_PORT` env var, then port 8765. `--mcp-port` must be in `[1, 65535]`; out of range fails with exit code 2. - `--mcp-token ` - bearer token override (testing / explicit only). The literal value is **visible to other same-user processes via the OS process listing** (`Get-CimInstance Win32_Process | Select CommandLine`, Process Explorer, etc.). The CLI emits a stderr warning when this flag is used. **Prefer `OPENCLAW_MCP_TOKEN` (env var) or the on-disk `%APPDATA%\OpenClawTray\mcp-token.txt`** which the release tray writes when MCP is enabled. Both `OPENCLAW_MCP_TOKEN` and the on-disk file should themselves be treated as sensitive operational secrets. - `--identity release|dev` - selects which tray profile supplies the default on-disk MCP token. Defaults to `OPENCLAW_APP_IDENTITY`, then `release`. Use `--identity dev` for a side-by-side dev tray; its default token path is `%APPDATA%\OpenClawTray-Dev\mcp-token.txt`. `OPENCLAW_TRAY_DATA_DIR` still wins for isolated runs and points directly at the data folder. - `--verbose` - log endpoint + ignored flags to stderr. Without `--verbose`, HTTP error bodies are emitted only as the first line; with `--verbose`, the full body is shown (after sanitization + token-shape redaction). - `--input-file ` - raw local input for `file.create`. Computes size and SHA256, checks any caller-supplied values, and streams bounded messages. - `--output-file ` - raw local output for `file.fetch`; requires explicit positive `maxBytes`. Never overwrites. Validates returned size and SHA256 before publishing its owned partial file at the requested destination. Both options use one `--invoke-timeout` deadline, preserve supplied expected identity, and use ordinary preflight only to fill missing binding/path fields. **Output contract:** stdout receives the capability payload as pretty-printed JSON (matches `openclaw nodes invoke`). stderr receives errors. Exit code: | Code | Meaning | |------|---------| | 0 | Success | | 1 | Tool error, JSON-RPC error, transport failure, or HTTP non-2xx | | 2 | Argument error (missing/invalid flags, bad `--params` JSON, out-of-range port/timeout, non-http URL) | **Off-loopback safety:** when `--mcp-url` points at a non-loopback host, the CLI **refuses to send the auto-loaded local MCP token** (and warns on stderr). An explicitly supplied `--mcp-token` is honored with a warning. This preserves the loopback-only threat model the tray's MCP server relies on. --- ## Commands ## Files and directories All eight commands use the **existing Node Sandbox configuration and executor**, the same as `system.run`. Supported Windows runs the fixed worker through MXC. Positively unsupported Windows uses explicit uncontained compatibility, with no Sandbox filesystem/network enforcement; failed probes, SDK or launch errors do not downgrade. Existing Node/MCP enablement applies, without new file permission switches or shell exec approval prompts. Remote Gateway trust/allowlists and admin checks remain independent. Settings changes apply to new operations only. Unary reads/archives default to 8 MiB and cap at 16 MiB. Writes/uploads cap at 16 MiB decoded. CLI file envelopes use a command-specific bounded HTTP allowance, not an increase to every tool's request/response limit. Ordinary MCP clients can use unary and preflight calls without the optional framed extension. ### file.stat Inspect metadata: `{ "path": "C:\\Data\\sample.txt" }`. Optional `followSymlinks`, `preflightOnly`, `expectedCanonicalPath`, `expectedBinding`. Returns `{ok,path,type,size,mtimeMs,binding}` and an opaque identity `binding`. Preserve returned bindings without rewriting IDs. Ordinary local drive paths are supported. Reparse points, Cloud Files hydration, case-sensitive directories and `followSymlinks:true` fail explicitly. ### file.fetch Unary read: `{ "path": "C:\\Data\\sample.txt", "maxBytes": 16777216 }`. Returns `{ok,path,size,mimeType,base64,sha256,binding}`. Optional shared identity fields and `preflightOnly`. For binary, require `transport:"binary"`, explicit positive `maxBytes`, an existing `expectedBinding`, and framed IO. ```powershell winnode --command file.fetch --params '{"path":"C:\\Data\\sample.bin","maxBytes":52428800}' --output-file .\received.bin --invoke-timeout 60000 ``` ### dir.list `{ "path": "C:\\Data", "maxEntries": 200, "pageToken": "0" }`. Returns entries, `nextPageToken`, path and binding. Page size caps at 5000. Children blocked by the current Sandbox policy are omitted, and paging counts only visible entries. A blocked target directory still returns a permission error. Optional `preflightOnly`, `followSymlinks` and shared identity fields. ### dir.fetch `{ "path": "C:\\Data", "maxBytes": 16777216 }`. Returns `tarBase64`, `tarBytes`, `sha256`, `fileCount`, entries and binding. Expanded archive data caps at 64 MiB; entry count caps at 5000. Optional `preflightOnly`, `followSymlinks` and shared identity fields. ### file.write `{ "path": "C:\\Data\\sample.txt", "contentBase64": "YWJj", "overwrite": false }`. Accepts canonical standard/base64url, padded or unpadded. Optional `createParents`, `rejectHardlinks`, `expectedSha256`, `expectedCanonicalPath`, `expectedBinding`, `preflightOnly`. Returns `{ok,path,size,sha256,overwritten,binding}`. No overwrite by default. ### file.create Requires `path`, `sizeBytes`, `expectedSha256`. Optional `maxBytes` (default 50 MiB), `createParents`, identity fields and `preflightOnly`. Preflight needs no channel; real creation streams messages of at most 1 MiB, waits for an `ack` after each, and sends an empty message for EOF. Returns `{ok,path,size,sha256,status:"created",binding}` when created. An existing target returns `status:"exists"` without size/hash fields and is not read back to verify content equality. Never overwrites. ```powershell winnode --command file.create --params '{"path":"C:\\Data\\new.bin"}' --input-file .\source.bin --invoke-timeout 60000 ``` ### fs.listDir Directory navigation, with optional `{ "path": "C:\\Data" }`. Remote calls are Gateway-admin-gated; local calls use the existing MCP bearer. Neither grants elevation or bypasses contained data access. Children blocked by the current Sandbox policy are omitted; requesting a blocked directory itself still fails. ### terminal.upload `{ "name": "sample.txt", "contentBase64": "YWJj" }`. Requires canonical padded standard base64. Returns `{path,size}`. The raw node command stages an upload without a terminal session. Remote calls require genuine Gateway admin authorization. The app schedules retained cleanup; the same mode-selected worker scans and deletes only identity-owned upload data. New uploads use automatic private storage belonging to the current Companion profile, outside its settings and runtime-code folders. Downloads and the legacy upload-location setting receive no automatic grant. Contained ordinary commands can read all retained uploads in that profile after ownership verification, but cannot modify them through the automatic grant. Only upload/cleanup operations receive managed-storage write access. Explicit read-only/No access restrictions still apply, including an inherited read-only policy. This is profile-scoped sharing, not per-file or per-conversation isolation. The app establishes only the exact private profile boundary as trusted metadata before launching the contained worker. It checks current policy and reserves cleanup ownership first. Uploaded bytes and cleanup still run inside the worker, without granting write access to the boundary's parent. Cancellation or launch failure can leave an owned empty boundary for later safe reuse. Unbound or unrecognized ownership requires review, not automatic adoption or deletion. The committed transfer budget defaults to 50 MiB and cannot exceed 1024 MiB; valid existing settings remain effective. This command still has the tighter 16 MiB decoded unary limit. Retention remains bounded to 24 hours, 64 upload directories and 256 MiB. Cleanup of remembered legacy roots still requires their current folder permissions and ownership identity. A blocked cleanup is not a reason to retry a completed upload. **Error semantics:** the six plugin commands retain the Gateway outer success envelope with inner `{ok:false,code,message}`. The two admin commands use outer errors. MCP converts typed semantic failure to `isError:true`; `winnode` exits 1. An explicit binary/create call without a channel returns `DUPLEX_REQUIRED`. See [local framed transport](../../../docs/MCP_MODE.md#file-channel-extension-v1) for the optional authenticated lease protocol. It is not standard MCP streaming. ### system.notify Show a Windows toast notification. ``` {"title": "OpenClaw", "body": "string", "subtitle": "string", "sound": true} ``` Returns `{ "sent": true }`. All fields optional except `body` in practice. ### system.run Execute canonical argv. Subject to the local exec approval policy at `%APPDATA%\OpenClawTray\exec-approvals.json`. Supported Windows uses `Microsoft.Mxc.Sdk` 1.0.0 containment. There is no Off setting. Positively unsupported Windows instead runs approved argv uncontained, with a persistent Node Sandbox warning and disabled sandbox permission controls. File/network/clipboard/UI restrictions are not enforced there; approvals, deadline, bounded output and revalidation remain active. Missing SDK components, unknown/failed probes or native execution failures never downgrade. On supported Windows, unsupported/unavailable policy and stale authorization are tool errors (`isError=true`). Balanced reads the OS user profile and resolved personal/redirected folders; Open reads/writes the same scope. Neither grants a whole drive or other users. Projects outside that scope need explicit Custom grants. Readonly profile plus selected write children is supported. Volume roots are never granted, even readonly. Missing required locations block with diagnostics; optional unavailable OneDrive is not included. Protected OpenClaw settings, SSH, browser-profile and PSReadLine roots remain excluded in every scope. Explicit cwd must already be granted; omitted cwd uses private scratch. Settings affect new commands only. Pending requests revalidate before spawn. Cancellation dismisses approval, releases the prompt lock, and explicitly terminates the owned SDK process, including a late native launch. ``` { "command": ["executable", "arg", ...], // required "rawCommand": "string", // optional display metadata "cwd": "string", "timeoutMs": 30000 } ``` Shell behavior must be explicit in the argv, for example `{"command":["cmd.exe","/d","/s","/c","echo hello"],"rawCommand":"echo hello"}`. The gateway's `exec host=node` path performs this wrapping automatically. Non-empty custom `env` is rejected until environment values can be identity-bound and shown safely during approval. #### Migration from the pre-V2 low-level contract The V2 node boundary intentionally rejects string-form `command` with `command-array-required`. Update raw MCP, `node.invoke`, plugin, and direct `winnode` callers to send the shell transport explicitly: ```json // Before {"command":"echo hello","shell":"cmd"} // V2 {"command":["cmd.exe","/d","/s","/c","echo hello"],"rawCommand":"echo hello"} ``` PowerShell callers must likewise name `powershell.exe` or `pwsh.exe` and its `-Command` arguments explicitly. Remove custom `env` from the request; V2 rejects non-empty environments rather than approving an executable under hidden process configuration. Exited commands return `{ stdout, stderr, exitCode, timedOut, success, durationMs }`. Retained output is bounded by UTF-8 bytes per stream, including any truncation marker, while discarded output continues to drain. Removing policy on exit does not undo writes to granted folders. ### system.run.prepare Pre-flight a `system.run` invocation. Same args as `system.run`. Returns the parsed plan (`argv`, `cwd`, `rawCommand`, `agentId`, `sessionKey`) without executing. ### system.which Resolve binary names to absolute paths. ``` {"bins": ["git", "node", "powershell"]} ``` Returns `{ "bins": { "git": "C:\\...", ... } }`. Names not found are omitted. ### system.execApprovals.get No params. Returns the active V2 snapshot: `{ path, exists, hash, file: { version, defaults: { security, ask, askFallback, autoAllowSkills }, agents: { "": { security, ask, askFallback, autoAllowSkills, allowlist: [{ id, pattern, lastUsedAt?, lastResolvedPath? }] } } } }`. `security` is `deny|allowlist|full`; `ask` is `off|on-miss|always|deny`; `askFallback` is `deny|allowlist|full`. Socket credentials are never returned. Pass `hash` as `baseHash` when calling `system.execApprovals.set`. ### system.execApprovals.set Replace the full V2 file using compare-and-swap. `baseHash` is required and must equal the latest hash returned by `system.execApprovals.get`. ``` { "baseHash": "", "file": { "version": 1, "defaults": { "security": "allowlist", "ask": "on-miss", "askFallback": "deny", "autoAllowSkills": false }, "agents": { "main": { "security": "allowlist", "ask": "on-miss", "askFallback": "deny", "autoAllowSkills": false, "allowlist": [] } } } } ``` Returns the updated snapshot. A remote update may preserve or remove existing allowlist grants, but it cannot add or change grants or set full access. Missing or stale hashes are rejected. This command changes future `system.run` authorization decisions. The `winnode` path is local loopback MCP, so no gateway scope applies. Remote operators must use the gateway `exec.approvals.node.*` control-plane methods, which are separately gated by gateway admin scope. ### canvas.present Open the WebView2 canvas window. ``` { "url": "string", // OR "html": "string" "html": "string", "width": 800, "height": 600, "x": -1, "y": -1, // -1 centers "title": "Canvas", "alwaysOnTop": false } ``` Returns `{ "presented": true }`. ### canvas.hide No params. Hides the canvas without destroying state. ### canvas.navigate ``` {"url": "https://..."} // also accepts file:// or local canvas paths ``` ### canvas.eval ``` {"script": "document.title"} // also accepts "javaScript" or "javascript" ``` Returns the evaluated result. ### canvas.snapshot ``` {"format": "png|jpeg", "maxWidth": 1200, "quality": 80} ``` Returns `{ format, base64 }`. ### canvas.a2ui.push Render an A2UI v0.8 surface in the canvas. The canvas window opens automatically - no `canvas.present` required. ``` { "jsonl": "string", // OR jsonlPath "jsonlPath": "string", // must live under %TEMP% "props": {} // optional } ``` Returns `{ "pushed": true }`. **See A2UI grammar below.** ### canvas.a2ui.pushJSONL Alias of `canvas.a2ui.push`. It uses the identical handler, size/line caps, and `jsonl`/`jsonlPath` contract. It exists for naming parity, not as a separate streaming path. ### canvas.a2ui.reset No params. Clears any rendered surfaces. Returns `{ "reset": true }`. ### canvas.a2ui.dump No params. Returns the current surface graph for introspection. **Read-all:** this exposes every currently-rendered surface - operators should treat it as equivalent to a screenshot of every open A2UI surface. ### canvas.caps No params. Returns renderer capabilities (renderer, snapshot, a2ui version). ### screen.snapshot ``` { "format": "png|jpeg", "maxWidth": 1920, "quality": 80, "monitor": 0, "screenIndex": 0, // 0 = primary "includePointer": true } ``` Returns `{ format, width, height, base64, image }` (image is a `data:` URL). The first use requires remembered user consent. Every invocation displays a visible notification before screen capture begins. ### screen.record ``` { "durationMs": 5000, // required, max 300000 "format": "mp4|webm", "monitor": 0, "screenIndex": 0, "maxWidth": 1920, "fps": 30 } ``` Returns `{ format, durationMs, base64 }`. ### camera.list No params. Returns `{ cameras: [{ deviceId, name, isDefault }] }`. ### camera.snap ``` {"deviceId": "string", "format": "jpeg|png", "maxWidth": 1280, "quality": 80} ``` Returns `{ format, width, height, base64 }`. `deviceId` defaults to system default camera. The first use requires remembered user consent. Every invocation displays a visible notification before camera capture begins. ### camera.clip ``` { "deviceId": "string", // optional "durationMs": 3000, // required, max 60000 "format": "mp4|webm", "maxWidth": 1280 } ``` Returns `{ format, durationMs, base64 }`. ## Speech-to-text (stt.*) Local Whisper.net runs on this device - no audio leaves the box. The model is downloaded on first use; until then every `stt.*` call returns a clear error pointing the caller at the Voice Settings page. **Privacy-sensitive: requires `NodeSttEnabled` in tray Settings.** ### stt.transcribe Bounded fixed-duration mic capture + transcription. ``` { "maxDurationMs": 5000, // required, > 0, max 30000 "language": "en" // optional BCP-47 tag or "auto" - falls back to SttLanguage setting } ``` Returns `{ transcribed, text, durationMs, language, engineEffective: "whisper" }`. ### stt.listen Mic capture with voice-activity detection. Returns when the user stops speaking or after `timeoutMs`. Result is the full silence-bounded utterance (all Whisper segments concatenated), not a partial first segment. ``` { "timeoutMs": 30000, // optional, default 30000, range 1000..120000 "language": "auto" // optional BCP-47 tag or "auto" } ``` Returns `{ text, language, durationMs, segments[{ text, startMs, endMs }], engineEffective: "whisper" }`. ### stt.status Engine readiness. No params. Carries no PII (no transcript history, no language history, no device IDs, no model paths). Returns `{ engine: "whisper", readiness, modelDownloadProgress, isListenWithVadSupported, isBoundedTranscribeSupported }` where `readiness` ∈ `"ready" | "initializing" | "model-downloading" | "model-not-downloaded" | "unavailable"`. ## Text-to-speech (tts.*) Five providers - Kokoro (local neural via kokoro.cpp, default), Piper (local neural via Sherpa-ONNX), Windows built-in speech, ElevenLabs (cloud), and MiniMax (cloud). Provider + per-provider voice are configured in tray Settings. While Kokoro is the provider and TTS is enabled, the tray downloads the selected Kokoro voice pack in the background on unmetered connections; until it is ready, `tts.status` reports `voice-not-downloaded` and speech falls back to Windows. ### tts.speak Speak text aloud on the Windows node. ``` { "text": "string", // required "provider": "kokoro|piper|windows|elevenlabs|minimax", // optional; omit to use TtsProvider setting "voiceId": "string", // optional, overrides the per-provider configured voice "model": "string", // optional, cloud providers only "interrupt": false // default false; true cuts off any in-progress playback } ``` When `provider` is omitted and the configured provider isn't usable (no cloud API key, Kokoro voice pack or Piper voice not downloaded), the node falls back to Windows TTS so playback still happens. Explicit `provider` requests stay strict and do not silently reroute. Returns `{ spoken, provider, requestedProvider, fellBack, contentType, durationMs }` where `provider` is the provider that actually spoke. ### tts.status TTS provider readiness. No params. Carries no PII (no voice ids, no key fragments, no device names). Returns `{ configuredProvider, effectiveProvider, willFallBack, providers[{ provider, readiness, isReady }] }` where `effectiveProvider` is what would run now after fallback and `readiness` ∈ `"ready" | "needs-api-key" | "needs-voice" | "voice-not-downloaded" | "unavailable"`. The configured/effective view reflects configured defaults only; explicit `tts.speak` provider requests stay strict and may not match the default snapshot. ## Local model inference (ollama.*) Runs against a separately installed Windows Ollama service (not the app-managed Local AI gateway provider). Any paired active gateway, local or remote, may invoke it once enabled. **Requires `NodeOllamaInferenceEnabled` in tray Settings > Permissions (opt-in, default off).** ### ollama.models List locally installed Ollama models. Read-only inventory - no prompt or chat content. No params. Returns `{ provider, models[{ name, size, modifiedAt, family, parameterSize, quantization, contextWindow, capabilities, loaded }] }`. ### ollama.chat Send a single-turn chat prompt to a local Ollama model. **Privacy and resource sensitive** - sends prompt content to a locally running model and consumes CPU/GPU. Only one chat runs at a time per node; a concurrent call while one is in flight returns an error. ``` { "model": "string", // required "prompt": "string", // required, max 128000 chars "system": "string", // optional, max 32000 chars "temperature": 0.7, // optional, 0..2 "maxTokens": 512, // optional, default 512, max 8192 "timeoutMs": 120000 // optional, default 120000, max 600000 } ``` Returns `{ provider, model, response, usage?: { promptTokens, completionTokens }, timings?: { loadMs, totalMs } }`. Newer gateways with the bundled Ollama plugin expose these commands through the `node_inference` agent tool. Older gateways can invoke them through the generic `nodes` tool after both exact command names are added to `gateway.nodes.allowCommands`. Feature-detect the node's effective commands; do not infer support from the gateway version. ## App control (app.*) Read-only and small write operations targeting the running tray. Used by the command palette and by automation that wants to drive the UI. ### app.navigate Navigate Workspace or the Settings companion to a specific page. ```json {"page": "workspace:home"} ``` Returns `{ navigated, page }`. `home` and `chat` open Workspace chat; `workspace:notifications` opens Workspace notifications. Unprefixed routes such as `sessions`, `settings`, and `connection` open the Settings companion. Deprecated Workspace routes return Home. Unknown `workspace:` routes return a tool error before any window is opened or navigation is forwarded. ### app.status Current connection / node state. No params. Returns `{ connectionStatus, overallState, operatorState, nodeState, nodeConnected, nodePaired, nodePendingApproval, nodeError, gatewayVersion, sessionCount, nodeCount }`. For agent-facing connection troubleshooting, prefer `app.connection.status`. ### app.sessions Active sessions, optionally filtered by agent. ``` {"agentId": "string"} // optional ``` Returns array of `{ Key, Status, Model, AgeText, tokens }`. ### app.agents List agents from the connected gateway. No params. Returns the raw agents JSON array. ### app.nodes List connected nodes and their capabilities. No params. Returns array of `{ DisplayName, NodeId, IsOnline, Platform, CapabilityCount }`. ### app.config.get Read gateway configuration value at a dot-path. ``` {"path": "string"} // optional; omit to fetch the full config tree ``` Returns the config subtree (or full config) as JSON. ### app.settings.get Read a local app setting by name. ``` {"name": "string"} // required ``` Returns the setting value (type depends on the setting). ### app.settings.set Set a local app setting, persist it, and apply the same reconnect/reload behavior as saving settings in the app UI. ``` {"name": "string", "value": "string"} // both required ``` Returns `{ name, value }`; runtime apply failures surface as tool errors. ### app.menu Get tray menu state (status, session count, node count). No params. Returns array of menu items; the status item includes `status`, `overallState`, `nodeState`, and `nodeError`. ### app.search Search locally available navigation destinations without opening them or making Gateway requests. Settings results identify their destination rather than toggle anything. Message snippets are excluded. ``` {"query": "string"} // required ``` Returns up to 10 `{ Title, Subtitle, Icon, Group, OpensDashboard, HasDashboardCounterpart }`. ### app.dashboard.url Build the same gateway dashboard URL the tray opens. ``` {"path": "string"} // optional ``` Returns `{ url, credentialSource, usesSharedGatewayToken, hasTokenQuery }`. ## App connection diagnostics and setup (app.connection.*) Local MCP-only tools for connection diagnostics, setup, pairing approvals, and targeted reconnects. These tools are not advertised to the remote gateway node transport. Read-only diagnostics redact token values and expose credential presence/outcome only. ### app.connection.status Read-only connection diagnostics for agents and CLIs. No params. Returns: `{ schemaVersion, connectionState, effectiveMode, legacyConnectionStatus, gateway, protocol, operator, node, mcp, browserProxy, pendingActions, retry, diagnostics }`. The payload includes the active gateway id/name/url, operator and node role states, credential sources/statuses, MCP enabled/running/error state, browser proxy shared-token caveat, pending approval commands, retry hints inferred from recent diagnostics, and recent connection diagnostic events. `effectiveMode` reflects Settings mode (`EnableNodeMode` / `EnableMcpServer`); `node.intended` reflects the manager snapshot plus current Node mode setting. `gateway.packageVersion` is the installed Gateway npm package version reported by `hello-ok`; it is separate from the wire protocol. `protocol` reports the accepted Gateway wire protocol, the Windows-supported current/minimum/maximum values (currently 4/3/4), normalized compatibility (`compatible`, `gateway_too_old`, `gateway_too_new`, `mismatch`, or `unknown`), the operator/node source, available Gateway protocol expectations, and whether the condition is retryable. During an active mismatch, `selectedProtocol` comes only from that connection attempt and is otherwise null, even if cached Gateway self information remains available. ### app.connection.gateways Read-only saved gateway diagnostics. No params. Returns: `{ activeGatewayId, count, gateways[] }`. Each gateway includes id/name/url, active flag, local/v2 flags, lastConnected, credential presence booleans (`hasSharedGatewayToken`, `hasBootstrapToken`), browser-control port/caveat, and SSH tunnel metadata. Token values are never returned. ### app.connection.applySetupCode Apply a setup or QR code and connect the tray to that gateway. ``` {"setupCode": "string"} // required ``` Returns `{ outcome, error, gatewayUrl, connected }`. Side effects: creates or updates a gateway record, sets it active, persists credentials from the setup code, and asks `GatewayConnectionManager` to connect. ### app.connection.connectSharedToken Connect with a shared gateway token. ``` { "gatewayUrl": "string", // required "token": "string" // required } ``` Returns `{ outcome, error, gatewayUrl, connected }`. Side effects: creates or updates a gateway record, stores the shared token on that record, sets it active, and asks `GatewayConnectionManager` to connect. ### app.connection.pendingApprovals Read pending device/node pairing approvals from the connected gateway. No params. Returns `{ connected, error, totalPending, devicePending[], nodePending[] }`. ### app.connection.approveDevicePairing Approve a pending device pairing request. ``` {"requestId": "string"} // required; "id" alias accepted ``` Returns the refreshed pending approvals payload plus `{ decision }`. Side effect: calls the connected operator client's device-pair approval RPC. ### app.connection.rejectDevicePairing Reject a pending device pairing request. ``` {"requestId": "string"} // required; "id" alias accepted ``` Returns the refreshed pending approvals payload plus `{ decision }`. Side effect: calls the connected operator client's device-pair rejection RPC. ### app.connection.approveNodePairing Approve a pending Windows node pairing or command-trust request. ``` {"requestId": "string"} // required; "id" alias accepted ``` Returns the refreshed pending approvals payload plus `{ decision }`. Side effect: calls the connected operator client's node-pair approval RPC. ### app.connection.rejectNodePairing Reject a pending Windows node pairing or command-trust request. ``` {"requestId": "string"} // required; "id" alias accepted ``` Returns the refreshed pending approvals payload plus `{ decision }`. Side effect: calls the connected operator client's node-pair rejection RPC. ### app.connection.reconnect Reconnect the active gateway through `GatewayConnectionManager`. No params. Returns `{ reconnected, error? }`. Side effect: disconnects and reconnects the operator/node lifecycle for the active gateway. ### app.connection.reconnectNode Reconnect only the Windows node role for the active gateway through `GatewayConnectionManager`. No params. Returns `{ reconnected, error? }`. Side effect: restarts node connection intent without changing the active gateway. ### app.chat.snapshot Read the current native chat snapshot for local automation and diagnostics. **READ-ALL:** returns recent chat text from the selected timeline and outgoing queue text. ``` {"threadId": "string"} // optional; "sessionKey" alias also accepted ``` Returns `{ connectionStatus, defaultThreadId, composeTarget, threads, queue, selectedTimeline }`. ### app.chat.send Send a message through the same native chat provider used by the Chat UI. ``` { "message": "string", // required "threadId": "string" // optional; defaults to compose/default thread } ``` Returns `{ sent, threadId, entryCount, turnActive, error? }`. ### app.chat.reset Reset the target chat session through the gateway `sessions.reset` path. ``` {"threadId": "string"} // optional; "sessionKey" alias also accepted ``` When `threadId`/`sessionKey` is omitted this resets the current compose/default thread, which usually means the active chat session. Returns `{ reset, threadId, error? }`. ### app.chat.queue.list List native chat outgoing queue entries. **READ-ALL:** returns queued message text. ``` {"threadId": "string"} // optional; "sessionKey" alias also accepted ``` When `threadId`/`sessionKey` is omitted this returns all queued threads. Returns: `{ defaultThreadId, requestedThreadId, totalCount, selectedThread, threads }` where each thread has `{ threadId, count, messages }`, and each message has `{ id, text, createdAt, sendState, errorText, canCancel }`. Queue message IDs are ephemeral UI/provider IDs; read them from the current `app.chat.queue.list` or `app.chat.snapshot` response and do not cache them across app lifetimes. ### app.chat.queue.cancel Cancel/remove one native chat outgoing queue entry before it is sent. ``` { "queuedMessageId": "string", // required; from the current app.chat.queue.list or app.chat.snapshot queue "threadId": "string" // required; "sessionKey" alias also accepted } ``` Only `Queued` and `Failed` entries can be removed. `Sending` entries may already have reached the gateway and are not canceled. Returns `{ canceled, threadId, queuedMessageId, remainingCount, error? }`. ## Location (location.*) ### location.get Get the device's current geographic location. ``` { "accuracy": "default|high", // optional, default "default" "maxAge": 30000, // ms; return a cached fix if younger than this "locationTimeout": 10000 // ms; fail if no fix within this time } ``` Returns `{ latitude, longitude, accuracy (meters), timestamp (ms) }`. Requires the Location capability to be enabled and OS location permission granted to the app. The first use also requires remembered OpenClaw consent. Every invocation displays a visible notification before location access begins. Error `LOCATION_PERMISSION_REQUIRED` if the user has not granted location access. ## Device (device.*) ### device.info Get static device metadata. No params. Returns `{ deviceName, modelIdentifier, systemName, systemVersion, appVersion, appBuild, locale }`. ### device.status Get live system health data. ``` { "sections": ["os","cpu","memory","disk","battery"] // optional; omit for all } ``` Returns a map with `collectedAt` (ISO-8601 string) and one key per section. Each section may contain `{ error: "collection failed" }` if data was unavailable. Legacy fields always present: `thermal`, `storage`, `network`, `uptimeSeconds`. Battery sub-object: `{ level, state ("charging"|"discharging"|"unknown"), lowPowerModeEnabled }`. **Privacy note**: `device.status` reveals battery level, network type, and disk usage. Agents should request only the sections they need. ## Browser control proxy (browser.*) ### browser.proxy Proxy an HTTP request to the local OpenClaw browser control host (Chrome DevTools Protocol server) running on gateway port + 2. ``` { "path": "/json/list", // required - local control path "method": "GET", // optional, default GET; allowed: GET|POST|DELETE "body": {}, // JSON object, for POST/DELETE "query": {}, // appended as query-string params "profile": "Default", // optional browser profile name "timeoutMs": 20000 // optional, max 120000 } ``` Returns `{ result, files? }` - `files` is an array of `{ path, base64, mimeType }` if the response referenced local file paths. Requires the gateway URL to have an explicit port (e.g. `ws://localhost:8080`). The browser control host must be running locally on `127.0.0.1:`. Listed only when `BrowserProxyActivation` is `RegistrationBlock.None`: the toggle is on, a gateway client is attached, the shared gateway token is set, and the browser endpoint is verified. MCP-only startup (`EnableMcpServer` true, `EnableNodeMode` false) does not attach a gateway client, so this command stays off `tools/list` and the call returns `Unknown tool: browser.proxy`. --- ## A2UI v0.8 grammar (for canvas.a2ui.push) The `jsonl` argument is a string of newline-separated JSON-RPC-like messages. Three message kinds are supported. **createSurface and v0.9 messages are rejected.** ### Message kinds ```jsonc // 1. Declare components for a surface (creates the surface if new). {"surfaceUpdate": { "surfaceId": "string", "components": [ ComponentDef, ... ] }} // 2. Pick the root component and (optionally) styles. Send AFTER surfaceUpdate. {"beginRendering": { "surfaceId": "string", "root": "componentId", "styles": { "primaryColor": "#FF6F61", "radius": 8.0, "spacing": 12.0 } }} // 3. Seed/update the data model bound by Path() values. {"dataModelUpdate": { "surfaceId": "string", "contents": [ {"key": "headline", "valueString": "Hi"}, {"key": "agreed", "valueBoolean": false}, {"key": "volume", "valueNumber": 20.0} ] }} // 4. (Optional) Remove a surface. {"deleteSurface": {"surfaceId": "string"}} ``` ### ComponentDef ```jsonc {"id": "uniqueId", "component": {"": { ...props }}} ``` ### Value bindings (inside a component prop) | Form | Meaning | |-------------------------------|----------------------------------------| | `{"literalString": "x"}` | Literal string | | `{"path": "/key"}` | Read/write the data model | | Plain string `"x"` | Component-id reference (e.g. `child`) | | Plain number / bool | Used directly for numeric/bool props | ### Component catalog | Category | Name | Notable props | |--------------|---------------|---------------| | Container | `Row` | `children: {explicitList: ["id", ...]}` | | Container | `Column` | `children: {explicitList: ["id", ...]}` | | Container | `List` | `children`, `dataBinding` | | Container | `Card` | `child: "id"` | | Container | `Tabs` | `tabItems: [{title, child}]` | | Container | `Modal` | `child` | | Container | `Divider` | `axis: "horizontal" | "vertical"` | | Display | `Text` | `text: Lit/Path`, `usageHint: "h1|h2|h3|h4|h5|body|caption"` | | Display | `Image` | `url: Lit/Path`, `fit: "contain|cover|fill|none"`, `usageHint` | | Display | `Icon` | `name: Lit("settings"\|...)` | | Display | `Video` | `url`, `autoplay`, `controls` | | Display | `AudioPlayer` | `url`, `controls` | | Interactive | `Button` | `child`, `primary: bool`, `action: {name, ...context}` | | Interactive | `CheckBox` | `label: Lit/Path`, `value: Path` | | Interactive | `TextField` | `value: Path`, `textFieldType: "shortText|longText|obscured"` | | Interactive | `DateTimeInput` | `value: Path`, `mode: "date|time|datetime"` | | Interactive | `MultipleChoice` | `value: Path`, `options: [{value, label}]` | | Interactive | `Slider` | `value: Path`, `minValue`, `maxValue`, `step` | Lit/Path = the value-binding shapes from the previous section. ### Minimal "hello world" payload ``` {"surfaceUpdate":{"surfaceId":"hello","components":[{"id":"helloText","component":{"Text":{"text":{"literalString":"Hello, world!"},"usageHint":"h1"}}}]}} {"beginRendering":{"surfaceId":"hello","root":"helloText"}} ``` Pass this as the `jsonl` value (a single JSON string with `\n` between messages). --- ## Token-efficient call patterns 1. **Skip `--node` / `--idempotency-key`** - they're ignored locally; including them just costs tokens. `--idempotency-key` triggers a stderr warning. 2. **Omit `--params` when the command takes no args** (`camera.list`, `canvas.hide`, `canvas.a2ui.reset`, `canvas.a2ui.dump`, `canvas.caps`, `system.execApprovals.get`). 3. **Large A2UI payloads** - write the JSONL to a file under the system temp directory and pass `{"jsonlPath": ""}`. The capability rejects paths outside `%TEMP%`. Or pass `--params @` to load the entire JSON argument object from disk. 4. **Big binary results (snapshots, captures)** - output is base64 in stdout. Pipe to a file (`> capture.json`) instead of letting the agent read it inline. 5. **Errors are exit-code-driven** - check `$LASTEXITCODE` (or `$?` in bash) first, then read stderr only on non-zero. Exit 2 = your call is malformed. 6. **Debug with `--verbose`, not by sharing transcripts** - without `--verbose` the CLI shows only the first line of an HTTP error body and redacts long base64url runs. With `--verbose` it shows the full sanitized body. Treat any verbose output as containing potentially sensitive paths or partial command output before pasting it elsewhere. ## What's NOT exposed - Pairing / device approval (gateway concept; doesn't apply locally). - `chat.send`, `sessions.list`, `usage.list`, `node.list` - these belong to the operator-side `OpenClaw.Cli.exe`, not `winnode.exe`. - Idempotency. The gateway de-dupes retries against `--idempotency-key`; local MCP does not. Retrying a `system.run` / `system.notify` / `canvas.present` call after a transient failure can double-execute the side effect. - Wildcards in `--command`. The MCP server has an explicit allowlist; unknown commands return `Unknown tool: `.