"many many times during the day, the orb speaks out without me addressing it. it happened today while i was on a meeting. also, the voice sounded like the male robot again."
Three independent paths to unprompted speech in HeyBrian. I traced each one:
| # | Path | Trigger | Where |
|---|---|---|---|
| 1 | Proactive /notify |
Hetzner POSTs to http://100.79.172.24:9100/notify {"message":"...", "speak":true} → HB shows orb + speaks |
HTTPServer.swift:210 → AppDelegate.onNotify (L598) → self.speak(message) (L608) |
| 2 | Wake-word false positive during meeting | Mic picks up other speakers (Zoom/Teams audio bleed) → wakeword_daemon fires above 0.65 threshold → SFSpeechRecognizer transcribes the OTHER speaker → routed to Brian API → Brian replies + speaks | wakeword_daemon.py (threshold 0.65, cooldown 4s) → AppDelegate.wakeWordDetected() (L161) → _actuallyStartListening() → commandRouter.route(text:fromVoice:true) → speak(response.spoken) |
| 3 | Male-robot TTS fallback | Puck (Gemini Live) request fails / times out → TTSManager.speakLocal() falls through to NSSpeechSynthesizer (the macOS robot voice) |
TTSManager.swift:104 — "remote Puck failed, falling back to local AVSpeech" |
New endpoints on the agent-api:
- GET /api/mac/heybrian/dnd — read current state + inbox count
- POST /api/mac/heybrian/dnd {"enabled": true|false, "reason": "..."} — toggle
- GET /api/mac/heybrian/dnd/inbox?limit=50 — read suppressed notifications
- POST /api/mac/heybrian/dnd/inbox/clear — wipe inbox
Modified POST /api/mac/heybrian/notify — when DND on, request is logged to inbox + 200 returned but NEVER forwarded to Mac. The orb stays silent.
When /api/meetings/active/ returns count > 0 (a real recording session is live), DND auto-engages even with manual toggle off. Meeting ends → auto-DND clears. You never have to remember to flip it.
G02 (Wake-word daemon) now has pause and resume verbs. Pause kills the wakeword_daemon Python process on your Mac — HB stays running but won't react to anything resembling "Hey Brian" until you resume. This completely eliminates path #2 during meetings, no false positives possible. Resume → opens HB which respawns the daemon within 2 seconds.
New function DND in the mac_app group with verbs enable / disable / inbox / clear-inbox. Click into Mac App tile → focused sheet → DND row → toggle. Inbox verb opens the inline log viewer showing every suppressed notification with timestamp, source, priority, and the 🔊 flag if it would have spoken.
The panel also auto-engages DND visually when a meeting is recording — status pill turns amber with "AUTO (meeting) · N suppressed".
Patches written to source — will take effect on next revive of HeyBrian.app:
wakeword_daemon.pyTTSManager.swift — robot-voice fix (path #3)Removed the AVSpeech fallback by default. When Puck fails, HB now stays silent instead of falling through to the male-robot voice. The text still surfaces in HB's log (and we can route it to the panel conversation log too).
Legacy behavior preserved via BRIAN_ALLOW_ROBOT_FALLBACK=1 env var if you ever want the robot back temporarily.
Before a meeting:
- Open mac.jonahtebaa.com → Mac App tile → DND row → click enable
- Optionally: Wake-word row → click pause
When the meeting ends:
- DND row → click disable
- Wake-word row → click resume
Or do nothing. If you start a meeting via the Meetings tile (click Meetings → start), auto-DND engages and clears itself. The wake-word pause still requires a manual click because killing the daemon during a meeting auto-started elsewhere (Cmd-M from menubar) isn't a server-known event.
When you're ready to rebuild HeyBrian.app with the threshold + TTS fixes:
- Click Mac App tile → HeyBrian.app row → restart (hard restart) — this rebuilds + relaunches HB picking up the new wakeword threshold + the silence-over-robot TTS behavior
Or wait — the changes are in the source tree, the next planned HB rebuild will pick them up automatically.
| Test | Result |
|---|---|
POST /heybrian/dnd {enabled:true} |
state.enabled=true, set_by=J0n@h ✓ |
3× POST /heybrian/notify while DND on |
All 3 return suppressed_by_dnd:true reason:manual ✓ |
| Inbox via panel verb | All 3 entries logged with ts, source, message, 🔊 flag ✓ |
POST /heybrian/dnd {enabled:false} |
effective_enabled=false ✓ |
| Auto-meeting DND | When /api/meetings/active/ count>0 → effective_enabled=true ✓ |
| Wake-word G02 verbs | pause, resume, logs, configure ✓ |
| Panel groups total | 46 functions (was 45) across 6 tiles ✓ |