Skip to content

Custom voice never speaks on pi.ai — new replies are decorated by the past-message observer #597

Description

@rosscado

What happens

Pick any SayPi voice for Pi in Settings → Voices (e.g. Shimmer, shown "IN USE"), then talk to Pi on pi.ai/talk. Pi answers in her own native voice. The chosen voice is never heard. Every new reply instead gets a "Generate Audio" button (the speech-incomplete state), and pressing it doesn't help.

The same voice, same account, same extension build works on claude.ai — so this is pi.ai-specific, not auth, quota, catalog, or preference storage.

Why

Only ChatHistoryAdditionsObserver synthesises speech. The "old message" observers replay from the speech history and, finding nothing for a brand-new message, mark it speech-incomplete. So whichever observer reaches a new reply first decides whether the user's voice ever speaks.

ChatHistorySpeechManager.registerPresentChatHistoryListener() runs once, in the constructor. On pi.ai the present container isn't mounted yet at that moment (the very fact #309 documents), so the additions observer resolves a null target and dies silently — permanently, for the life of that chat history.

#309's fix restored decoration of the most-recent message by attaching a ChatHistoryOldMessageObserver to the present container as it appears. That fixed the missing controls and, in the same stroke, gave the present container the past container's semantics: every new reply is treated as history. Hence a Generate-Audio button where the voice should have been.

Measured live (pi.ai, 2026-08-05): a new reply is mounted, decorated assistant-message, then speech-incomplete within ~10ms — far too fast for a synthesis round-trip — and no /speak request is ever made. The extension knows the voice is Shimmer throughout (the speech-incomplete branch is only reachable when the active provider is SayPi).

Impact

SayPi voices are dead on pi.ai for anyone on a build with #309 in it. Pi's own voice keeps playing, so it reads as "my custom voice is ignored" rather than as silence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions