ca42d7a034
Voice state was keyed `<platform>:<chat_id>` with no profile namespace, so two bots in one Discord channel shared one /voice mode; every `_voice_input_callback` was the bare `_handle_voice_channel_input`, which (like `_handle_voice_timeout_cleanup` and the /voice slash handler) always picked `self.adapters[DISCORD]` — a secondary profile's voice transcripts were dispatched through the default profile's bot. - `_voice_key(platform, chat_id, profile=None)`: named profiles get a `<profile>:` prefix; default keeps the legacy shape (persisted state valid). - `_voice_key_for_source` keys by the transport-OWNING profile (`_adapter_profile_for_source`), matching what `_sync_voice_mode_state_to_adapter` now restores per adapter via `_owner_profile`. - `_bind_voice_input_callback` binds the capturing adapter into the transcript handler (functools.partial); used at primary connect, primary reconnect, /voice channel join, and `_configure_profile_adapter`. - `_handle_voice_timeout_cleanup` takes the adapter it was bound to. - /voice, join, leave and `_should_send_voice_reply` resolve the adapter via `_adapter_for_source` (fail-closed) instead of `self.adapters[platform]`. - #84872: `_start_one_profile_adapters` now calls `_sync_voice_mode_state_to_adapter` on secondary INITIAL connect, as the primary path and both reconnect paths already did. Co-authored-by: davidxyuan <124700534+davidxyuan@users.noreply.github.com>