feat(settings,host): Video calls in Settings › Voice opens each call with Luke's avatar, off by default (avatar 12/12)

Merged
CH
5d ago
+156-28

Plan item 12. switch on (stacks on #2563, avatar 11/12, merged into feature/avatar-video-keyframe).

What changes

  • Setting. voiceVideo joins APP_SETTING_SCHEMA as 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 id voice_video in APP_SETTING_ID.

  • videoOnCall reads the setting. composeLive now takes the settings store and reads voiceVideo as 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_VIDEO is 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.ts goes back to #11's parent shape: no Config, AppIdentity or Environment read.

  • 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 how frames.ts keeps them from OpenAI, avatar.ts's serial answering, the keyframe.ts mint on KEYFRAME_API_KEY and LUKE_KEYFRAME_PERSONA_ID, the failure kinds (no_capacity, error, not_configured), the voice_avatar_sessions row and its close order, and the sweep's orphaned close and repair of closed calls. "Deploying" names the two variables.

  • CHANGELOG: left untouched, as the orchestrator decided. repository-checks.sh accepts 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 real SettingsStore in a temp directory of its own (temporaryDirectoryScoped) instead of a stubbed videoOnCall:

    • at the default, a call opens with no avatar;
    • with voiceVideo written on, a call opens with avatar.start;
    • turning it on during a call leaves that call alone and opens the next one with the avatar.

    Both "on" tests fail if videoOnCall doesn't read the setting (checked by reverting it).

  • settings-store.test.ts's table covering every setting gets voiceVideo: true, so the generic default, write, reopen and clear test covers it.

  • ./scripts/check.sh exits 0.

Not verified

  • CI could not verify this; Mac evidence pending. This PR still needs ./scripts/verify.sh on 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


Devin Review


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.