feat(settings,host): Video calls in Settings › Voice opens each call with Luke's avatar, off by default (avatar 12/12)
Plan item 12. switch on (stacks on #2563, avatar 11/12, merged into feature/avatar-video-keyframe).
What changes
-
Setting.
voiceVideojoinsAPP_SETTING_SCHEMAas a toggle, "Video calls", on Settings › Voice beside Captions (order: 55,SETTING_SECTION.CONTROLS, drawn only while the voice controls are), reset with the voice scope, default off. Its detail says what reaches Keyframe: "Calls open with Luke on video in the call window. Keyframe Labs animates him from his voice, so what he says about your plan reaches Keyframe. Your own voice never does." The row is drawn by the schema renderer the Voice page already uses, so there is no new component. New idvoice_videoinAPP_SETTING_ID. -
videoOnCallreads the setting.composeLivenow takes the settings store and readsvoiceVideoas each call comes to stand. A settings file that cannot be read opens the call without video, since every avatar is a Keyframe session the plan pays for. Turning the setting on mid-call changes nothing until the next call, and the tile's own Video off and Restart video still work within a call. -
LUKE_VOICE_VIDEOis removed, not kept as a dev override. In dev you can turn the setting on the same way, so an override would be a second switch with nothing left to do.compose-host.tsgoes back to #11's parent shape: noConfig,AppIdentityorEnvironmentread. -
PRIVACY.md. One self-contained bullet under "Who we send it to" (kept to that one spot because #2462 is also editing PRIVACY.md). It covers: only while Video calls is on; our service mints the session on our key, naming only the stock persona; the session pass reaches only the floating call window; the call window sends Keyframe Luke's synthesized voice, which carries what he says about the plan; the developer's microphone and nothing else of theirs reaches Keyframe; what we log and keep (start and end, length, reason); and "Keyframe keeps what it receives under its own retention policy". It makes no retention, training or DPA claim.
-
apps/web/server/README.md. A new "Luke's avatar" subsection under Hosted voice service covers the four frames and howframes.tskeeps them from OpenAI,avatar.ts's serial answering, thekeyframe.tsmint onKEYFRAME_API_KEYandLUKE_KEYFRAME_PERSONA_ID, the failure kinds (no_capacity,error,not_configured), thevoice_avatar_sessionsrow and its close order, and the sweep'sorphanedclose and repair of closed calls. "Deploying" names the two variables. -
CHANGELOG: left untouched, as the orchestrator decided.
repository-checks.shaccepts only## <version> — <date>headings, so there's no unreleased section to add to. Ready-to-paste entry for 0.13.0's notes:### Video calls with Luke Turn on Video calls in Settings › Voice and each call opens with Luke on video in the floating call window, lip-synced to his voice. Video off puts him away and the call goes on as voice, and Restart video brings him back. ([#2478](https://github.com/ReviewStage/luke/pull/2478), [#2537](https://github.com/ReviewStage/luke/pull/2537), [#2545](https://github.com/ReviewStage/luke/pull/2545), [#2548](https://github.com/ReviewStage/luke/pull/2548), [#2563](https://github.com/ReviewStage/luke/pull/2563), [#2565](https://github.com/ReviewStage/luke/pull/2565))(Add the integration branch's landing PR and the remaining avatar PRs when it lands.)
Tests
-
packages/settings/src/schema.test.ts: the Voice page draws the Video calls row among its controls, off at the default and not marked changed, and on once set. -
packages/host/src/compose-live.test.ts: the live concern now reads a realSettingsStorein a temp directory of its own (temporaryDirectoryScoped) instead of a stubbedvideoOnCall:- at the default, a call opens with no avatar;
- with
voiceVideowritten on, a call opens withavatar.start; - turning it on during a call leaves that call alone and opens the next one with the avatar.
Both "on" tests fail if
videoOnCalldoesn't read the setting (checked by reverting it). -
settings-store.test.ts's table covering every setting getsvoiceVideo: true, so the generic default, write, reopen and clear test covers it. -
./scripts/check.shexits 0.
Not verified
- CI could not verify this; Mac evidence pending. This PR still needs
./scripts/verify.shon a Mac, plus a screenshot of the Video calls row drawn on Settings › Voice. This workspace is a Linux VM, and CI builds nothing for the Mac. - No live Keyframe session was ever opened. Nothing here or in #1 to #11 has been run against a real Keyframe session: minting on a real key, the LiveKit join under the call window's CSP, lip sync and the handover with no word heard twice, the stop key and barge-in, the server-close restart, whether billing ends on disconnect, and the row's estimated seconds against Keyframe's own meter. The end-to-end list in the plan's Verification section still stands.
⚠️ For you: Keyframe privacy is unconfirmed
Keyframe's retention, use for training, and DPA are unconfirmed. Zero data retention is listed for the Enterprise plan only. PRIVACY.md now says Keyframe receives Luke's synthesized voice, which carries plan content, and keeps it "under its own retention policy", which is true whatever that policy turns out to be. It doesn't claim zero retention or no training. Please confirm those terms (and choose the plan: Growth, Scale or Enterprise) before the default changes or the feature ships in a release. AGENTS.md is deliberately untouched while you decide the carve-out wording.
🤖 Generated with Claude Code
Summary by cubic
Adds a "Video calls" setting to Settings › Voice that opens each call with Luke's avatar, defaulting to off. The host now reads the setting from the settings store when each call starts, replacing the LUKE_VOICE_VIDEO dev override, which is removed. Also documents Keyframe privacy in PRIVACY.md and the avatar session flow in the server README.