From 9e26fe44597adf46dbf6a53b3a037a9ec671e161 Mon Sep 17 00:00:00 2001 From: Mariano <132747814+mbelinky@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:37:10 +0000 Subject: [PATCH] fix(ios): gate talk barge-in on isolated audio routes (#18265) Co-authored-by: Mariano Belinky --- CHANGELOG.md | 1 + apps/ios/Sources/Voice/TalkModeManager.swift | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf7317c5f7a..4c795a2d085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai - Cron/Gateway: separate per-job webhook delivery (`delivery.mode = "webhook"`) from announce delivery, enforce valid HTTP(S) webhook URLs, and keep a temporary legacy `notify + cron.webhook` fallback for stored jobs. (#17901) Thanks @advaitpaliwal. - iOS/Talk: add a `Voice Directive Hint` toggle for Talk Mode prompts so users can disable ElevenLabs voice-switching instructions to save tokens when not needed. (#18250) Thanks @zeulewan. - iOS/Talk: add a `Background Listening` toggle that keeps Talk Mode active while the app is backgrounded (off by default for battery safety). Thanks @zeulewan. +- iOS/Talk: harden barge-in behavior by disabling interrupt-on-speech when output route is built-in speaker/receiver, reducing false interruptions from local TTS bleed-through. Thanks @zeulewan. - Telegram/Agents: add inline button `style` support (`primary|success|danger`) across message tool schema, Telegram action parsing, send pipeline, and runtime prompt guidance. (#18241) Thanks @obviyus. ### Fixes diff --git a/apps/ios/Sources/Voice/TalkModeManager.swift b/apps/ios/Sources/Voice/TalkModeManager.swift index 5691da8f664..be90208af47 100644 --- a/apps/ios/Sources/Voice/TalkModeManager.swift +++ b/apps/ios/Sources/Voice/TalkModeManager.swift @@ -1115,6 +1115,7 @@ final class TalkModeManager: NSObject { } private func shouldInterrupt(with transcript: String) -> Bool { + guard self.shouldAllowSpeechInterruptForCurrentRoute() else { return false } let trimmed = transcript.trimmingCharacters(in: .whitespacesAndNewlines) guard trimmed.count >= 3 else { return false } if let spoken = self.lastSpokenText?.lowercased(), spoken.contains(trimmed.lowercased()) { @@ -1123,6 +1124,20 @@ final class TalkModeManager: NSObject { return true } + private func shouldAllowSpeechInterruptForCurrentRoute() -> Bool { + let route = AVAudioSession.sharedInstance().currentRoute + // Built-in speaker/receiver often feeds TTS back into STT, causing false interrupts. + // Allow barge-in for isolated outputs (headphones/Bluetooth/USB/CarPlay/AirPlay). + return !route.outputs.contains { output in + switch output.portType { + case .builtInSpeaker, .builtInReceiver: + return true + default: + return false + } + } + } + private func shouldUseIncrementalTTS() -> Bool { true }