Files
openclaw/docs/nodes/voicewake.md
lsr911 0990bccb32 fix(gateway): use truncateUtf16Safe for voice-wake trigger truncation (#103210)
* fix(gateway): use truncateUtf16Safe for voice-wake trigger truncation

Replace naive .slice(0, 64) with truncateUtf16Safe() in
normalizeVoiceWakeTriggers to prevent surrogate pair splitting
in user-configured voice wake trigger phrases.

Voice wake triggers are user-configurable text strings that may
contain emoji or non-BMP characters. A naive .slice(0, 64) at
a surrogate pair boundary produces a lone surrogate, which
corrupts the trigger text.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add proof scripts for C1 sanitize and voice-wake UTF-16

* chore: remove unrelated proof script from voice-wake PR

ClawSweeper review: the console-sanitization proof script belongs
to #103226, not this gateway voice-wake fix. Remove it to keep the
branch clean.

Ref: #103210 review

* fix(gateway): harden voice wake unicode boundary

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-10 14:41:25 +01:00

4.0 KiB

summary, read_when, title
summary read_when title
Global voice wake words (Gateway-owned) and how they sync across nodes
Changing voice wake words behavior or defaults
Adding new node platforms that need wake word sync
Voice wake

Wake words are one global list owned by the Gateway — there are no per-node custom lists. Any node or app UI can edit the list; the Gateway persists the change and broadcasts it to every connected client.

  • macOS: local Voice Wake enable/disable toggle. Requires macOS 26+; see Voice wake (macOS) for runtime/PTT details.
  • iOS: local Voice Wake enable/disable toggle in Settings.
  • Android: Voice Wake is force-disabled at runtime. The Voice tab uses manual mic capture instead of wake-word triggers.

Storage

Wake words and routing rules live in the Gateway state database, ~/.openclaw/state/openclaw.sqlite by default (override with OPENCLAW_STATE_DIR), tables voicewake_triggers, voicewake_routing_config, voicewake_routing_routes. Legacy settings/voicewake.json and settings/voicewake-routing.json are openclaw doctor --fix migration inputs only — runtime never reads them.

Protocol

Trigger list

Method Params Result
voicewake.get none { triggers: string[] }
voicewake.set { triggers: string[] } { triggers: string[] }

voicewake.set normalizes input: trims whitespace, drops empty entries, keeps at most 32 triggers, and truncates each to 64 UTF-16 code units without splitting surrogate pairs. An empty result falls back to the built-in defaults (openclaw, claude, computer).

Routing (trigger to target)

Method Params Result
voicewake.routing.get none { config: VoiceWakeRoutingConfig }
voicewake.routing.set { config: VoiceWakeRoutingConfig } { config: VoiceWakeRoutingConfig }
{
  "version": 1,
  "defaultTarget": { "mode": "current" },
  "routes": [{ "trigger": "robot wake", "target": { "sessionKey": "agent:main:main" } }],
  "updatedAtMs": 1730000000000
}

Each route target supports exactly one of:

  • { "mode": "current" }
  • { "agentId": "main" }
  • { "sessionKey": "agent:main:main" }

Limits: at most 32 routes, trigger text at most 64 characters. Route triggers are normalized for matching and duplicate detection by lowercasing, stripping leading/trailing punctuation from each word, and collapsing whitespace ("Hey, Bot!!" and "hey bot" match and count as duplicates) — this is a stricter normalization than the plain trim used for the global trigger list above.

Events

Event Payload
voicewake.changed { triggers: string[] }
voicewake.routing.changed { config: VoiceWakeRoutingConfig }

Both broadcast to every WebSocket client with read scope (macOS app, WebChat, and similar) and to every connected node. A node also gets both as an initial snapshot push right after it connects.

Client behavior

  • macOS: calls voicewake.set/voicewake.get and listens for voicewake.changed to stay in sync with other clients.
  • iOS: calls voicewake.set/voicewake.get and listens for voicewake.changed to keep local wake-word detection responsive.
  • Android: VoiceWakeMode (Off/Foreground/Always) and gateway sync code exist, but the app forces the mode to Off on startup — Voice Wake is not currently reachable from Android Settings.