feat(desktop): the call window is handed the avatar's grant and voice feed alone, stands at the video tile's size while the avatar is up, and reports its status
Merged
CH
5d ago+628+628+012345678901234567890123456789-38-38-01234567890123456789
Plan item 9a. main: call window avatar plumbing, stacked on #2472.
What changes
- Seams on
CallWindow:showAvatarSession(grant | undefined)andfeedAvatar(feed). They send on two new one-shot channels,app:avatar-sessionandapp:avatar-feed, to the floating call window's ownwebContentsand nowhere else.- The sending lives in
call-window-avatar.ts(CallAvatarRelay), which does not touch Electron. - It holds the standing grant so that a page which draws after the grant arrived still gets it. That covers a window opened by the first
STARTINGbefore its renderer loaded, and a renderer that died mid-call. - Feed messages are not held; a chunk that found no page has already expired.
- The grant is never written to app state and never broadcast.
- The sending lives in
- Presence and size:
CallWindowPresencekeeps the floating window up while the avatar isSTARTINGorLIVE, even when a panel has the keyboard.CallWindowresizes toCALL_VIDEO_BOUNDS(336Ć196: #5's 320Ć180 tile plus 8 pt of shadow room on each side). The tile grows up from the card's bottom-left corner, and is clamped back onto the work area if growing pushed it off.- The window holds the card's own place apart from the frame, so a clamped tile gives the card back where it was. Only a drag moves the saved placement; a move the window made itself is not kept.
- The "may reach no host" comment now names the one host: the LiveKit server the grant points to. The CSP itself changes in #9b.
ACT_KIND.AVATAR_REPORT(avatar.report, payload{status, endReason?}) writesstate.voice.avatar.endReasonis accepted withENDEDand nowhere else.- The router row refuses any sender but
call.
- AGENTS.md "Never": adds the approved carve-out. The Keyframe session token for a call's own avatar may reach the floating call window, and never the voice window, a Gateway answer or event, app state, a counted event, a trace or a fixture.
- New door
@sidecar/hosted/avatar-grant: it holds the grant schema{serverUrl, participantToken, agentIdentity}, its bounds andavatarGrantFromWire.avatar.startedinlive-contract.tsis now that grant plustypeandrequestId, so the call window never seesrequestId. It's a separate subpath because reading the grant through the barrel put all oflive-contract.tsinto the renderer bundle, 3.5 KB gzipped over its ceiling. Through the door the renderer grows by about 0.3 KB. The started frame's JSON Schema golden is unchanged.
Tests
avatar-acts.test.ts:- a panel's or the voice window's
AVATAR_REPORTis refused and writes nothing; - the call window's report becomes
state.voice.avatar; - a reason with a status other than
ENDED, orENDEDwithout a reason, is refused.
- a panel's or the voice window's
call-window-avatar.test.ts:- the grant, the feed and the closing
undefinedreach the call window's page, and the panel and voice pages receive nothing; - a page that draws later gets the standing grant but not the missed feed;
- after a close it gets nothing.
- the grant, the feed and the closing
call-window-presence.test.ts:- the video stays up through panel focus and comes down with
ENDEDor the end of the call; - the tile grows from the card's bottom-left corner and clamps to the work area, and the card comes back to its own place.
- the video stays up through panel focus and comes down with
Not in this PR
- No caller yet. #11 binds
showAvatarSessionandfeedAvatarto the host's grant and the voice window's feed, and writes the status from the host side. - #9b runs the Keyframe session in the call window: it subscribes to these channels, adds the CSP
connect-src, and sendsAVATAR_REPORT. - #9b's subscription has to be in place by the call page's first paint (
ready-to-show), which is when the held grant is re-sent.
Evidence
./scripts/check.sh exits 0 locally.
CI could not verify this; Mac evidence pending. The verify.sh run on a Mac is owed: the window's refit and presence change are Mac UI, and CI builds nothing for the Mac.
š¤ Generated with Claude Code