react-native-ai-debugger 1.8.1 → 1.9.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 (126) hide show
  1. package/README.md +73 -66
  2. package/build/__tests__/integration/auto-reconnect.test.d.ts +2 -0
  3. package/build/__tests__/integration/auto-reconnect.test.d.ts.map +1 -0
  4. package/build/__tests__/integration/auto-reconnect.test.js +118 -0
  5. package/build/__tests__/integration/auto-reconnect.test.js.map +1 -0
  6. package/build/__tests__/integration/per-call-timeout.test.d.ts +2 -0
  7. package/build/__tests__/integration/per-call-timeout.test.d.ts.map +1 -0
  8. package/build/__tests__/integration/per-call-timeout.test.js +103 -0
  9. package/build/__tests__/integration/per-call-timeout.test.js.map +1 -0
  10. package/build/__tests__/setup.js +3 -5
  11. package/build/__tests__/setup.js.map +1 -1
  12. package/build/__tests__/unit/debugGlobals.test.d.ts +2 -0
  13. package/build/__tests__/unit/debugGlobals.test.d.ts.map +1 -0
  14. package/build/__tests__/unit/debugGlobals.test.js +14 -0
  15. package/build/__tests__/unit/debugGlobals.test.js.map +1 -0
  16. package/build/__tests__/unit/escapeNonAscii.test.d.ts +2 -0
  17. package/build/__tests__/unit/escapeNonAscii.test.d.ts.map +1 -0
  18. package/build/__tests__/unit/escapeNonAscii.test.js +76 -0
  19. package/build/__tests__/unit/escapeNonAscii.test.js.map +1 -0
  20. package/build/__tests__/unit/executor.test.js +59 -7
  21. package/build/__tests__/unit/executor.test.js.map +1 -1
  22. package/build/__tests__/unit/fastRefreshRecorder.test.d.ts +2 -0
  23. package/build/__tests__/unit/fastRefreshRecorder.test.d.ts.map +1 -0
  24. package/build/__tests__/unit/fastRefreshRecorder.test.js +68 -0
  25. package/build/__tests__/unit/fastRefreshRecorder.test.js.map +1 -0
  26. package/build/__tests__/unit/fastRefreshTools.test.d.ts +2 -0
  27. package/build/__tests__/unit/fastRefreshTools.test.d.ts.map +1 -0
  28. package/build/__tests__/unit/fastRefreshTools.test.js +112 -0
  29. package/build/__tests__/unit/fastRefreshTools.test.js.map +1 -0
  30. package/build/__tests__/unit/fingerprint.test.js +10 -0
  31. package/build/__tests__/unit/fingerprint.test.js.map +1 -1
  32. package/build/__tests__/unit/measureComponent.test.d.ts +2 -0
  33. package/build/__tests__/unit/measureComponent.test.d.ts.map +1 -0
  34. package/build/__tests__/unit/measureComponent.test.js +66 -0
  35. package/build/__tests__/unit/measureComponent.test.js.map +1 -0
  36. package/build/__tests__/unit/measureComponentTools.test.d.ts +2 -0
  37. package/build/__tests__/unit/measureComponentTools.test.d.ts.map +1 -0
  38. package/build/__tests__/unit/measureComponentTools.test.js +94 -0
  39. package/build/__tests__/unit/measureComponentTools.test.js.map +1 -0
  40. package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts +2 -0
  41. package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts.map +1 -0
  42. package/build/__tests__/unit/rnGlobalsBootstrap.test.js +29 -0
  43. package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -0
  44. package/build/__tests__/unit/timeoutClamp.test.d.ts +2 -0
  45. package/build/__tests__/unit/timeoutClamp.test.d.ts.map +1 -0
  46. package/build/__tests__/unit/timeoutClamp.test.js +17 -0
  47. package/build/__tests__/unit/timeoutClamp.test.js.map +1 -0
  48. package/build/__tests__/unit/transportClassify.test.d.ts +2 -0
  49. package/build/__tests__/unit/transportClassify.test.d.ts.map +1 -0
  50. package/build/__tests__/unit/transportClassify.test.js +60 -0
  51. package/build/__tests__/unit/transportClassify.test.js.map +1 -0
  52. package/build/core/bundle.d.ts +1 -1
  53. package/build/core/bundle.d.ts.map +1 -1
  54. package/build/core/bundle.js +6 -10
  55. package/build/core/bundle.js.map +1 -1
  56. package/build/core/componentSearch.d.ts +3 -0
  57. package/build/core/componentSearch.d.ts.map +1 -1
  58. package/build/core/componentSearch.js +73 -8
  59. package/build/core/componentSearch.js.map +1 -1
  60. package/build/core/componentTree.d.ts +1 -0
  61. package/build/core/componentTree.d.ts.map +1 -1
  62. package/build/core/componentTree.js +2 -2
  63. package/build/core/componentTree.js.map +1 -1
  64. package/build/core/debugGlobals.d.ts +1 -0
  65. package/build/core/debugGlobals.d.ts.map +1 -1
  66. package/build/core/debugGlobals.js +45 -9
  67. package/build/core/debugGlobals.js.map +1 -1
  68. package/build/core/escapeNonAscii.d.ts +18 -0
  69. package/build/core/escapeNonAscii.d.ts.map +1 -0
  70. package/build/core/escapeNonAscii.js +352 -0
  71. package/build/core/escapeNonAscii.js.map +1 -0
  72. package/build/core/fastRefreshRecorder.d.ts +23 -0
  73. package/build/core/fastRefreshRecorder.d.ts.map +1 -0
  74. package/build/core/fastRefreshRecorder.js +83 -0
  75. package/build/core/fastRefreshRecorder.js.map +1 -0
  76. package/build/core/fastRefreshTools.d.ts +19 -0
  77. package/build/core/fastRefreshTools.d.ts.map +1 -0
  78. package/build/core/fastRefreshTools.js +71 -0
  79. package/build/core/fastRefreshTools.js.map +1 -0
  80. package/build/core/guides.d.ts.map +1 -1
  81. package/build/core/guides.js +2 -1
  82. package/build/core/guides.js.map +1 -1
  83. package/build/core/index.d.ts +2 -0
  84. package/build/core/index.d.ts.map +1 -1
  85. package/build/core/index.js +2 -0
  86. package/build/core/index.js.map +1 -1
  87. package/build/core/jsExecute.d.ts +34 -0
  88. package/build/core/jsExecute.d.ts.map +1 -1
  89. package/build/core/jsExecute.js +249 -23
  90. package/build/core/jsExecute.js.map +1 -1
  91. package/build/core/measureComponent.d.ts +19 -0
  92. package/build/core/measureComponent.d.ts.map +1 -0
  93. package/build/core/measureComponent.js +94 -0
  94. package/build/core/measureComponent.js.map +1 -0
  95. package/build/core/measureComponentTools.d.ts +6 -0
  96. package/build/core/measureComponentTools.d.ts.map +1 -0
  97. package/build/core/measureComponentTools.js +43 -0
  98. package/build/core/measureComponentTools.js.map +1 -0
  99. package/build/core/rnGlobalsBootstrap.d.ts +16 -0
  100. package/build/core/rnGlobalsBootstrap.d.ts.map +1 -0
  101. package/build/core/rnGlobalsBootstrap.js +101 -0
  102. package/build/core/rnGlobalsBootstrap.js.map +1 -0
  103. package/build/core/screenLayout.d.ts +1 -0
  104. package/build/core/screenLayout.d.ts.map +1 -1
  105. package/build/core/screenLayout.js +4 -4
  106. package/build/core/screenLayout.js.map +1 -1
  107. package/build/core/state.d.ts +2 -2
  108. package/build/core/state.d.ts.map +1 -1
  109. package/build/core/state.js +10 -5
  110. package/build/core/state.js.map +1 -1
  111. package/build/core/telemetry.d.ts +23 -0
  112. package/build/core/telemetry.d.ts.map +1 -1
  113. package/build/core/telemetry.js +67 -0
  114. package/build/core/telemetry.js.map +1 -1
  115. package/build/core/types.d.ts +7 -0
  116. package/build/core/types.d.ts.map +1 -1
  117. package/build/tools/componentTools.d.ts.map +1 -1
  118. package/build/tools/componentTools.js +134 -69
  119. package/build/tools/componentTools.js.map +1 -1
  120. package/build/tools/executionTools.d.ts.map +1 -1
  121. package/build/tools/executionTools.js +80 -8
  122. package/build/tools/executionTools.js.map +1 -1
  123. package/build/tools/interactionTools.d.ts.map +1 -1
  124. package/build/tools/interactionTools.js +146 -2
  125. package/build/tools/interactionTools.js.map +1 -1
  126. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,12 @@ ExecBro is the runtime bridge that gives your AI coding assistant live access to
