feat(voice): route wake phrases to their profile — "hey <profile>" wakes that profile

One sherpa listener now enrolls every wake-enabled profile's phrase
(defaulting to "hey <profile name>") and reports WHICH phrase fired.
wake.detected gains a profile field; the desktop live-switches to the
matching profile (same path as the profile rail), opens a fresh session
there, and starts hands-free voice. The single-profile CLI/TUI print the
hermes -p switch command for foreign-profile phrases instead of
answering as the wrong profile. Opt out per listener with
wake_word.profile_routing: false.
This commit is contained in:
Hermes Agent 2026-07-24 09:52:00 -07:00 committed by Teknium
parent 567f47f01f
commit 2a35c8f0b8
No known key found for this signature in database
10 changed files with 274 additions and 16 deletions

16
cli.py
View file

@ -12278,6 +12278,22 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
return
self._wake_suspended = True
# Multi-profile routing: the CLI is a single-profile process, so a
# phrase enrolled by ANOTHER profile can't be routed here — print the
# switch command and re-arm rather than answering as the wrong profile.
try:
from tools.wake_word import get_last_match
_match = get_last_match()
except Exception:
_match = None
if _match and _match[1]:
from tools.wake_word import _active_profile_name
if _match[1] != _active_profile_name():
_cprint(f"\n{_DIM}Wake phrase for profile '{_match[1]}'"
f"run: hermes -p {_match[1]}{_RST}")
self._wake_suspended = True # watchdog resumes the listener
return
_cprint(f"\n{_ACCENT}✦ Wake word detected — listening...{_RST}")
if getattr(self, "_app", None):
try: