react-native-ai-debugger 1.6.15 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +24 -14
  2. package/build/__tests__/unit/exec.test.d.ts +2 -0
  3. package/build/__tests__/unit/exec.test.d.ts.map +1 -0
  4. package/build/__tests__/unit/exec.test.js +54 -0
  5. package/build/__tests__/unit/exec.test.js.map +1 -0
  6. package/build/__tests__/unit/focusedInput.test.d.ts +2 -0
  7. package/build/__tests__/unit/focusedInput.test.d.ts.map +1 -0
  8. package/build/__tests__/unit/focusedInput.test.js +58 -0
  9. package/build/__tests__/unit/focusedInput.test.js.map +1 -0
  10. package/build/__tests__/unit/focusedInputTools.test.d.ts +2 -0
  11. package/build/__tests__/unit/focusedInputTools.test.d.ts.map +1 -0
  12. package/build/__tests__/unit/focusedInputTools.test.js +79 -0
  13. package/build/__tests__/unit/focusedInputTools.test.js.map +1 -0
  14. package/build/__tests__/unit/tap.test.js +46 -0
  15. package/build/__tests__/unit/tap.test.js.map +1 -1
  16. package/build/core/android.d.ts +3 -3
  17. package/build/core/android.d.ts.map +1 -1
  18. package/build/core/android.js +21 -14
  19. package/build/core/android.js.map +1 -1
  20. package/build/core/connection.d.ts.map +1 -1
  21. package/build/core/connection.js +3 -2
  22. package/build/core/connection.js.map +1 -1
  23. package/build/core/errors.d.ts +14 -0
  24. package/build/core/errors.d.ts.map +1 -0
  25. package/build/core/errors.js +17 -0
  26. package/build/core/errors.js.map +1 -0
  27. package/build/core/exec.d.ts +22 -0
  28. package/build/core/exec.d.ts.map +1 -0
  29. package/build/core/exec.js +54 -0
  30. package/build/core/exec.js.map +1 -0
  31. package/build/core/executor.d.ts +2 -0
  32. package/build/core/executor.d.ts.map +1 -1
  33. package/build/core/executor.js +232 -2
  34. package/build/core/executor.js.map +1 -1
  35. package/build/core/focusedInput.d.ts +27 -0
  36. package/build/core/focusedInput.d.ts.map +1 -0
  37. package/build/core/focusedInput.js +110 -0
  38. package/build/core/focusedInput.js.map +1 -0
  39. package/build/core/focusedInputTools.d.ts +21 -0
  40. package/build/core/focusedInputTools.d.ts.map +1 -0
  41. package/build/core/focusedInputTools.js +51 -0
  42. package/build/core/focusedInputTools.js.map +1 -0
  43. package/build/core/guides.d.ts.map +1 -1
  44. package/build/core/guides.js +15 -2
  45. package/build/core/guides.js.map +1 -1
  46. package/build/core/index.d.ts +1 -1
  47. package/build/core/index.d.ts.map +1 -1
  48. package/build/core/index.js +1 -1
  49. package/build/core/index.js.map +1 -1
  50. package/build/core/ios.d.ts +8 -0
  51. package/build/core/ios.d.ts.map +1 -1
  52. package/build/core/ios.js +142 -2
  53. package/build/core/ios.js.map +1 -1
  54. package/build/core/ocr.d.ts +13 -0
  55. package/build/core/ocr.d.ts.map +1 -1
  56. package/build/core/ocr.js +37 -15
  57. package/build/core/ocr.js.map +1 -1
  58. package/build/core/posthog.d.ts.map +1 -1
  59. package/build/core/posthog.js +7 -1
  60. package/build/core/posthog.js.map +1 -1
  61. package/build/index.d.ts.map +1 -1
  62. package/build/index.js +158 -31
  63. package/build/index.js.map +1 -1
  64. package/build/pro/tap.d.ts +51 -5
  65. package/build/pro/tap.d.ts.map +1 -1
  66. package/build/pro/tap.js +457 -54
  67. package/build/pro/tap.js.map +1 -1
  68. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Marker class for errors caused by invalid agent input (wrong device name,
3
+ * missing required predicate, ambiguous match, etc). H2 (Step 9 in the
4
+ * 2026-05-15 plan): the top-level catch in index.ts skips PostHog
5
+ * captureException for these so error tracking surfaces real product bugs,
6
+ * not validation noise (~13% of recent dashboard volume).
7
+ *
8
+ * The trade-off vs regex-matching the message: this is self-documenting at
9
+ * the throw site and survives message reformatting / translation.
10
+ */
11
+ export declare class UserInputError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI9B"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Marker class for errors caused by invalid agent input (wrong device name,
3
+ * missing required predicate, ambiguous match, etc). H2 (Step 9 in the
4
+ * 2026-05-15 plan): the top-level catch in index.ts skips PostHog
5
+ * captureException for these so error tracking surfaces real product bugs,
6
+ * not validation noise (~13% of recent dashboard volume).
7
+ *
8
+ * The trade-off vs regex-matching the message: this is self-documenting at
9
+ * the throw site and survives message reformatting / translation.
10
+ */
11
+ export class UserInputError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "UserInputError";
15
+ }
16
+ }
17
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACrC,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IACjC,CAAC;CACJ"}
@@ -0,0 +1,22 @@
1
+ import { ExecOptions } from "node:child_process";
2
+ export interface ExecAsyncOptions extends ExecOptions {
3
+ signal?: AbortSignal;
4
+ }
5
+ /**
6
+ * Drop-in replacement for `promisify(exec)` that accepts an AbortSignal.
7
+ * On abort, the child gets SIGTERM and then SIGKILL after 500ms — the
8
+ * escalation matters for ADB calls like `uiautomator dump` whose device-side
9
+ * process is independent of the host ADB process and may not exit on SIGTERM.
10
+ */
11
+ export declare function execAsync(cmd: string, opts?: ExecAsyncOptions): Promise<{
12
+ stdout: string;
13
+ stderr: string;
14
+ }>;
15
+ /**
16
+ * Race an async operation against a timeout, with cancellation. The inner
17
+ * factory receives an AbortSignal it can pass into execAsync, fetch, etc.
18
+ * On timeout, the signal aborts so in-flight subprocesses get killed instead
19
+ * of running on past the strategy cap.
20
+ */
21
+ export declare function withCancelableTimeout<T>(make: (signal: AbortSignal) => Promise<T>, ms: number, label: string): Promise<T>;
22
+ //# sourceMappingURL=exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/core/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,gBAAiB,SAAQ,WAAW;IACjD,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACrB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,gBAAqB,GAC5B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAqB7C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACnC,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EACzC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,CAAC,CAWZ"}
@@ -0,0 +1,54 @@
1
+ import { exec } from "node:child_process";
2
+ /**
3
+ * Drop-in replacement for `promisify(exec)` that accepts an AbortSignal.
4
+ * On abort, the child gets SIGTERM and then SIGKILL after 500ms — the
5
+ * escalation matters for ADB calls like `uiautomator dump` whose device-side
6
+ * process is independent of the host ADB process and may not exit on SIGTERM.
7
+ */
8
+ export function execAsync(cmd, opts = {}) {
9
+ const { signal, ...execOpts } = opts;
10
+ return new Promise((resolve, reject) => {
11
+ const child = exec(cmd, execOpts, (err, stdout, stderr) => {
12
+ if (err)
13
+ reject(err);
14
+ else
15
+ resolve({ stdout: stdout.toString(), stderr: stderr.toString() });
16
+ });
17
+ const kill = () => {
18
+ try {
19
+ child.kill("SIGTERM");
20
+ }
21
+ catch { /* already dead */ }
22
+ setTimeout(() => {
23
+ try {
24
+ child.kill("SIGKILL");
25
+ }
26
+ catch { /* already dead */ }
27
+ }, 500).unref();
28
+ };
29
+ if (signal) {
30
+ if (signal.aborted) {
31
+ kill();
32
+ return;
33
+ }
34
+ signal.addEventListener("abort", kill, { once: true });
35
+ }
36
+ });
37
+ }
38
+ /**
39
+ * Race an async operation against a timeout, with cancellation. The inner
40
+ * factory receives an AbortSignal it can pass into execAsync, fetch, etc.
41
+ * On timeout, the signal aborts so in-flight subprocesses get killed instead
42
+ * of running on past the strategy cap.
43
+ */
44
+ export function withCancelableTimeout(make, ms, label) {
45
+ const ctrl = new AbortController();
46
+ const timer = setTimeout(() => ctrl.abort(), ms);
47
+ return make(ctrl.signal).then((val) => { clearTimeout(timer); return val; }, (err) => {
48
+ clearTimeout(timer);
49
+ if (ctrl.signal.aborted)
50
+ throw new Error(`${label} timed out after ${ms}ms`);
51
+ throw err;
52
+ });
53
+ }
54
+ //# sourceMappingURL=exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/core/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAe,MAAM,oBAAoB,CAAC;AAMvD;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACrB,GAAW,EACX,OAAyB,EAAE;IAE3B,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACtD,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC3E,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,EAAE;YACd,IAAI,CAAC;gBAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;YAC3D,UAAU,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC;oBAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;YAC/D,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,CAAC;QACF,IAAI,MAAM,EAAE,CAAC;YACT,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,EAAE,CAAC;gBACP,OAAO;YACX,CAAC;YACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACjC,IAAyC,EACzC,EAAU,EACV,KAAa;IAEb,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CACzB,CAAC,GAAG,EAAE,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,EAC7C,CAAC,GAAG,EAAE,EAAE;QACJ,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,EAAE,IAAI,CAAC,CAAC;QAC7E,MAAM,GAAG,CAAC;IACd,CAAC,CACJ,CAAC;AACN,CAAC"}
@@ -104,6 +104,8 @@ interface PressableElement {
104
104
  }
