contracts: avatar frames and desktop avatar status
Merged
CH
6d ago+550+550+012345678901234567890123456789-10-10-01234567890123456789
PR 1 of the video-calls rollout: the wire contract and the desktop status vocabulary. Nothing sends or reads these frames yet. The consumers are #8 (web voice service), #10a (device socket), #11 (call window), #12 (voice-window audio path) and #13 (the wire-up).
Frames (packages/hosted/src/live-contract.ts)
- New
VOICE_SERVICE_FRAMEentriesavatar.start,avatar.stop,avatar.started,avatar.endedandavatar.unavailable, each with a schema and a reader. - Desktop ā service (
avatarRequestFrameFromWire, strict: an extra key or an unknown reason is refused):avatar.start {}avatar.stop {reason}, whereAVATAR_STOP_REASON = {USER: "user", MEDIA_LOST: "media_lost"}
- Service ā desktop (read like other answers: a key added by a newer service is dropped):
avatar.started {avatarSessionId, livekitUrl, viewerToken, mediaUrl, maxSeconds}. Both URLs must parse as absolutewss:and are capped at 2048 chars. The token is non-blank and capped at 8192. The session id is trimmed, non-empty and capped at 256.maxSecondsis an integer from 1 to 86400.avatar.ended {reason}, whereAVATAR_END_REASON= the stop reasons plusmax_duration,no_creditsanderror.avatar.unavailable {reason}, whereAVATAR_UNAVAILABLE_REASON = {not_configured, no_credits, error}.- A reason string the desktop doesn't know reads as
errorinstead of refusing the frame, so the desktop still learns the face is down. A reason that isn't a string, or is over 64 chars, is refused.
- JSON Schema goldens recorded for the six new schemas (new files only; no existing golden changed).
Desktop status (apps/desktop/src/shared/messages/avatar.ts, app state)
AVATAR_STATUS = {OFF, STARTING, LIVE, ENDED}andAVATAR_END_REASON = {USER, MEDIA_LOST, MAX_DURATION, NO_CREDITS, UNAVAILABLE, ERROR}.AvatarState = {status, endReason?}, plusAVATAR_OFF.state.voice.avataris required and set toAVATAR_OFFat launch.- When the voice window is replaced, its view is still dropped, but the avatar status is kept (
voiceWindowGone), because the host's live session owns it, not that window. - The room URL and viewer token are never put in app state.
Tests
live-contract.test.ts:- malformed or oversized
avatar.startedfields are refused (non-wssURLs, unparsable URLs, oversized URL/token/id, a blank token, out-of-range or non-integermaxSeconds, a missing token); - stray keys and unknown reasons on the desktop's requests are refused;
- an unknown answered reason falls back to
error; - a malformed reason is refused.
- malformed or oversized
app-state.test.ts: launch holdsAVATAR_OFF, and a replaced voice window leaves the avatar status in place.
./scripts/check.sh exits 0. This PR touches no UI or window code, so there is no verify.sh evidence. CI does not build or check the Mac.
š¤ Generated with Claude Code