From f79a15881fbbf625eb8efda94b7f9a028513268f Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Tue, 20 Oct 2026 02:26:38 +0400 Subject: [PATCH] fix: pick exact locator match --- src-tauri/src/mcp_server.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/mcp_server.rs b/src-tauri/src/mcp_server.rs index 30be5265..a0a8c6f7 100644 --- a/src-tauri/src/mcp_server.rs +++ b/src-tauri/src/mcp_server.rs @@ -1537,6 +1537,17 @@ fn preferred_candidate( (kept.len() == 1).then(|| kept[0]) } +/// A candidate from the browser's ambiguity reply as a match. That reply lists +/// no box, and the box is read again once the node is confirmed. +fn ambiguity_candidate(candidate: &serde_json::Value) -> Option { + let mut candidate = candidate.clone(); + let fields = candidate.as_object_mut()?; + fields + .entry("bounds") + .or_insert_with(|| serde_json::json!({ "x": 0.0, "y": 0.0, "width": 0.0, "height": 0.0 })); + serde_json::from_value(candidate).ok() +} + /// The ambiguity refusal, listing at most `limit` candidates. fn ambiguity( match_count: u64, @@ -1833,7 +1844,7 @@ async fn wayfern_locate( .. }) => { let preferred = preferred_candidate(locator, match_count, &candidates) - .and_then(|index| serde_json::from_value(candidates[index].clone()).ok()); + .and_then(|index| ambiguity_candidate(&candidates[index])); match preferred { Some(matched) => Some(matched), None => return Err(ambiguity(match_count, candidates, candidate_limit)), @@ -15319,6 +15330,21 @@ mod tests { .starts_with("Ambiguous locator: 30 nodes match")); assert!(refused.message().contains("Row 4") && !refused.message().contains("Row 5\"")); } + #[test] + fn a_candidate_from_the_browsers_ambiguity_reply_becomes_a_match() { + // The browser's reply lists candidates without a box. + let candidate = serde_json::json!({ + "backendNodeId": 4, + "role": "textField", + "name": "First Name", + "text": "", + "signature": "8eb94bf54df1ce79", + "attributes": [{ "name": "id", "value": "first-name" }], + }); + let matched = ambiguity_candidate(&candidate).expect("a candidate without bounds still reads"); + assert_eq!(matched.name, "First Name"); + assert_eq!(matched.backend_node_id, Some(4)); + } #[tokio::test] async fn a_native_ambiguity_with_one_exact_name_is_not_refused() {