105
105
  export declare function getPressableElements(options?: {
106
106
  device?: string;
107
+ platform?: "ios" | "android";
108
+ udid?: string;
107
109
  }): Promise<ExecutionResult & {
108
110
  parsedElements?: PressableElement[];
109
111
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import WebSocket from "ws";
2
2
  import { pendingExecutions, getNextMessageId, connectedApps } from "./state.js";
3
- import { getFirstConnectedApp, getConnectedAppByDevice, connectToDevice, clearReconnectionSuppression, purgeStaleConnectionsForPorts } from "./connection.js";
3
+ import { getFirstConnectedApp, getConnectedAppByDevice, getConnectedAppBySimulatorUdid, getConnectedAppByAndroidDeviceId, connectToDevice, clearReconnectionSuppression, purgeStaleConnectionsForPorts } from "./connection.js";
4
4
  import { fetchDevices, selectMainDevice, filterDebuggableDevices, scanMetroPorts } from "./metro.js";
5
5
  import { DEFAULT_RECONNECTION_CONFIG, cancelReconnectionTimer } from "./connectionState.js";
6
6
  // Hermes runtime compatibility: polyfill for 'global' which doesn't exist in Hermes
@@ -1786,8 +1786,118 @@ export async function getScreenLayout(options = {}) {
1786
1786
  }
1787
1787
  return result;
1788
1788
  }
