fix(desktop): handle wake.detected on the canonical event pipeline

The GUI armed the detector (wake.start) and the gateway fired
wake.detected, but the desktop never reacted: detection was wired through
a side-registered gatewayRef.current.on('wake.detected', …) listener that
was instance/timing-fragile (and silently dead across reconnects/HMR),
even though the raw events were arriving on the socket.

Route wake.detected through handleGatewayEventWithWake — the same onEvent
pipeline every gateway socket already feeds via useGatewayBoot — and open
a fresh session + start back-and-forth voice there. Drop the separate
.on() listener; the open-effect now only arms wake.start.
This commit is contained in:
Brooklyn Nicholson 2026-06-26 22:38:21 -05:00 committed by Teknium
parent d2fab75ffb
commit a6aada24f5
No known key found for this signature in database

View file

@ -739,6 +739,21 @@ export function DesktopController() {
updateSessionState
})
// "Hey Hermes": handle the wake event on the canonical onEvent pipeline (the
// path every gateway socket already feeds), not a side-registered listener —
// open a fresh session and begin back-and-forth voice.
const handleGatewayEventWithWake = useCallback(
(event: Parameters<typeof handleDesktopGatewayEvent>[0]) => {
if (event.type === 'wake.detected') {
startFreshSessionDraft()
requestVoiceStart()
return
}
handleDesktopGatewayEvent(event)
},
[handleDesktopGatewayEvent, startFreshSessionDraft]
)
// Single global listener for every rebindable hotkey (incl. profile switching)
// plus the on-screen keybind editor's capture mode.
useKeybinds({
@ -987,7 +1002,7 @@ export function DesktopController() {
}, [])
useGatewayBoot({
handleGatewayEvent: handleDesktopGatewayEvent,
handleGatewayEvent: handleGatewayEventWithWake,
onConnectionReady: c => {
connectionRef.current = c
},
@ -1007,24 +1022,14 @@ export function DesktopController() {
}, [gatewayState, refreshCurrentModel, refreshSessions])
// "Hey Hermes" wake word: arm the server-side detector for this surface
// (gated on config) and open a fresh session when it fires. Idempotent and
// self-cleaning across reconnects.
// (gated on config). Detection arrives as a wake.detected event handled in
// handleGatewayEventWithWake. Idempotent server-side, so reconnects are safe.
useEffect(() => {
if (gatewayState !== 'open') {
return
}
void requestGateway('wake.start', { surface: 'gui' }).catch(() => undefined)
const gw = gatewayRef.current
if (!gw) {
return
}
return gw.on('wake.detected', () => {
startFreshSessionDraft()
// Begin hands-free back-and-forth voice in the fresh session. The bus
// defers a tick, so the new composer is mounted before voice starts.
requestVoiceStart()
})
}, [gatewayState, requestGateway, gatewayRef, startFreshSessionDraft])
}, [gatewayState, requestGateway])
// Keep the cron jobs section live without a user action: the scheduler ticks
// in the background (advancing next-run/state and creating runs), so poll the