fix(voicewake): avoid crash on foreign transcript ranges

This commit is contained in:
ImLukeF
2026-03-14 12:37:11 +11:00
parent 8de2f7339c
commit a69a5a0d59
3 changed files with 43 additions and 12 deletions

View File

@@ -106,20 +106,14 @@ public enum WakeWordGate {
triggerEndTime: TimeInterval) triggerEndTime: TimeInterval)
-> String { -> String {
let threshold = triggerEndTime + 0.001 let threshold = triggerEndTime + 0.001
var commandWords: [String] = []
commandWords.reserveCapacity(segments.count)
for segment in segments where segment.start >= threshold { for segment in segments where segment.start >= threshold {
if normalizeToken(segment.text).isEmpty { continue } let normalized = normalizeToken(segment.text)
if let range = segment.range { if normalized.isEmpty { continue }
let slice = transcript[range.lowerBound...] commandWords.append(segment.text)
return String(slice).trimmingCharacters(in: Self.whitespaceAndPunctuation)
}
break
} }
return commandWords.joined(separator: " ").trimmingCharacters(in: Self.whitespaceAndPunctuation)
let text = segments
.filter { $0.start >= threshold && !normalizeToken($0.text).isEmpty }
.map(\.text)
.joined(separator: " ")
return text.trimmingCharacters(in: Self.whitespaceAndPunctuation)
} }
public static func matchesTextOnly(text: String, triggers: [String]) -> Bool { public static func matchesTextOnly(text: String, triggers: [String]) -> Bool {

View File

@@ -46,6 +46,25 @@ import Testing
let match = WakeWordGate.match(transcript: transcript, segments: segments, config: config) let match = WakeWordGate.match(transcript: transcript, segments: segments, config: config)
#expect(match?.command == "do it") #expect(match?.command == "do it")
} }
@Test func commandTextHandlesForeignRangeIndices() {
let transcript = "hey clawd do thing"
let other = "do thing"
let foreignRange = other.range(of: "do")
let segments = [
WakeWordSegment(text: "hey", start: 0.0, duration: 0.1, range: transcript.range(of: "hey")),
WakeWordSegment(text: "clawd", start: 0.2, duration: 0.1, range: transcript.range(of: "clawd")),
WakeWordSegment(text: "do", start: 0.9, duration: 0.1, range: foreignRange),
WakeWordSegment(text: "thing", start: 1.1, duration: 0.1, range: nil),
]
let command = WakeWordGate.commandText(
transcript: transcript,
segments: segments,
triggerEndTime: 0.3)
#expect(command == "do thing")
}
} }
private func makeSegments( private func makeSegments(

View File

@@ -74,4 +74,22 @@ struct VoiceWakeRuntimeTests {
let config = WakeWordGateConfig(triggers: ["openclaw"], minPostTriggerGap: 0.3) let config = WakeWordGateConfig(triggers: ["openclaw"], minPostTriggerGap: 0.3)
#expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config)?.command == "do thing") #expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config)?.command == "do thing")
} }
@Test func `gate command text handles foreign string ranges`() {
let transcript = "hey openclaw do thing"
let other = "do thing"
let foreignRange = other.range(of: "do")
let segments = [
WakeWordSegment(text: "hey", start: 0.0, duration: 0.1, range: transcript.range(of: "hey")),
WakeWordSegment(text: "openclaw", start: 0.2, duration: 0.1, range: transcript.range(of: "openclaw")),
WakeWordSegment(text: "do", start: 0.9, duration: 0.1, range: foreignRange),
WakeWordSegment(text: "thing", start: 1.1, duration: 0.1, range: nil),
]
#expect(
WakeWordGate.commandText(
transcript: transcript,
segments: segments,
triggerEndTime: 0.3) == "do thing")
}
} }