1789
+ /**
1790
+ * E1 / Step 5 (2026-05-15 plan): build a pressables list from the platform
1791
+ * accessibility tree when fiber isn't reachable. Used when metro is offline
1792
+ * (connectedApps.size === 0) so the screenshot tools can still hand the agent
1793
+ * labelled coordinates instead of forcing it to guess pixels — the dominant
1794
+ * cause of unmeaningful coordinate-strategy taps in the 7-day failure data.
1795
+ */
1796
+ async function getAccessibilityPressables(platform, udid) {
1797
+ try {
1798
+ if (platform === "ios") {
1799
+ const { iosGetUITree } = await import("./ios.js");
1800
+ const tree = await iosGetUITree(udid);
1801
+ if (!tree.success || !tree.elements)
1802
+ return [];
1803
+ const interactiveTraits = new Set(["button", "link", "searchfield", "tab", "image"]);
1804
+ return tree.elements
1805
+ .filter(el => {
1806
+ // iOS: rely on a11y traits + element type. Buttons, links,
1807
+ // tabs, and any element with a non-empty label/identifier
1808
+ // that isn't a static text container.
1809
+ const traitsLower = (el.traits || []).map(t => t.toLowerCase());
1810
+ if (traitsLower.some(t => interactiveTraits.has(t)))
1811
+ return true;
1812
+ const type = (el.type || "").toLowerCase();
1813
+ if (type.includes("button") || type.includes("textfield") || type.includes("searchfield"))
1814
+ return true;
1815
+ return false;
1816
+ })
1817
+ .map(el => {
1818
+ const type = (el.type || "").toLowerCase();
1819
+ return {
1820
+ component: el.type || "Element",
1821
+ path: "",
1822
+ center: el.center,
1823
+ frame: el.frame,
1824
+ text: el.label || "",
1825
+ testID: el.identifier || null,
1826
+ accessibilityLabel: el.label || null,
1827
+ hasLabel: !!el.label,
1828
+ isInput: type.includes("textfield") || type.includes("searchfield")
1829
+ };
1830
+ });
1831
+ }
1832
+ else {
1833
+ const { androidGetUITree } = await import("./android.js");
1834
+ const tree = await androidGetUITree();
1835
+ if (!tree.success || !tree.elements)
1836
+ return [];
1837
+ return tree.elements
1838
+ .filter(el => el.clickable || el.focused || (el.className && el.className.includes("EditText")))
1839
+ .map(el => ({
1840
+ component: (el.className || "View").split(".").pop() || "View",
1841
+ path: "",
1842
+ center: el.center,
1843
+ frame: { x: el.bounds.left, y: el.bounds.top, width: el.bounds.width, height: el.bounds.height },
1844
+ text: el.text || "",
1845
+ testID: el.resourceId || null,
1846
+ accessibilityLabel: el.contentDesc || null,
1847
+ hasLabel: !!(el.text || el.contentDesc),
1848
+ isInput: !!(el.className && el.className.includes("EditText"))
1849
+ }));
1850
+ }
1851
+ }
1852
+ catch {
1853
+ return [];
1854
+ }
1855
+ }
1789
1856
  export async function getPressableElements(options = {}) {
1790
- const { device } = options;
1857
+ const { device, platform, udid } = options;
1858
+ // Resolve which connected RN app corresponds to the requested device/udid.
1859
+ // The E1 fallback below must fire when no app matches THIS device, even if
1860
+ // unrelated apps remain connected to Metro. Without this filter the fiber
1861
+ // path would silently fall back to the first-connected app and return data
1862
+ // for the wrong device (Bug 1, 2026-05-16).
1863
+ let targetApp = null;
1864
+ if (udid && platform === "ios") {
1865
+ targetApp = getConnectedAppBySimulatorUdid(udid);
1866
+ }
1867
+ else if (device) {
1868
+ const matched = getConnectedAppByDevice(device);
1869
+ if (matched && (!platform || matched.platform === platform))
1870
+ targetApp = matched;
1871
+ }
1872
+ else if (platform === "android") {
1873
+ targetApp = getConnectedAppByAndroidDeviceId(undefined);
1874
+ }
1875
+ else if (platform === "ios") {
1876
+ const first = getFirstConnectedApp();
1877
+ targetApp = first && first.platform === "ios" ? first : null;
1878
+ }
1879
+ else {
1880
+ targetApp = getFirstConnectedApp();
1881
+ }
1882
+ // E1 fallback: no app resolved for THIS device → use platform accessibility tree.
1883
+ // Only kicks in when caller passed `platform` so we don't break older
1884
+ // metro-required call paths that didn't opt into the fallback.
1885
+ if (!targetApp && platform) {
1886
+ const elements = await getAccessibilityPressables(platform, udid);
1887
+ return {
1888
+ success: true,
1889
+ result: elements.length > 0
1890
+ ? `Pressable elements (from accessibility tree, ${elements.length} found):\n` +
1891
+ elements.map((el, i) => {
1892
+ const label = el.accessibilityLabel || el.text || el.testID || el.component;
1893
+ const idStr = el.testID ? ` testID="${el.testID}"` : "";
1894
+ const inputStr = el.isInput ? " [input]" : "";
1895
+ return `${i + 1}. ${el.component} "${label}"${idStr}${inputStr} — center:(${el.center.x},${el.center.y}) frame:(${el.frame.x},${el.frame.y} ${el.frame.width}x${el.frame.height})`;
1896
+ }).join("\n")
1897
+ : "No interactive elements found via accessibility.",
1898
+ parsedElements: elements
1899
+ };
1900
+ }
1791
1901
  // --- Step 1: walk fiber tree, find pressable/input elements, dispatch measureInWindow ---
1792
1902
  const dispatchExpression = `
1793
1903
  (function() {
@@ -2376,6 +2486,126 @@ export async function getPressableElements(options = {}) {
2376
2486
  if (parsed.error)
2377
2487
  return { success: false, error: parsed.error };
2378
2488
  const pressableElements = parsed.pressableElements || [];
2489
+ // Android coordinate reconciliation (2026-05-17).
2490
+ //
2491
+ // The fiber walker produces rich metadata (component names, testID, intent,
2492
+ // wrapper flags, nearbyText) but its measureInWindow output on Bridgeless/Fabric
2493
+ // is in app-window DP space — off from the actual screen-pixel hit area by the
2494
+ // status bar height (varies per device) AND an additional unaccounted offset
2495
+ // we couldn't characterize across devices.
2496
+ //
2497
+ // uiautomator's bounds, on the other hand, are in absolute display pixels and
2498
+ // match what tap(x, y) → adb input dispatches — they are touch-accurate.
2499
+ //
2500
+ // Strategy: keep fiber's metadata, but replace coords with uiautomator's bounds
2501
+ // when we can match a fiber element to a uiautomator node by text / contentDesc /
2502
+ // resourceId. For icon-only pressables that have no text-match candidate,
2503
+ // fall back to fiber's DP coords scaled by density — they're imperfect but
2504
+ // they keep the same gross layout that an agent could use as a "near this
2505
+ // region" hint, and they're better than nothing.
2506
+ //
2507
+ // iOS is unaffected — fiber returns points and AXe returns points; uniform.
2508
+ if (platform === "android" && pressableElements.length > 0) {
2509
+ let densityScale = 2.625;
2510
+ let statusBarPixels = 0;
2511
+ try {
2512
+ const { androidGetDensity, androidGetStatusBarHeight } = await import("./android.js");
2513
+ const [densityResult, statusBarResult] = await Promise.all([
2514
+ androidGetDensity(),
2515
+ androidGetStatusBarHeight().catch(() => ({ success: false, heightPixels: 0 })),
2516
+ ]);
2517
+ densityScale = (densityResult.density || 420) / 160;
2518
+ if (statusBarResult.success && statusBarResult.heightPixels) {
2519
+ statusBarPixels = statusBarResult.heightPixels;
2520
+ }
2521
+ }
2522
+ catch { /* default */ }
2523
+ let uiNodes;
2524
+ try {
2525
+ const { androidGetUITree } = await import("./android.js");
2526
+ const tree = await androidGetUITree();
2527
+ if (tree.success && tree.elements)
2528
+ uiNodes = tree.elements;
2529
+ }
2530
+ catch { /* uiautomator unavailable */ }
2531
+ const norm = (s) => (s ?? "").toLowerCase().replace(/\s+/g, " ").trim();
2532
+ const findUiMatch = (el) => {
2533
+ if (!uiNodes || uiNodes.length === 0)
2534
+ return null;
2535
+ const targetTestID = norm(el.testID);
2536
+ const targetText = norm(el.text);
2537
+ const targetA11y = norm(el.accessibilityLabel);
2538
+ if (!targetTestID && !targetText && !targetA11y)
2539
+ return null;
2540
+ // Prefer testID → resourceId exact match (most stable).
2541
+ if (targetTestID) {
2542
+ const m = uiNodes.find(u => norm(u.resourceId).endsWith("/" + targetTestID) || norm(u.resourceId) === targetTestID);
2543
+ if (m)
2544
+ return m;
2545
+ }
2546
+ // Then exact text/contentDesc match.
2547
+ for (const query of [targetText, targetA11y]) {
2548
+ if (!query)
2549
+ continue;
2550
+ const m = uiNodes.find(u => norm(u.text) === query || norm(u.contentDesc) === query);
2551
+ if (m)
2552
+ return m;
2553
+ }
2554
+ // Then substring — handles fiber's collected text being a superset of one node's label.
2555
+ for (const query of [targetText, targetA11y]) {
2556
+ if (!query)
2557
+ continue;
2558
+ const m = uiNodes.find(u => {
2559
+ const ut = norm(u.text);
2560
+ const ud = norm(u.contentDesc);
2561
+ return (ut && (ut.includes(query) || query.includes(ut))) ||
2562
+ (ud && (ud.includes(query) || query.includes(ud)));
2563
+ });
2564
+ if (m)
2565
+ return m;
2566
+ }
2567
+ return null;
2568
+ };
2569
+ for (const el of pressableElements) {
2570
+ const match = findUiMatch(el);
2571
+ if (match) {
2572
+ el.center = { x: match.center.x, y: match.center.y };
2573
+ el.frame = {
2574
+ x: match.bounds.left,
2575
+ y: match.bounds.top,
2576
+ width: match.bounds.width,
2577
+ height: match.bounds.height,
2578
+ };
2579
+ }
2580
+ else {
2581
+ // Fall back to fiber's DP → device-pixel scaling, plus the dynamic
2582
+ // status-bar pixel offset so the y origin lines up with display
2583
+ // coordinates (uiautomator's space). measureInWindow on Bridgeless
2584
+ // returns coordinates relative to the React window which sits
2585
+ // below the system status bar on non-edge-to-edge apps; without
2586
+ // this shift, fallback elements report y-values that are
2587
+ // ~status-bar-height too small.
2588
+ //
2589
+ // Note: this is a best-effort correction. Edge-to-edge apps where
2590
+ // the React window starts at the display origin will see a
2591
+ // small over-correction here. The proper fix would query
2592
+ // WindowInsets, but the data isn't exposed via adb in a way
2593
+ // we can reach today, and the icon-only-no-text fallback is
2594
+ // already a "region hint" path — agents should prefer
2595
+ // tap(component=...) / tap(testID=...) for precision.
2596
+ el.center = {
2597
+ x: el.center.x * densityScale,
2598
+ y: el.center.y * densityScale + statusBarPixels,
2599
+ };
2600
+ el.frame = {
2601
+ x: el.frame.x * densityScale,
2602
+ y: el.frame.y * densityScale + statusBarPixels,
2603
+ width: el.frame.width * densityScale,
2604
+ height: el.frame.height * densityScale,
2605
+ };
2606
+ }
2607
+ }
2608
+ }
2379
2609
  // Format as readable text
2380
2610
  const lines = [parsed.summary, ""];
2381
2611
  for (let i = 0; i < pressableElements.length; i++) {