22
22
 
23
23
  Have an idea or found something that could be better? Head over to [GitHub Discussions](https://github.com/igorzheludkov/react-native-ai-devtools/discussions) to share feedback, request features, and vote on what gets built next.
24
24
 
25
+ ## Pricing
26
+
27
+ ExecBro is and will remain free for typical workflows. A paid tier launches in summer 2026 with a single $9/month plan covering unlimited tool calls; the free tier will be capped at 300 tool calls / 30 days at that point. Everyone already using ExecBro before the announcement gets 3 months free of the paid tier — auto-applied via your installation ID, no action required.
28
+
29
+ Full details and exact dates will be published at [execbro.com/pricing](https://execbro.com/pricing) closer to launch. Until then, nothing changes for current users.
30
+
25
31
  ## Features
26
32
 
27
33
  ### Runtime Interaction
@@ -37,6 +43,7 @@ Have an idea or found something that could be better? Head over to [GitHub Discu
37
43
  - **iOS Simulator** - Screenshots, app management, URL handling, boot/terminate (via simctl)
38
44
  - **Android Devices** - Screenshots, app install/launch, package management (via ADB)
39
45
  - **Unified Tap** - Single `tap` tool with automatic fallback chain: fiber tree → accessibility → OCR → coordinates. Auto-detects platform, accepts pixels from screenshots. Returns post-tap screenshot and verifies visual change by default
46
+ - **Unified Swipe** - Single `swipe` tool that auto-routes to iOS or Android based on the connected device. Accepts screenshot pixel coordinates and handles per-platform conversion. Essential for scrolling virtualized lists (FlatList/SectionList) where off-screen items aren't in the fiber tree. Platform-specific `ios_swipe` / `android_swipe` remain available as fallbacks
40
47
  - **UI Automation** - Swipe, long press, key events, and text input on both platforms. On Bridgeless/Fabric apps, `clear_focused_input` and `dismiss_keyboard` operate on whatever has focus, and `ios_input_text` / `android_input_text` accept `replace:true` to overwrite pre-filled values — all three update React state through `onChangeText` so controlled components (Formik, react-hook-form, useState) stay consistent
41
48
  - **Accessibility Inspection** - Query UI hierarchy to find elements by text, label, or resource ID
42
49
  - **OCR Text Extraction** - Extract visible text with tap-ready coordinates via Google Cloud Vision (works on any screen content)
@@ -58,7 +65,7 @@ Have an idea or found something that could be better? Head over to [GitHub Discu
58
65
 
59
66
  ### Related
60
67
 
61
- - **[execbro-runner](https://github.com/igorzheludkov/execbro-runner)** *(early alpha, in active development)* — queue-based autonomous task runner built on top of ExecBro. Drop in a markdown prompt and a background daemon spins up an isolated sandbox (git worktree + simulator/emulator + Metro), runs Claude Code headlessly, has the agent verify the change live on the device via ExecBro, and optionally pushes a branch with a PR URL. Fire-and-forget Claude Code runs for your React Native app.
68
+ - **[execbro-runner](https://github.com/igorzheludkov/execbro-runner)** _(early alpha, in active development)_ — queue-based autonomous task runner built on top of ExecBro. Drop in a markdown prompt and a background daemon spins up an isolated sandbox (git worktree + simulator/emulator + Metro), runs Claude Code headlessly, has the agent verify the change live on the device via ExecBro, and optionally pushes a branch with a PR URL. Fire-and-forget Claude Code runs for your React Native app.
62
69
 
63
70
  ## Setup
64
71
 
@@ -82,13 +89,13 @@ Or edit `~/.claude.json` (user) / `.mcp.json` (project) manually:
82
89
 
83
90
  ```json
84
91
  {
85
- "mcpServers": {
86
- "execbro": {
87
- "type": "stdio",
88
- "command": "npx",
89
- "args": ["-y", "react-native-ai-devtools@latest"]
92
+ "mcpServers": {
93
+ "execbro": {
94
+ "type": "stdio",
95
+ "command": "npx",
96
+ "args": ["-y", "react-native-ai-devtools@latest"]
97
+ }
90
98
  }
91
- }
92
99
  }
93
100
  ```
94
101
 
@@ -101,12 +108,12 @@ Edit the config at:
101
108
 
102
109
  ```json
103
110
  {
104
- "mcpServers": {
105
- "execbro": {
106
- "command": "npx",
107
- "args": ["-y", "react-native-ai-devtools@latest"]
111
+ "mcpServers": {
112
+ "execbro": {
113
+ "command": "npx",
114
+ "args": ["-y", "react-native-ai-devtools@latest"]
115
+ }
108
116
  }
109
- }
110
117
  }
111
118
  ```
112
119
 
@@ -132,12 +139,12 @@ args = ["-y", "react-native-ai-devtools@latest"]
132
139
 
133
140
  ```json
134
141
  {
135
- "mcpServers": {
136
- "execbro": {
137
- "command": "npx",
138
- "args": ["-y", "react-native-ai-devtools@latest"]
142
+ "mcpServers": {
143
+ "execbro": {
144
+ "command": "npx",
145
+ "args": ["-y", "react-native-ai-devtools@latest"]
146
+ }
139
147
  }
140
- }
141
148
  }
142
149
  ```
143
150
 
@@ -147,13 +154,13 @@ Requires VS Code 1.102+ with Copilot ([docs](https://code.visualstudio.com/docs/
147
154
 
148
155
  ```json
149
156
  {
150
- "servers": {
151
- "execbro": {
152
- "type": "stdio",
153
- "command": "npx",
154
- "args": ["-y", "react-native-ai-devtools@latest"]
157
+ "servers": {
158
+ "execbro": {
159
+ "type": "stdio",
160
+ "command": "npx",
161
+ "args": ["-y", "react-native-ai-devtools@latest"]
162
+ }
155
163
  }
156
- }
157
164
  }
158
165
  ```
159
166
 
@@ -163,12 +170,12 @@ Requires VS Code 1.102+ with Copilot ([docs](https://code.visualstudio.com/docs/
163
170
 
164
171
  ```json
165
172
  {
166
- "mcpServers": {
167
- "execbro": {
168
- "command": "npx",
169
- "args": ["-y", "react-native-ai-devtools@latest"]
173
+ "mcpServers": {
174
+ "execbro": {
175
+ "command": "npx",
176
+ "args": ["-y", "react-native-ai-devtools@latest"]
177
+ }
170
178
  }
171
- }
172
179
  }
173
180
  ```
174
181
 
@@ -178,13 +185,13 @@ Requires VS Code 1.102+ with Copilot ([docs](https://code.visualstudio.com/docs/
178
185
 
179
186
  ```json
180
187
  {
181
- "context_servers": {
182
- "execbro": {
183
- "command": "npx",
184
- "args": ["-y", "react-native-ai-devtools@latest"],
185
- "env": {}
188
+ "context_servers": {
189
+ "execbro": {
190
+ "command": "npx",
191
+ "args": ["-y", "react-native-ai-devtools@latest"],
192
+ "env": {}
193
+ }
186
194
  }
187
- }
188
195
  }
189
196
  ```
190
197
 
@@ -194,12 +201,12 @@ Edit `~/.gemini/settings.json` (user) or `.gemini/settings.json` (project):
194
201
 
195
202
  ```json
196
203
  {
197
- "mcpServers": {
198
- "execbro": {
199
- "command": "npx",
200
- "args": ["-y", "react-native-ai-devtools@latest"]
204
+ "mcpServers": {
205
+ "execbro": {
206
+ "command": "npx",
207
+ "args": ["-y", "react-native-ai-devtools@latest"]
208
+ }
201
209
  }
202
- }
203
210
  }
204
211
  ```
205
212
 
@@ -241,14 +248,14 @@ Opt in by setting `IOS_DRIVER=idb` in your MCP server configuration:
241
248
 
242
249
  ```json
243
250
  {
244
- "mcpServers": {
245
- "execbro": {
246
- "type": "stdio",
247
- "command": "npx",
248
- "args": ["-y", "react-native-ai-devtools@latest"],
249
- "env": { "IOS_DRIVER": "idb" }
251
+ "mcpServers": {
252
+ "execbro": {
253
+ "type": "stdio",
254
+ "command": "npx",
255
+ "args": ["-y", "react-native-ai-devtools@latest"],
256
+ "env": { "IOS_DRIVER": "idb" }
257
+ }
250
258
  }
251
- }
252
259
  }
253
260
  ```
254
261
 
@@ -300,22 +307,22 @@ See the [full tool reference](docs/tools.md) for all tools with descriptions. Ke
300
307
 
301
308
  1. Start your React Native app:
302
309
 
303
- ```bash
304
- npm start
305
- # or
306
- expo start
307
- ```
310
+ ```bash
311
+ npm start
312
+ # or
313
+ expo start
314
+ ```
308
315
 
309
316
  2. In Claude Code, scan for Metro:
310
317
 
311
- ```
312
- Use scan_metro to find and connect to Metro
313
- ```
318
+ ```
319
+ Use scan_metro to find and connect to Metro
320
+ ```
314
321
 
315
322
  3. Get logs:
316
- ```
317
- Use get_logs to see recent console output
318
- ```
323
+ ```
324
+ Use get_logs to see recent console output
325
+ ```
319
326
 
320
327
  ## Detailed Guides
321
328
 
@@ -346,8 +353,8 @@ See the [full tool reference](docs/tools.md) for all tools with descriptions. Ke
346
353
  2. Connects to the main JS runtime via CDP (Chrome DevTools Protocol) WebSocket
347
354
  3. Enables `Runtime.enable` to receive `Runtime.consoleAPICalled` events
348
355
  4. Network capture via two paths:
349
- - **With SDK**: Reads from the SDK's in-app buffer via `Runtime.evaluate` — captures all requests from startup with full headers and bodies, including cold-start events that CDP would miss
350
- - **Without SDK**: Enables CDP `Network.enable` (on supported targets) or injects a JS fetch interceptor as fallback. On cold start, events emitted before the CDP connection is established are lost; subsequent reloads capture everything
356
+ - **With SDK**: Reads from the SDK's in-app buffer via `Runtime.evaluate` — captures all requests from startup with full headers and bodies, including cold-start events that CDP would miss
357
+ - **Without SDK**: Enables CDP `Network.enable` (on supported targets) or injects a JS fetch interceptor as fallback. On cold start, events emitted before the CDP connection is established are lost; subsequent reloads capture everything
351
358
  5. Stores logs and network requests in circular buffers for retrieval
352
359
 
353
360
  ## Connection Management
@@ -453,14 +460,14 @@ To disable telemetry and auto-registration, add `RN_DEBUGGER_TELEMETRY` to the `
453
460
 
454
461
  ```json
455
462
  {
456
- "mcpServers": {
457
- "execbro": {
458
- "type": "stdio",
459
- "command": "npx",
460
- "args": ["-y", "react-native-ai-devtools@latest"],
461
- "env": { "RN_DEBUGGER_TELEMETRY": "false" }
463
+ "mcpServers": {
464
+ "execbro": {
465
+ "type": "stdio",
466
+ "command": "npx",
467
+ "args": ["-y", "react-native-ai-devtools@latest"],
468
+ "env": { "RN_DEBUGGER_TELEMETRY": "false" }
469
+ }
462
470
  }
463
- }
464
471
  }
465
472
  ```
466
473
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auto-reconnect.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-reconnect.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration/auto-reconnect.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,118 @@
1
+ import { jest, describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from "@jest/globals";
2
+ import { connectToDevice } from "../../core/connection.js";
3
+ import { executeInApp } from "../../core/jsExecute.js";
4
+ import { connectedApps, pendingExecutions } from "../../core/state.js";
5
+ import { FakeCDPServer } from "../helpers/fake-cdp-server.js";
6
+ describe("auto-reconnect via CDP wrapper", () => {
7
+ let server;
8
+ let device;
9
+ beforeAll(() => {
10
+ jest.spyOn(console, "error").mockImplementation(() => { });
11
+ });
12
+ afterAll(() => {
13
+ jest.restoreAllMocks();
14
+ });
15
+ beforeEach(async () => {
16
+ for (const [key, app] of connectedApps.entries()) {
17
+ try {
18
+ app.ws.close();
19
+ }
20
+ catch { /* ignore */ }
21
+ connectedApps.delete(key);
22
+ }
23
+ pendingExecutions.clear();
24
+ server = new FakeCDPServer();
25
+ const port = await server.start();
26
+ device = {
27
+ id: "test-device",
28
+ title: "Hermes React Native",
29
+ description: "Test Device",
30
+ appId: "com.test.app",
31
+ type: "node",
32
+ webSocketDebuggerUrl: `${server.wsUrl}/inspector/device?page=1`,
33
+ deviceName: "Test",
34
+ };
35
+ await connectToDevice(device, port, {
36
+ reconnectionConfig: { enabled: false, maxAttempts: 0, initialDelayMs: 0, maxDelayMs: 0, backoffMultiplier: 1 },
37
+ });
38
+ });
39
+ afterEach(async () => {
40
+ const closePromises = [];
41
+ for (const [key, app] of connectedApps.entries()) {
42
+ closePromises.push(new Promise((resolve) => {
43
+ if (app.ws.readyState === app.ws.CLOSED) {
44
+ resolve();
45
+ }
46
+ else {
47
+ app.ws.on("close", () => resolve());
48
+ try {
49
+ app.ws.close();
50
+ }
51
+ catch {
52
+ resolve();
53
+ }
54
+ }
55
+ }));
56
+ connectedApps.delete(key);
57
+ }
58
+ await Promise.all(closePromises);
59
+ pendingExecutions.clear();
60
+ await server.stop();
61
+ });
62
+ it("does NOT auto-retry on logical failure (ReferenceError)", async () => {
63
+ server.respondWithError("ReferenceError", "__FOO__ is not defined");
64
+ const result = await executeInApp("__FOO__", false, {
65
+ maxRetries: 0,
66
+ originatingToolName: "execute_in_app",
67
+ });
68
+ expect(result.success).toBe(false);
69
+ // Logical CDP exception — outer wrapper must NOT mark _meta.reconnected.
70
+ expect(result._meta?.reconnected).toBeFalsy();
71
+ expect(result.error).not.toMatch(/^reconnect/);
72
+ expect(result.error).toMatch(/ReferenceError/);
73
+ });
74
+ it("does NOT auto-retry on server-side timeoutMs (logical, not transport)", async () => {
75
+ server.respondWithTimeout(); // never replies
76
+ const result = await executeInApp("blockForever()", false, {
77
+ maxRetries: 0,
78
+ timeoutMs: 300,
79
+ originatingToolName: "execute_in_app",
80
+ });
81
+ expect(result.success).toBe(false);
82
+ expect(result.error).toMatch(/Timeout: Expression took too long/);
83
+ // Critical: a server-side timer hit is logical — no reconnect.
84
+ expect(result._meta?.reconnected).toBeFalsy();
85
+ expect(result.error).not.toMatch(/^reconnect/);
86
+ });
87
+ it("attempts reconnect on transport failure and surfaces _meta on the result", async () => {
88
+ // Force a transport-classified failure by closing the WebSocket before the call.
89
+ // executeInAppInner will return "WebSocket connection is not open." → classified as ws_closed.
90
+ const app = [...connectedApps.values()][0];
91
+ try {
92
+ app.ws.close();
93
+ }
94
+ catch { /* ignore */ }
95
+ await new Promise((r) => setTimeout(r, 50));
96
+ const result = await executeInApp("doSomething()", false, {
97
+ maxRetries: 0,
98
+ autoReconnect: false, // disable inner reconnect so outer wrapper's path is exercised
99
+ originatingToolName: "execute_in_app",
100
+ });
101
+ expect(result.success).toBe(false);
102
+ // The outer wrapper triggered reconnect path — Metro discovery from the test process
103
+ // probes real ports (8081, 8082, 19000-19002). Either outcome is acceptable:
104
+ // - scan_failed → error starts "reconnect_attempted:"
105
+ // - retry_failed → error starts "reconnected_but_still_failed:"
106
+ // - success → result.success === true, _meta.reconnected === true
107
+ // What MUST hold: _meta exists and transportError captures the original message.
108
+ if (result.success) {
109
+ expect(result._meta?.reconnected).toBe(true);
110
+ }
111
+ else {
112
+ expect(result.error).toMatch(/^(reconnect_attempted:|reconnected_but_still_failed:)/);
113
+ expect(result._meta).toBeDefined();
114
+ expect(result._meta?.transportError).toMatch(/WebSocket connection is not open|No apps connected/);
115
+ }
116
+ });
117
+ });
118
+ //# sourceMappingURL=auto-reconnect.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-reconnect.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/auto-reconnect.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=per-call-timeout.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"per-call-timeout.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration/per-call-timeout.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import { jest, describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from "@jest/globals";
2
+ import { connectToDevice } from "../../core/connection.js";
3
+ import { executeInApp } from "../../core/jsExecute.js";
4
+ import { connectedApps, pendingExecutions } from "../../core/state.js";
5
+ import { FakeCDPServer } from "../helpers/fake-cdp-server.js";
6
+ describe("per-call timeoutMs", () => {
7
+ let server;
8
+ let device;
9
+ beforeAll(() => {
10
+ jest.spyOn(console, "error").mockImplementation(() => { });
11
+ });
12
+ afterAll(() => {
13
+ jest.restoreAllMocks();
14
+ });
15
+ beforeEach(async () => {
16
+ for (const [key, app] of connectedApps.entries()) {
17
+ try {
18
+ app.ws.close();
19
+ }
20
+ catch { /* ignore */ }
21
+ connectedApps.delete(key);
22
+ }
23
+ pendingExecutions.clear();
24
+ server = new FakeCDPServer();
25
+ const port = await server.start();
26
+ device = {
27
+ id: "test-device",
28
+ title: "Hermes React Native",
29
+ description: "Test Device",
30
+ appId: "com.test.app",
31
+ type: "node",
32
+ webSocketDebuggerUrl: `${server.wsUrl}/inspector/device?page=1`,
33
+ deviceName: "Test",
34
+ };
35
+ await connectToDevice(device, port, {
36
+ reconnectionConfig: { enabled: false, maxAttempts: 0, initialDelayMs: 0, maxDelayMs: 0, backoffMultiplier: 1 },
37
+ });
38
+ });
39
+ afterEach(async () => {
40
+ const closePromises = [];
41
+ for (const [key, app] of connectedApps.entries()) {
42
+ closePromises.push(new Promise((resolve) => {
43
+ if (app.ws.readyState === app.ws.CLOSED) {
44
+ resolve();
45
+ }
46
+ else {
47
+ app.ws.on("close", () => resolve());
48
+ try {
49
+ app.ws.close();
50
+ }
51
+ catch {
52
+ resolve();
53
+ }
54
+ }
55
+ }));
56
+ connectedApps.delete(key);
57
+ }
58
+ await Promise.all(closePromises);
59
+ pendingExecutions.clear();
60
+ await server.stop();
61
+ });
62
+ it("forwards timeoutMs to Runtime.evaluate as the 'timeout' field", async () => {
63
+ server.respondWithValue(1, "number");
64
+ await executeInApp("doSomething()", false, { timeoutMs: 7777, originatingToolName: "execute_in_app" });
65
+ // Find the non-probe Runtime.evaluate call. The probe sends "1+1"; we sent "doSomething()" which
66
+ // becomes wrapped with the GLOBAL_POLYFILL.
67
+ const evalMsg = server.receivedMessages.find((m) => m.method === "Runtime.evaluate" && typeof m.params.expression === "string"
68
+ && m.params.expression.includes("doSomething()"));
69
+ expect(evalMsg).toBeDefined();
70
+ expect(evalMsg.params.timeout).toBe(7777);
71
+ });
72
+ it("Promise.race fires the server-side timer at the configured timeout", async () => {
73
+ server.respondWithTimeout();
74
+ const start = Date.now();
75
+ const result = await executeInApp("blockForever()", false, {
76
+ maxRetries: 0,
77
+ timeoutMs: 500,
78
+ originatingToolName: "execute_in_app",
79
+ });
80
+ const elapsed = Date.now() - start;
81
+ expect(result.success).toBe(false);
82
+ expect(result.error).toMatch(/Timeout: Expression took too long/);
83
+ expect(elapsed).toBeGreaterThanOrEqual(450);
84
+ expect(elapsed).toBeLessThan(2500);
85
+ // Critical: a timeoutMs hit is logical, NOT transport — no reconnect marker.
86
+ expect(result._meta?.reconnected).toBeFalsy();
87
+ });
88
+ it("clamps timeoutMs > 120000 and reports the original in _meta", async () => {
89
+ server.respondWithValue(42, "number");
90
+ const result = await executeInApp("getAnswer()", false, {
91
+ timeoutMs: 3_000_000,
92
+ originatingToolName: "execute_in_app",
93
+ });
94
+ expect(result.success).toBe(true);
95
+ expect(result._meta?.timeoutClampedFrom).toBe(3_000_000);
96
+ // And the forwarded Hermes-side timeout must be the clamped value, not the raw input.
97
+ const evalMsg = server.receivedMessages.find((m) => m.method === "Runtime.evaluate" && typeof m.params.expression === "string"
98
+ && m.params.expression.includes("getAnswer()"));
99
+ expect(evalMsg).toBeDefined();
100
+ expect(evalMsg.params.timeout).toBe(120000);
101
+ });
102
+ });
103
+ //# sourceMappingURL=per-call-timeout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"per-call-timeout.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/per-call-timeout.test.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,7 @@
1
1
  import { afterEach } from "@jest/globals";
2
- import { cancelAllReconnectionTimers, clearAllConnectionState } from "../core/connectionState.js";
3
- import { pendingExecutions } from "../core/state.js";
4
- // Ensure no reconnection timers or connection state leaks between tests,
5
- // even when a real Metro server is running during test execution.
6
- afterEach(() => {
2
+ afterEach(async () => {
3
+ const { cancelAllReconnectionTimers, clearAllConnectionState } = await import("../core/connectionState.js");
4
+ const { pendingExecutions } = await import("../core/state.js");
7
5
  cancelAllReconnectionTimers();
8
6
  clearAllConnectionState();
9
7
  pendingExecutions.clear();
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,yEAAyE;AACzE,kEAAkE;AAClE,SAAS,CAAC,GAAG,EAAE;IACX,2BAA2B,EAAE,CAAC;IAC9B,uBAAuB,EAAE,CAAC;IAC1B,iBAAiB,CAAC,KAAK,EAAE,CAAC;AAC9B,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,SAAS,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,GAAG,MAAM,MAAM,CACzE,4BAA4B,CAC/B,CAAC;IACF,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAC/D,2BAA2B,EAAE,CAAC;IAC9B,uBAAuB,EAAE,CAAC;IAC1B,iBAAiB,CAAC,KAAK,EAAE,CAAC;AAC9B,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=debugGlobals.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debugGlobals.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/debugGlobals.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { buildListDebugGlobalsExpression } from "../../core/debugGlobals.js";
3
+ describe("buildListDebugGlobalsExpression", () => {
4
+ it("probes globalThis.__rn__ and surfaces its keys", () => {
5
+ const expr = buildListDebugGlobalsExpression();
6
+ expect(expr).toContain("__rn__");
7
+ expect(expr).toMatch(/rn\s*:/);
8
+ });
9
+ it("emits a hint that mentions globalThis.__rn__", () => {
10
+ const expr = buildListDebugGlobalsExpression();
11
+ expect(expr).toContain("globalThis.__rn__");
12
+ });
13
+ });
14
+ //# sourceMappingURL=debugGlobals.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debugGlobals.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/debugGlobals.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAE7E,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACtD,MAAM,IAAI,GAAG,+BAA+B,EAAE,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG,+BAA+B,EAAE,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=escapeNonAscii.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escapeNonAscii.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/escapeNonAscii.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { escapeNonAsciiInStringLiterals } from "../../core/escapeNonAscii.js";
3
+ function run(src) {
4
+ const r = escapeNonAsciiInStringLiterals(src);
5
+ if (!r.ok)
6
+ throw new Error("recognizer failed: " + r.reason);
7
+ return r.expression;
8
+ }
9
+ describe("escapeNonAsciiInStringLiterals — happy paths", () => {
10
+ it("is a no-op on a plain ASCII expression", () => {
11
+ expect(run("1 + 2")).toBe("1 + 2");
12
+ });
13
+ it("is a no-op when there are no literals", () => {
14
+ expect(run("foo.bar.baz")).toBe("foo.bar.baz");
15
+ });
16
+ it("escapes Arabic in a double-quoted string", () => {
17
+ const out = run('"اللغة"');
18
+ expect(out).toBe('"\\u0627\\u0644\\u0644\\u063A\\u0629"');
19
+ });
20
+ it("escapes Arabic in a single-quoted string", () => {
21
+ expect(run("'اللغة'")).toContain("\\u0627");
22
+ });
23
+ it("escapes emoji (astral) using \\u{...} form", () => {
24
+ const out = run('"😀"');
25
+ expect(out).toBe('"\\u{1F600}"');
26
+ });
27
+ it("escapes CJK characters", () => {
28
+ expect(run('"中文"')).toBe('"\\u4E2D\\u6587"');
29
+ });
30
+ it("leaves identifiers and keywords alone", () => {
31
+ expect(run("var x = 1; x")).toBe("var x = 1; x");
32
+ });
33
+ it("preserves regex literals verbatim", () => {
34
+ expect(run("/[abc]/.test('x')")).toBe("/[abc]/.test('x')");
35
+ });
36
+ it("preserves comments verbatim", () => {
37
+ expect(run("// 中文 comment\n1")).toBe("// 中文 comment\n1");
38
+ expect(run("/* 中文 */ 1")).toBe("/* 中文 */ 1");
39
+ });
40
+ });
41
+ describe("escapeNonAsciiInStringLiterals — tricky cases", () => {
42
+ it("handles backslash escapes inside a string", () => {
43
+ expect(run('"a\\"中b"')).toBe('"a\\"\\u4E2Db"');
44
+ });
45
+ it("handles apostrophe inside a double-quoted string", () => {
46
+ expect(run('"it\'s café"')).toBe('"it\'s caf\\u00E9"');
47
+ });
48
+ it("handles template literal substitutions as expression context", () => {
49
+ const out = run("`a中${1 + 2}b文`");
50
+ expect(out).toBe("`a\\u4E2D${1 + 2}b\\u6587`");
51
+ });
52
+ it("handles a regex with a / inside a character class", () => {
53
+ expect(run("/[/]/.test('x')")).toBe("/[/]/.test('x')");
54
+ });
55
+ it("handles multi-line template literals", () => {
56
+ const src = "`café\nlatte`";
57
+ expect(run(src)).toBe("`caf\\u00E9\nlatte`");
58
+ });
59
+ it("handles ASCII printables (no escape)", () => {
60
+ const src = "`a\tb\nc`";
61
+ expect(run(src)).toBe("`a\tb\nc`");
62
+ });
63
+ });
64
+ describe("escapeNonAsciiInStringLiterals — fallback path", () => {
65
+ it("returns ok:false with a clear message on unbalanced quotes", () => {
66
+ const r = escapeNonAsciiInStringLiterals('"unterminated');
67
+ expect(r.ok).toBe(false);
68
+ if (!r.ok)
69
+ expect(r.reason).toMatch(/unbalanced|unterminated/i);
70
+ });
71
+ it("returns ok:false on unterminated template literal", () => {
72
+ const r = escapeNonAsciiInStringLiterals("`unterminated");
73
+ expect(r.ok).toBe(false);
74
+ });
75
+ });
76
+ //# sourceMappingURL=escapeNonAscii.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escapeNonAscii.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/escapeNonAscii.test.ts"],"names":[],"mappings":"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"}