feat(voice): extend "Hey Hermes" wake word to TUI + desktop GUI
Makes the wake word a tri-surface feature with one configurable owner.
- wake_word.surface ("auto" | "cli" | "tui" | "gui") + shared
wake_surface_enabled() gate consulted by every surface, so exactly one
place owns the listener and the new session it opens.
- tui_gateway: wake.start/stop/pause/resume/status RPCs + a wake.detected
event, sharing one server-side detector for both TUI and desktop. The
detector yields the mic to voice.record (pause on capture start, resume
on terminal) and to the desktop's browser mic (wake.pause/resume).
- TUI (Ink): arm wake.start on gateway.ready; on wake.detected open a
fresh session and start voice capture.
- Desktop (Electron): arm wake.start on connect; on wake.detected open a
fresh session.
- CLI now gates on wake_surface_enabled("cli"); /wake status shows surface.
- Tests for the surface gate; docs cover the surface knob + cross-surface.
This commit is contained in:
committed by
Teknium
parent
5f43452e91
commit
86d5b8b90f
@@ -49,12 +49,16 @@ _FIRE_COOLDOWN_SECONDS = 2.0
|
||||
|
||||
_DEFAULTS: Dict[str, Any] = {
|
||||
"enabled": False,
|
||||
"surface": "auto",
|
||||
"provider": "openwakeword",
|
||||
"phrase": "hey jarvis",
|
||||
"sensitivity": 0.5,
|
||||
"start_new_session": True,
|
||||
}
|
||||
|
||||
# Surfaces that can host the listener. "auto" means whichever one is running.
|
||||
SURFACES = ("cli", "tui", "gui")
|
||||
|
||||
|
||||
def load_wake_word_config() -> Dict[str, Any]:
|
||||
"""Return the ``wake_word`` config section, shape-guarded to a dict."""
|
||||
@@ -91,6 +95,20 @@ def wake_phrase(cfg: Optional[Dict[str, Any]] = None) -> str:
|
||||
return str(_get(cfg, "phrase")) or "hey jarvis"
|
||||
|
||||
|
||||
def wake_surface_enabled(surface: str, cfg: Optional[Dict[str, Any]] = None) -> bool:
|
||||
"""Should ``surface`` (``cli`` / ``tui`` / ``gui``) host the listener?
|
||||
|
||||
True when the wake word is enabled and the configured ``surface`` is either
|
||||
``auto`` or this exact surface — the single gate every surface consults so
|
||||
only one place owns the wake word and the new session it opens.
|
||||
"""
|
||||
cfg = cfg if cfg is not None else load_wake_word_config()
|
||||
if not cfg.get("enabled"):
|
||||
return False
|
||||
want = str(_get(cfg, "surface")).strip().lower() or "auto"
|
||||
return want == "auto" or want == surface.strip().lower()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Audio capture (lazy — never import sounddevice at module load)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user