Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions crates/genie-core/src/tools/quick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2589,10 +2589,19 @@ fn home_status_target(text: &str) -> Option<String> {
});
}

if contains_any(
&target,
&["switch", "switches", "plug", "plugs", "outlet", "outlets"],
) {
// Match the switch/plug/outlet tokens as whole words, not substrings: a bare
// `contains_any` fired on "[switch]board" / "[switch]gear", "un[plug]ged" /
// "ear[plug]s" and "[plug]in", so "what is the switchboard status" collapsed
// to the whole-house "switches" readout and "is the plugin enabled" / "are
// the earplugs in the drawer" misrouted to a garbled home_status entity
// instead of abstaining. Mirrors the ice/iron/cooktop/cover/car whole-word
// fixes elsewhere in this function.
if target.split_whitespace().any(|word| {
matches!(
word,
"switch" | "switches" | "plug" | "plugs" | "outlet" | "outlets"
)
}) {
return Some(if target.split_whitespace().count() == 1 {
"switches".into()
} else {
Expand Down Expand Up @@ -5753,6 +5762,41 @@ mod tests {
}
}

#[test]
fn switch_and_outlet_status_match_whole_words_not_substrings() {
// The switch/plug/outlet branch matched its tokens with a substring
// `contains_any`, so "[switch]board" / "[switch]gear", "un[plug]ged" /
// "ear[plug]s" and "[plug]in" all fired it. "what is the switchboard
// status" collapsed to the whole-house "switches" readout, and the
// multi-word cases misrouted to a garbled home_status entity.
for utterance in [
"what is the switchboard status",
"is the switchgear ok",
"is the plugin enabled",
"is the toaster unplugged",
"are the earplugs in the drawer",
] {
assert!(
route(utterance).is_none(),
"{utterance:?} must abstain, not resolve to a switch/plug/outlet status entity"
);
}

// Genuine switch/plug/outlet queries still resolve exactly as before.
for (utterance, entity) in [
("are the switches on", "switches"),
("is the switch on", "switches"),
("are the outlets on", "switches"),
("check the outlet", "switches"),
("is the kitchen plug on", "kitchen plug"),
("are the kitchen plugs on", "kitchen plugs"),
] {
let call = route(utterance).unwrap_or_else(|| panic!("no route for {utterance:?}"));
assert_eq!(call.name, "home_status", "{utterance:?}");
assert_eq!(call.arguments["entity"], entity, "{utterance:?}");
}
}

#[test]
fn control_entity_drops_leading_indefinite_article() {
// clean_control_entity stripped a leading "the " but left "a"/"an", so
Expand Down
Loading