mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(gateway): namespace voice mode state by platform to prevent cross-platform collision (#12542)
This commit is contained in:
parent
be3bec55be
commit
52a972e927
2 changed files with 281 additions and 18 deletions
|
|
@ -786,6 +786,10 @@ class GatewayRunner:
|
|||
|
||||
_VOICE_MODE_PATH = _hermes_home / "gateway_voice_mode.json"
|
||||
|
||||
def _voice_key(self, platform: Platform, chat_id: str) -> str:
|
||||
"""Return a platform-namespaced key for voice mode state."""
|
||||
return f"{platform.value}:{chat_id}"
|
||||
|
||||
def _load_voice_modes(self) -> Dict[str, str]:
|
||||
try:
|
||||
data = json.loads(self._VOICE_MODE_PATH.read_text())
|
||||
|
|
@ -796,11 +800,21 @@ class GatewayRunner:
|
|||
return {}
|
||||
|
||||
valid_modes = {"off", "voice_only", "all"}
|
||||
return {
|
||||
str(chat_id): mode
|
||||
for chat_id, mode in data.items()
|
||||
if mode in valid_modes
|
||||
}
|
||||
result = {}
|
||||
for chat_id, mode in data.items():
|
||||
if mode not in valid_modes:
|
||||
continue
|
||||
key = str(chat_id)
|
||||
# Skip legacy unprefixed keys (warn and skip)
|
||||
if ":" not in key:
|
||||
logger.warning(
|
||||
"Skipping legacy unprefixed voice mode key %r during migration. "
|
||||
"Re-enable voice mode on that chat to rebuild the prefixed key.",
|
||||
key,
|
||||
)
|
||||
continue
|
||||
result[key] = mode
|
||||
return result
|
||||
|
||||
def _save_voice_modes(self) -> None:
|
||||
try:
|
||||
|
|
@ -826,9 +840,14 @@ class GatewayRunner:
|
|||
disabled_chats = getattr(adapter, "_auto_tts_disabled_chats", None)
|
||||
if not isinstance(disabled_chats, set):
|
||||
return
|
||||
platform = getattr(adapter, "platform", None)
|
||||
if not isinstance(platform, Platform):
|
||||
return
|
||||
disabled_chats.clear()
|
||||
prefix = f"{platform.value}:"
|
||||
disabled_chats.update(
|
||||
chat_id for chat_id, mode in self._voice_mode.items() if mode == "off"
|
||||
key[len(prefix):] for key, mode in self._voice_mode.items()
|
||||
if mode == "off" and key.startswith(prefix)
|
||||
)
|
||||
|
||||
async def _safe_adapter_disconnect(self, adapter, platform) -> None:
|
||||
|
|
@ -5830,11 +5849,13 @@ class GatewayRunner:
|
|||
"""Handle /voice [on|off|tts|channel|leave|status] command."""
|
||||
args = event.get_command_args().strip().lower()
|
||||
chat_id = event.source.chat_id
|
||||
platform = event.source.platform
|
||||
voice_key = self._voice_key(platform, chat_id)
|
||||
|
||||
adapter = self.adapters.get(event.source.platform)
|
||||
adapter = self.adapters.get(platform)
|
||||
|
||||
if args in ("on", "enable"):
|
||||
self._voice_mode[chat_id] = "voice_only"
|
||||
self._voice_mode[voice_key] = "voice_only"
|
||||
self._save_voice_modes()
|
||||
if adapter:
|
||||
self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=False)
|
||||
|
|
@ -5844,13 +5865,13 @@ class GatewayRunner:
|
|||
"Use /voice tts to get voice replies for all messages."
|
||||
)
|
||||
elif args in ("off", "disable"):
|
||||
self._voice_mode[chat_id] = "off"
|
||||
self._voice_mode[voice_key] = "off"
|
||||
self._save_voice_modes()
|
||||
if adapter:
|
||||
self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=True)
|
||||
return "Voice mode disabled. Text-only replies."
|
||||
elif args == "tts":
|
||||
self._voice_mode[chat_id] = "all"
|
||||
self._voice_mode[voice_key] = "all"
|
||||
self._save_voice_modes()
|
||||
if adapter:
|
||||
self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=False)
|
||||
|
|
@ -5863,7 +5884,7 @@ class GatewayRunner:
|
|||
elif args == "leave":
|
||||
return await self._handle_voice_channel_leave(event)
|
||||
elif args == "status":
|
||||
mode = self._voice_mode.get(chat_id, "off")
|
||||
mode = self._voice_mode.get(voice_key, "off")
|
||||
labels = {
|
||||
"off": "Off (text only)",
|
||||
"voice_only": "On (voice reply to voice messages)",
|
||||
|
|
@ -5887,15 +5908,15 @@ class GatewayRunner:
|
|||
return f"Voice mode: {labels.get(mode, mode)}"
|
||||
else:
|
||||
# Toggle: off → on, on/all → off
|
||||
current = self._voice_mode.get(chat_id, "off")
|
||||
current = self._voice_mode.get(voice_key, "off")
|
||||
if current == "off":
|
||||
self._voice_mode[chat_id] = "voice_only"
|
||||
self._voice_mode[voice_key] = "voice_only"
|
||||
self._save_voice_modes()
|
||||
if adapter:
|
||||
self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=False)
|
||||
return "Voice mode enabled."
|
||||
else:
|
||||
self._voice_mode[chat_id] = "off"
|
||||
self._voice_mode[voice_key] = "off"
|
||||
self._save_voice_modes()
|
||||
if adapter:
|
||||
self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=True)
|
||||
|
|
@ -5941,7 +5962,7 @@ class GatewayRunner:
|
|||
adapter._voice_text_channels[guild_id] = int(event.source.chat_id)
|
||||
if hasattr(adapter, "_voice_sources"):
|
||||
adapter._voice_sources[guild_id] = event.source.to_dict()
|
||||
self._voice_mode[event.source.chat_id] = "all"
|
||||
self._voice_mode[self._voice_key(Platform.DISCORD, event.source.chat_id)] = "all"
|
||||
self._save_voice_modes()
|
||||
self._set_adapter_auto_tts_disabled(adapter, event.source.chat_id, disabled=False)
|
||||
return (
|
||||
|
|
@ -5968,7 +5989,7 @@ class GatewayRunner:
|
|||
except Exception as e:
|
||||
logger.warning("Error leaving voice channel: %s", e)
|
||||
# Always clean up state even if leave raised an exception
|
||||
self._voice_mode[event.source.chat_id] = "off"
|
||||
self._voice_mode[self._voice_key(Platform.DISCORD, event.source.chat_id)] = "off"
|
||||
self._save_voice_modes()
|
||||
self._set_adapter_auto_tts_disabled(adapter, event.source.chat_id, disabled=True)
|
||||
if hasattr(adapter, "_voice_input_callback"):
|
||||
|
|
@ -5980,7 +6001,7 @@ class GatewayRunner:
|
|||
|
||||
Cleans up runner-side voice_mode state that the adapter cannot reach.
|
||||
"""
|
||||
self._voice_mode[chat_id] = "off"
|
||||
self._voice_mode[self._voice_key(Platform.DISCORD, chat_id)] = "off"
|
||||
self._save_voice_modes()
|
||||
adapter = self.adapters.get(Platform.DISCORD)
|
||||
self._set_adapter_auto_tts_disabled(adapter, chat_id, disabled=True)
|
||||
|
|
@ -6066,7 +6087,7 @@ class GatewayRunner:
|
|||
return False
|
||||
|
||||
chat_id = event.source.chat_id
|
||||
voice_mode = self._voice_mode.get(chat_id, "off")
|
||||
voice_mode = self._voice_mode.get(self._voice_key(event.source.platform, chat_id), "off")
|
||||
is_voice_input = (event.message_type == MessageType.VOICE)
|
||||
|
||||
should = (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue