* ci: guard gateway protocol event coverage for iOS/Android clients (#100198)
Adds scripts/check-protocol-event-coverage.mjs, which derives the
server->client event catalog from GATEWAY_EVENTS in
src/gateway/server-methods-list.ts, extracts the events each mobile app
handles from Swift/Kotlin dispatch sites, and fails on gateway events no
client handles unless allowlisted with a reason in
scripts/protocol-event-coverage.allowlist.json. Wired as
pnpm check:protocol-coverage in the CI guards shard.
* fix(ci): scope Kotlin event extraction to handle*Event dispatch functions (#100198)
Bare event == "..." literals in predicate helpers outside the dispatch
path (gatewayEventInvalidatesNodesDevices in NodeRuntime.kt, which has no
production caller) counted as Android coverage, silently masking that
node.pair.requested/resolved have no live handler. Kotlin extraction now
only reads when(event) labels and event comparisons inside fun
handle*Event(...) bodies; node.pair.* moved to the Android allowlist with
a truthful reason. Swift extraction stays tree-wide because consumption
there always reads .event off a received EventFrame.
* fix(android): remove dead node pairing event helper
* fix(ci): preserve protocol allowlist parse errors
* test(ci): align tooling import plan
Ports the openclaw.ai hero mascot animation (body float 4s, eye blink 3s,
antenna wiggle 2s, staggered claw snaps 4s) to every product surface that
shows the logo:
- web: ui/public/favicon.svg now carries SMIL animations, so every <img>
usage (login gate, sidebar brand, agent avatar fallback) animates with
no markup changes; CSS animations do not run in <img>-loaded SVGs.
- iOS: new OpenClawMascotView renders the canonical 120x120 vector via
Canvas + TimelineView (30fps, honors Reduce Motion); OpenClawProMark
uses it in onboarding, sidebar, and command center. Static PNG imageset
deleted (watch app keeps its own copy).
- Android: new OpenClawMascot composable (PathParser + infinite
transitions, honors the OS animator scale) replaces the static vector
drawable in onboarding and the chat/shell/voice headers, with tint
support for the monochrome header silhouettes. Drawable deleted.
* fix: clean up Android camera clips on cancellation
* fix(android): own camera clip cleanup lifecycle
---------
Co-authored-by: NianJiuZst <180004567+users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(android): strip ws scheme prefix from manual gateway host input
Reject bare ws:// in the Host field and normalize pasted ws:// LAN URLs
so manual onboarding resolves the real hostname instead of host ws.
Fixes#87216.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(android): preserve complete manual gateway endpoints
* fix(android): preserve gateway i18n inventory
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(android): ignore chat events with missing assistant role in voice text extraction
Voice chat event parsing now rejects messages without an explicit assistant role,
matching ChatController fail-closed behavior and preventing accidental TTS playback.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(android): require canonical assistant role for voice events
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(android): distinguish gateway auth-recovery reasons instead of one generic label
gatewaySummary()/gatewayStatusLabel() collapsed every auth failure (expired
setup QR, missing/invalid token, missing/invalid password, stale device
identity) into "Authentication needed", discarding the already-computed
MainViewModel.gatewayConnectionProblem signal consumed elsewhere in the app
(OnboardingFlow's recoveryGatewayAuthDetail). Thread it into both status
labels via a shared gatewayAuthNeededSummary() helper so the Overview and
Settings screens tell the user which recovery action applies.
Fixes#98046
* test(android): lock gateway auth-needed labels to the real unauthorized message format
Verified against src/gateway/server/ws-connection/auth-messages.ts:
formatGatewayAuthFailureMessage always returns strings starting with
"unauthorized", which contains the "auth" substring the status-text gate
checks for. Add tests using the exact real-world message text (not just
synthetic "auth failed" strings) so this stays regression-locked.
* fix(android): widen the auth-needed status gate to cover device-identity failures
CONTROL_UI_DEVICE_IDENTITY_REQUIRED/DEVICE_IDENTITY_REQUIRED real gateway
messages ("device identity required", "control ui requires device
identity...") don't contain "auth", so the status.contains("auth") gate
never reached their specific label. Add "device identity" as an additional
substring the gate checks for those two codes, with regression tests using
the exact real message text (message-handler.ts).
Verified via GitHub-style codex review round 2 (round 1's "unauthorized
doesn't contain auth" claim was checked against source and rejected as
false — "unauthorized" does contain "auth" — but this second, narrower
finding about device-identity codes held up under the same source check).
* fix(android): centralize gateway auth status labels
* fix(android): correlate gateway connection diagnostics
* chore(android): align native i18n inventory after rebase
* fix(android): preserve retryable pairing guidance
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: release Android camera snap use case
* fix(android): release one-shot camera capture promptly
---------
Co-authored-by: NianJiuZst <180004567+users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>