fix(voice): Ohr re-armt nach Fast-Path-Befehl (kein TTS → kein Re-Arm-Trigger)

Fast-Path-Antworten sind speak=False → kein TTS → onPlaybackFinished feuert
nie. Das Wake-Word-Re-Arm haengt aber genau daran → nach "nächster Titel"
blieb das Ohr grau in 'conversing' stecken (Stefan im Auto, 2 Min gewartet,
kein Re-Arm). Log bestaetigt: nach stream.final kam kein wake.end/wake.start.

- Bridge: chat-Payload traegt jetzt 'speak' (war nur answeredBy).
- App: bei stiller Antwort (speak=false ODER answeredBy=fast-path) und
  laufender Konversation dieselbe Re-Arm-Logik wie bei TTS-Ende anstossen
  (aktiv → resume/Konversationsfenster, sonst → endConversation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 20:40:57 +02:00
co-authored by Claude Opus 4.8
parent aefdff89dc
commit 2b48e5cac6
2 changed files with 20 additions and 0 deletions
+15
View File
@@ -1203,6 +1203,21 @@ const ChatScreen: React.FC = () => {
clearAckTimer(cmid);
pendingPayloads.current.delete(cmid);
}
// Stille Antwort (speak=false, z.B. Fast-Path „nächster Titel") → es
// kommt KEIN TTS, also feuert onPlaybackFinished nie. Genau daran haengt
// aber das Wake-Word-Re-Arm — sonst bleibt das Ohr nach dem Steuerbefehl
// grau haengen (Reproduktion Stefan im Auto). Hier dieselbe Logik wie
// bei TTS-Ende anstossen: aktiv → Konversations-Fenster (resume),
// sonst → Konversation beenden (re-arm).
const _isSilent = (message.payload as any).speak === false
|| ((message.payload as any).answeredBy === 'fast-path');
if (_isSilent && wakeWordService.isConversing()) {
if (AppState.currentState === 'active') {
wakeWordService.resume();
} else {
wakeWordService.endConversation().catch(() => {});
}
}
}
// TTS-Audio abspielen wenn vorhanden — respektiert geraetelokalen Mute/Disable
+5
View File
@@ -1366,6 +1366,11 @@ class ARIABridge:
"projectId": (payload.get("projectId") or "") if isinstance(payload, dict) else "",
# Quell-Backend (local/claude/fast-path) fuer den Diagnostic-Badge.
"answeredBy": (payload.get("answeredBy") or "") if isinstance(payload, dict) else "",
# Wird diese Antwort vorgelesen? Fast-Path/Steuerbefehl = False.
# Die App braucht das: ohne TTS feuert onPlaybackFinished nie,
# und daran haengt das Wake-Word-Re-Arm — sonst bleibt das Ohr
# nach einem Fast-Path-Befehl grau haengen.
"speak": bool(payload.get("speak", True)) if isinstance(payload, dict) else True,
},
"timestamp": int(asyncio.get_event_loop().time() * 1000),
})