diff --git a/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift b/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift index 805211122..b4049a0a9 100644 --- a/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift +++ b/apps/macos/Sources/Moltbot/VoiceWakeRuntime.swift @@ -740,6 +740,8 @@ actor VoiceWakeRuntime { let token = trigger.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) guard !token.isEmpty, let range = lower.range(of: token) else { continue } let after = range.upperBound + // Guard against index out of bounds when the trigger is at the end of the string + guard after <= text.endIndex else { continue } let trimmed = text[after...].trimmingCharacters(in: .whitespacesAndNewlines) return String(trimmed) } diff --git a/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift index 137df3ec7..10b1dfd8b 100644 --- a/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift +++ b/apps/macos/Tests/MoltbotIPCTests/VoiceWakeRuntimeTests.swift @@ -35,6 +35,19 @@ import Testing #expect(VoiceWakeRuntime._testHasContentAfterTrigger(text, triggers: triggers)) } + @Test func trimsAfterTriggerHandlesTriggerAtEnd() { + let triggers = ["clawd"] + let text = "hey clawd" + #expect(VoiceWakeRuntime._testTrimmedAfterTrigger(text, triggers: triggers) == "") + } + + @Test func trimsAfterTriggerHandlesEdgeCaseIndexBounds() { + // Regression test for crash when trigger range upperBound exceeds text.endIndex + let triggers = ["claude"] + let text = "claude" + #expect(VoiceWakeRuntime._testTrimmedAfterTrigger(text, triggers: triggers) == "") + } + @Test func gateRequiresGapBetweenTriggerAndCommand() { let transcript = "hey clawd do thing" let segments = makeSegments(