devharness 0.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 (627) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/build/atomic-write.d.ts +28 -0
  4. package/build/atomic-write.d.ts.map +1 -0
  5. package/build/atomic-write.js +126 -0
  6. package/build/atomic-write.js.map +1 -0
  7. package/build/cdp-manager.d.ts +524 -0
  8. package/build/cdp-manager.d.ts.map +1 -0
  9. package/build/cdp-manager.js +1828 -0
  10. package/build/cdp-manager.js.map +1 -0
  11. package/build/cdp-manager.test.d.ts +6 -0
  12. package/build/cdp-manager.test.d.ts.map +1 -0
  13. package/build/cdp-manager.test.js +546 -0
  14. package/build/cdp-manager.test.js.map +1 -0
  15. package/build/chrome-launcher.d.ts +400 -0
  16. package/build/chrome-launcher.d.ts.map +1 -0
  17. package/build/chrome-launcher.js +1071 -0
  18. package/build/chrome-launcher.js.map +1 -0
  19. package/build/chrome-launcher.test.d.ts +11 -0
  20. package/build/chrome-launcher.test.d.ts.map +1 -0
  21. package/build/chrome-launcher.test.js +204 -0
  22. package/build/chrome-launcher.test.js.map +1 -0
  23. package/build/clickable-cache.d.ts +73 -0
  24. package/build/clickable-cache.d.ts.map +1 -0
  25. package/build/clickable-cache.js +90 -0
  26. package/build/clickable-cache.js.map +1 -0
  27. package/build/command-recorder.d.ts +401 -0
  28. package/build/command-recorder.d.ts.map +1 -0
  29. package/build/command-recorder.js +701 -0
  30. package/build/command-recorder.js.map +1 -0
  31. package/build/config.d.ts +390 -0
  32. package/build/config.d.ts.map +1 -0
  33. package/build/config.js +826 -0
  34. package/build/config.js.map +1 -0
  35. package/build/config.test.d.ts +2 -0
  36. package/build/config.test.d.ts.map +1 -0
  37. package/build/config.test.js +112 -0
  38. package/build/config.test.js.map +1 -0
  39. package/build/connection-manager.d.ts +159 -0
  40. package/build/connection-manager.d.ts.map +1 -0
  41. package/build/connection-manager.js +432 -0
  42. package/build/connection-manager.js.map +1 -0
  43. package/build/connection-manager.test.d.ts +11 -0
  44. package/build/connection-manager.test.d.ts.map +1 -0
  45. package/build/connection-manager.test.js +114 -0
  46. package/build/connection-manager.test.js.map +1 -0
  47. package/build/console-monitor.d.ts +150 -0
  48. package/build/console-monitor.d.ts.map +1 -0
  49. package/build/console-monitor.js +454 -0
  50. package/build/console-monitor.js.map +1 -0
  51. package/build/dashboard/bundle.js +1 -0
  52. package/build/dashboard/dashboard-client.d.ts +42 -0
  53. package/build/dashboard/dashboard-client.d.ts.map +1 -0
  54. package/build/dashboard/dashboard-client.js +184 -0
  55. package/build/dashboard/dashboard-client.js.map +1 -0
  56. package/build/dashboard/dashboard-hub.d.ts +50 -0
  57. package/build/dashboard/dashboard-hub.d.ts.map +1 -0
  58. package/build/dashboard/dashboard-hub.js +654 -0
  59. package/build/dashboard/dashboard-hub.js.map +1 -0
  60. package/build/dashboard/hub-lock.d.ts +16 -0
  61. package/build/dashboard/hub-lock.d.ts.map +1 -0
  62. package/build/dashboard/hub-lock.js +77 -0
  63. package/build/dashboard/hub-lock.js.map +1 -0
  64. package/build/dashboard/index.d.ts +25 -0
  65. package/build/dashboard/index.d.ts.map +1 -0
  66. package/build/dashboard/index.js +77 -0
  67. package/build/dashboard/index.js.map +1 -0
  68. package/build/dashboard/types.d.ts +83 -0
  69. package/build/dashboard/types.d.ts.map +1 -0
  70. package/build/dashboard/types.js +6 -0
  71. package/build/dashboard/types.js.map +1 -0
  72. package/build/debug-logger.d.ts +85 -0
  73. package/build/debug-logger.d.ts.map +1 -0
  74. package/build/debug-logger.js +195 -0
  75. package/build/debug-logger.js.map +1 -0
  76. package/build/debugger-aware-wrapper.d.ts +37 -0
  77. package/build/debugger-aware-wrapper.d.ts.map +1 -0
  78. package/build/debugger-aware-wrapper.js +128 -0
  79. package/build/debugger-aware-wrapper.js.map +1 -0
  80. package/build/dom-change-monitor.d.ts +92 -0
  81. package/build/dom-change-monitor.d.ts.map +1 -0
  82. package/build/dom-change-monitor.js +437 -0
  83. package/build/dom-change-monitor.js.map +1 -0
  84. package/build/element-collector.d.ts +16 -0
  85. package/build/element-collector.d.ts.map +1 -0
  86. package/build/element-collector.js +207 -0
  87. package/build/element-collector.js.map +1 -0
  88. package/build/error-helpers.d.ts +43 -0
  89. package/build/error-helpers.d.ts.map +1 -0
  90. package/build/error-helpers.js +143 -0
  91. package/build/error-helpers.js.map +1 -0
  92. package/build/formatters/console-formatter.d.ts +160 -0
  93. package/build/formatters/console-formatter.d.ts.map +1 -0
  94. package/build/formatters/console-formatter.js +338 -0
  95. package/build/formatters/console-formatter.js.map +1 -0
  96. package/build/helpers/parser-plugins.d.ts +44 -0
  97. package/build/helpers/parser-plugins.d.ts.map +1 -0
  98. package/build/helpers/parser-plugins.js +102 -0
  99. package/build/helpers/parser-plugins.js.map +1 -0
  100. package/build/helpers/paths.d.ts +79 -0
  101. package/build/helpers/paths.d.ts.map +1 -0
  102. package/build/helpers/paths.js +168 -0
  103. package/build/helpers/paths.js.map +1 -0
  104. package/build/helpers/process-liveness.d.ts +24 -0
  105. package/build/helpers/process-liveness.d.ts.map +1 -0
  106. package/build/helpers/process-liveness.js +36 -0
  107. package/build/helpers/process-liveness.js.map +1 -0
  108. package/build/helpers/process-liveness.test.d.ts +2 -0
  109. package/build/helpers/process-liveness.test.d.ts.map +1 -0
  110. package/build/helpers/process-liveness.test.js +40 -0
  111. package/build/helpers/process-liveness.test.js.map +1 -0
  112. package/build/helpers/sequence-tree.d.ts +27 -0
  113. package/build/helpers/sequence-tree.d.ts.map +1 -0
  114. package/build/helpers/sequence-tree.js +80 -0
  115. package/build/helpers/sequence-tree.js.map +1 -0
  116. package/build/helpers/sequence-tree.test.d.ts +2 -0
  117. package/build/helpers/sequence-tree.test.d.ts.map +1 -0
  118. package/build/helpers/sequence-tree.test.js +122 -0
  119. package/build/helpers/sequence-tree.test.js.map +1 -0
  120. package/build/index.d.ts +3 -0
  121. package/build/index.d.ts.map +1 -0
  122. package/build/index.js +2007 -0
  123. package/build/index.js.map +1 -0
  124. package/build/interaction-recorder.d.ts +214 -0
  125. package/build/interaction-recorder.d.ts.map +1 -0
  126. package/build/interaction-recorder.js +2007 -0
  127. package/build/interaction-recorder.js.map +1 -0
  128. package/build/issue-tracker.d.ts +107 -0
  129. package/build/issue-tracker.d.ts.map +1 -0
  130. package/build/issue-tracker.js +582 -0
  131. package/build/issue-tracker.js.map +1 -0
  132. package/build/issue-tracker.test.d.ts +2 -0
  133. package/build/issue-tracker.test.d.ts.map +1 -0
  134. package/build/issue-tracker.test.js +88 -0
  135. package/build/issue-tracker.test.js.map +1 -0
  136. package/build/launch-chrome-port.test.d.ts +14 -0
  137. package/build/launch-chrome-port.test.d.ts.map +1 -0
  138. package/build/launch-chrome-port.test.js +97 -0
  139. package/build/launch-chrome-port.test.js.map +1 -0
  140. package/build/log-processor/orchestrator.d.ts +32 -0
  141. package/build/log-processor/orchestrator.d.ts.map +1 -0
  142. package/build/log-processor/orchestrator.js +27 -0
  143. package/build/log-processor/orchestrator.js.map +1 -0
  144. package/build/logpoint-execution-tracker.d.ts +57 -0
  145. package/build/logpoint-execution-tracker.d.ts.map +1 -0
  146. package/build/logpoint-execution-tracker.js +112 -0
  147. package/build/logpoint-execution-tracker.js.map +1 -0
  148. package/build/mcp-supervisor.d.ts +3 -0
  149. package/build/mcp-supervisor.d.ts.map +1 -0
  150. package/build/mcp-supervisor.js +211 -0
  151. package/build/mcp-supervisor.js.map +1 -0
  152. package/build/messages.d.ts +165 -0
  153. package/build/messages.d.ts.map +1 -0
  154. package/build/messages.js +574 -0
  155. package/build/messages.js.map +1 -0
  156. package/build/network-monitor.d.ts +172 -0
  157. package/build/network-monitor.d.ts.map +1 -0
  158. package/build/network-monitor.js +350 -0
  159. package/build/network-monitor.js.map +1 -0
  160. package/build/overlays.d.ts +49 -0
  161. package/build/overlays.d.ts.map +1 -0
  162. package/build/overlays.js +409 -0
  163. package/build/overlays.js.map +1 -0
  164. package/build/persistent-profiles.test.d.ts +15 -0
  165. package/build/persistent-profiles.test.d.ts.map +1 -0
  166. package/build/persistent-profiles.test.js +470 -0
  167. package/build/persistent-profiles.test.js.map +1 -0
  168. package/build/port-reserver.d.ts +32 -0
  169. package/build/port-reserver.d.ts.map +1 -0
  170. package/build/port-reserver.js +118 -0
  171. package/build/port-reserver.js.map +1 -0
  172. package/build/puppeteer-manager.d.ts +47 -0
  173. package/build/puppeteer-manager.d.ts.map +1 -0
  174. package/build/puppeteer-manager.js +116 -0
  175. package/build/puppeteer-manager.js.map +1 -0
  176. package/build/recording-coverage.test.d.ts +12 -0
  177. package/build/recording-coverage.test.d.ts.map +1 -0
  178. package/build/recording-coverage.test.js +72 -0
  179. package/build/recording-coverage.test.js.map +1 -0
  180. package/build/reference-validator.d.ts +47 -0
  181. package/build/reference-validator.d.ts.map +1 -0
  182. package/build/reference-validator.js +86 -0
  183. package/build/reference-validator.js.map +1 -0
  184. package/build/reference-validator.test.d.ts +5 -0
  185. package/build/reference-validator.test.d.ts.map +1 -0
  186. package/build/reference-validator.test.js +243 -0
  187. package/build/reference-validator.test.js.map +1 -0
  188. package/build/replay-cursor.d.ts +27 -0
  189. package/build/replay-cursor.d.ts.map +1 -0
  190. package/build/replay-cursor.js +240 -0
  191. package/build/replay-cursor.js.map +1 -0
  192. package/build/runners/docker-compose-runner.d.ts +69 -0
  193. package/build/runners/docker-compose-runner.d.ts.map +1 -0
  194. package/build/runners/docker-compose-runner.js +438 -0
  195. package/build/runners/docker-compose-runner.js.map +1 -0
  196. package/build/runners/docker-runner.d.ts +65 -0
  197. package/build/runners/docker-runner.d.ts.map +1 -0
  198. package/build/runners/docker-runner.js +414 -0
  199. package/build/runners/docker-runner.js.map +1 -0
  200. package/build/runners/index.d.ts +19 -0
  201. package/build/runners/index.d.ts.map +1 -0
  202. package/build/runners/index.js +35 -0
  203. package/build/runners/index.js.map +1 -0
  204. package/build/runners/native-runner.d.ts +95 -0
  205. package/build/runners/native-runner.d.ts.map +1 -0
  206. package/build/runners/native-runner.js +370 -0
  207. package/build/runners/native-runner.js.map +1 -0
  208. package/build/runners/types.d.ts +147 -0
  209. package/build/runners/types.d.ts.map +1 -0
  210. package/build/runners/types.js +25 -0
  211. package/build/runners/types.js.map +1 -0
  212. package/build/self-restart.d.ts +12 -0
  213. package/build/self-restart.d.ts.map +1 -0
  214. package/build/self-restart.js +41 -0
  215. package/build/self-restart.js.map +1 -0
  216. package/build/self-restart.test.d.ts +2 -0
  217. package/build/self-restart.test.d.ts.map +1 -0
  218. package/build/self-restart.test.js +34 -0
  219. package/build/self-restart.test.js.map +1 -0
  220. package/build/server-claims.d.ts +161 -0
  221. package/build/server-claims.d.ts.map +1 -0
  222. package/build/server-claims.js +382 -0
  223. package/build/server-claims.js.map +1 -0
  224. package/build/server-claims.test.d.ts +2 -0
  225. package/build/server-claims.test.d.ts.map +1 -0
  226. package/build/server-claims.test.js +197 -0
  227. package/build/server-claims.test.js.map +1 -0
  228. package/build/server-manager.d.ts +438 -0
  229. package/build/server-manager.d.ts.map +1 -0
  230. package/build/server-manager.js +1674 -0
  231. package/build/server-manager.js.map +1 -0
  232. package/build/server-manager.test.d.ts +17 -0
  233. package/build/server-manager.test.d.ts.map +1 -0
  234. package/build/server-manager.test.js +122 -0
  235. package/build/server-manager.test.js.map +1 -0
  236. package/build/server-ownership.test.d.ts +2 -0
  237. package/build/server-ownership.test.d.ts.map +1 -0
  238. package/build/server-ownership.test.js +238 -0
  239. package/build/server-ownership.test.js.map +1 -0
  240. package/build/server-watcher.d.ts +21 -0
  241. package/build/server-watcher.d.ts.map +1 -0
  242. package/build/server-watcher.js +105 -0
  243. package/build/server-watcher.js.map +1 -0
  244. package/build/session-detector.d.ts +47 -0
  245. package/build/session-detector.d.ts.map +1 -0
  246. package/build/session-detector.js +229 -0
  247. package/build/session-detector.js.map +1 -0
  248. package/build/session-detector.test.d.ts +9 -0
  249. package/build/session-detector.test.d.ts.map +1 -0
  250. package/build/session-detector.test.js +45 -0
  251. package/build/session-detector.test.js.map +1 -0
  252. package/build/sourcemap-handler.d.ts +114 -0
  253. package/build/sourcemap-handler.d.ts.map +1 -0
  254. package/build/sourcemap-handler.js +479 -0
  255. package/build/sourcemap-handler.js.map +1 -0
  256. package/build/supervisor/child-manager.d.ts +43 -0
  257. package/build/supervisor/child-manager.d.ts.map +1 -0
  258. package/build/supervisor/child-manager.js +148 -0
  259. package/build/supervisor/child-manager.js.map +1 -0
  260. package/build/supervisor/child-manager.test.d.ts +2 -0
  261. package/build/supervisor/child-manager.test.d.ts.map +1 -0
  262. package/build/supervisor/child-manager.test.js +107 -0
  263. package/build/supervisor/child-manager.test.js.map +1 -0
  264. package/build/supervisor/client-watcher.d.ts +48 -0
  265. package/build/supervisor/client-watcher.d.ts.map +1 -0
  266. package/build/supervisor/client-watcher.js +186 -0
  267. package/build/supervisor/client-watcher.js.map +1 -0
  268. package/build/supervisor/client-watcher.test.d.ts +2 -0
  269. package/build/supervisor/client-watcher.test.d.ts.map +1 -0
  270. package/build/supervisor/client-watcher.test.js +121 -0
  271. package/build/supervisor/client-watcher.test.js.map +1 -0
  272. package/build/supervisor/idle-config.d.ts +30 -0
  273. package/build/supervisor/idle-config.d.ts.map +1 -0
  274. package/build/supervisor/idle-config.js +76 -0
  275. package/build/supervisor/idle-config.js.map +1 -0
  276. package/build/supervisor/idle-config.test.d.ts +2 -0
  277. package/build/supervisor/idle-config.test.d.ts.map +1 -0
  278. package/build/supervisor/idle-config.test.js +119 -0
  279. package/build/supervisor/idle-config.test.js.map +1 -0
  280. package/build/supervisor/ndjson-reader.d.ts +47 -0
  281. package/build/supervisor/ndjson-reader.d.ts.map +1 -0
  282. package/build/supervisor/ndjson-reader.js +75 -0
  283. package/build/supervisor/ndjson-reader.js.map +1 -0
  284. package/build/supervisor/ndjson-reader.test.d.ts +2 -0
  285. package/build/supervisor/ndjson-reader.test.d.ts.map +1 -0
  286. package/build/supervisor/ndjson-reader.test.js +92 -0
  287. package/build/supervisor/ndjson-reader.test.js.map +1 -0
  288. package/build/supervisor/pidfile.d.ts +11 -0
  289. package/build/supervisor/pidfile.d.ts.map +1 -0
  290. package/build/supervisor/pidfile.js +29 -0
  291. package/build/supervisor/pidfile.js.map +1 -0
  292. package/build/supervisor/pidfile.test.d.ts +8 -0
  293. package/build/supervisor/pidfile.test.d.ts.map +1 -0
  294. package/build/supervisor/pidfile.test.js +46 -0
  295. package/build/supervisor/pidfile.test.js.map +1 -0
  296. package/build/supervisor/restart-coordinator.d.ts +73 -0
  297. package/build/supervisor/restart-coordinator.d.ts.map +1 -0
  298. package/build/supervisor/restart-coordinator.js +300 -0
  299. package/build/supervisor/restart-coordinator.js.map +1 -0
  300. package/build/supervisor/restart-coordinator.test.d.ts +2 -0
  301. package/build/supervisor/restart-coordinator.test.d.ts.map +1 -0
  302. package/build/supervisor/restart-coordinator.test.js +434 -0
  303. package/build/supervisor/restart-coordinator.test.js.map +1 -0
  304. package/build/test-support/fake-execute-tool-call.d.ts +11 -0
  305. package/build/test-support/fake-execute-tool-call.d.ts.map +1 -0
  306. package/build/test-support/fake-execute-tool-call.js +19 -0
  307. package/build/test-support/fake-execute-tool-call.js.map +1 -0
  308. package/build/tool-error.d.ts +14 -0
  309. package/build/tool-error.d.ts.map +1 -0
  310. package/build/tool-error.js +18 -0
  311. package/build/tool-error.js.map +1 -0
  312. package/build/tool-response.d.ts +293 -0
  313. package/build/tool-response.d.ts.map +1 -0
  314. package/build/tool-response.js +365 -0
  315. package/build/tool-response.js.map +1 -0
  316. package/build/tools/assert-tools.d.ts +62 -0
  317. package/build/tools/assert-tools.d.ts.map +1 -0
  318. package/build/tools/assert-tools.js +330 -0
  319. package/build/tools/assert-tools.js.map +1 -0
  320. package/build/tools/breakpoint-await-abort.test.d.ts +2 -0
  321. package/build/tools/breakpoint-await-abort.test.d.ts.map +1 -0
  322. package/build/tools/breakpoint-await-abort.test.js +124 -0
  323. package/build/tools/breakpoint-await-abort.test.js.map +1 -0
  324. package/build/tools/breakpoint-tools.d.ts +99 -0
  325. package/build/tools/breakpoint-tools.d.ts.map +1 -0
  326. package/build/tools/breakpoint-tools.js +1197 -0
  327. package/build/tools/breakpoint-tools.js.map +1 -0
  328. package/build/tools/config-tools.d.ts +58 -0
  329. package/build/tools/config-tools.d.ts.map +1 -0
  330. package/build/tools/config-tools.js +180 -0
  331. package/build/tools/config-tools.js.map +1 -0
  332. package/build/tools/connection-routing.test.d.ts +12 -0
  333. package/build/tools/connection-routing.test.d.ts.map +1 -0
  334. package/build/tools/connection-routing.test.js +206 -0
  335. package/build/tools/connection-routing.test.js.map +1 -0
  336. package/build/tools/console-tools.d.ts +84 -0
  337. package/build/tools/console-tools.d.ts.map +1 -0
  338. package/build/tools/console-tools.js +241 -0
  339. package/build/tools/console-tools.js.map +1 -0
  340. package/build/tools/content-tools.d.ts +74 -0
  341. package/build/tools/content-tools.d.ts.map +1 -0
  342. package/build/tools/content-tools.js +582 -0
  343. package/build/tools/content-tools.js.map +1 -0
  344. package/build/tools/dashboard-tools.d.ts +38 -0
  345. package/build/tools/dashboard-tools.d.ts.map +1 -0
  346. package/build/tools/dashboard-tools.js +123 -0
  347. package/build/tools/dashboard-tools.js.map +1 -0
  348. package/build/tools/dom-tools.d.ts +41 -0
  349. package/build/tools/dom-tools.d.ts.map +1 -0
  350. package/build/tools/dom-tools.js +277 -0
  351. package/build/tools/dom-tools.js.map +1 -0
  352. package/build/tools/download-tools.d.ts +55 -0
  353. package/build/tools/download-tools.d.ts.map +1 -0
  354. package/build/tools/download-tools.js +280 -0
  355. package/build/tools/download-tools.js.map +1 -0
  356. package/build/tools/execution-tools.d.ts +40 -0
  357. package/build/tools/execution-tools.d.ts.map +1 -0
  358. package/build/tools/execution-tools.js +121 -0
  359. package/build/tools/execution-tools.js.map +1 -0
  360. package/build/tools/input-abort.test.d.ts +2 -0
  361. package/build/tools/input-abort.test.d.ts.map +1 -0
  362. package/build/tools/input-abort.test.js +227 -0
  363. package/build/tools/input-abort.test.js.map +1 -0
  364. package/build/tools/input-ambiguous-selector.test.d.ts +2 -0
  365. package/build/tools/input-ambiguous-selector.test.d.ts.map +1 -0
  366. package/build/tools/input-ambiguous-selector.test.js +34 -0
  367. package/build/tools/input-ambiguous-selector.test.js.map +1 -0
  368. package/build/tools/input-tools.d.ts +168 -0
  369. package/build/tools/input-tools.d.ts.map +1 -0
  370. package/build/tools/input-tools.js +1397 -0
  371. package/build/tools/input-tools.js.map +1 -0
  372. package/build/tools/inspection-tools.d.ts +112 -0
  373. package/build/tools/inspection-tools.d.ts.map +1 -0
  374. package/build/tools/inspection-tools.js +637 -0
  375. package/build/tools/inspection-tools.js.map +1 -0
  376. package/build/tools/interpolation.d.ts +30 -0
  377. package/build/tools/interpolation.d.ts.map +1 -0
  378. package/build/tools/interpolation.js +143 -0
  379. package/build/tools/interpolation.js.map +1 -0
  380. package/build/tools/issues-abort-hygiene.test.d.ts +2 -0
  381. package/build/tools/issues-abort-hygiene.test.d.ts.map +1 -0
  382. package/build/tools/issues-abort-hygiene.test.js +79 -0
  383. package/build/tools/issues-abort-hygiene.test.js.map +1 -0
  384. package/build/tools/issues-tools.d.ts +98 -0
  385. package/build/tools/issues-tools.d.ts.map +1 -0
  386. package/build/tools/issues-tools.js +821 -0
  387. package/build/tools/issues-tools.js.map +1 -0
  388. package/build/tools/issues-tools.test.d.ts +8 -0
  389. package/build/tools/issues-tools.test.d.ts.map +1 -0
  390. package/build/tools/issues-tools.test.js +184 -0
  391. package/build/tools/issues-tools.test.js.map +1 -0
  392. package/build/tools/modal-tools.d.ts +73 -0
  393. package/build/tools/modal-tools.d.ts.map +1 -0
  394. package/build/tools/modal-tools.js +161 -0
  395. package/build/tools/modal-tools.js.map +1 -0
  396. package/build/tools/network-tools.d.ts +72 -0
  397. package/build/tools/network-tools.d.ts.map +1 -0
  398. package/build/tools/network-tools.js +432 -0
  399. package/build/tools/network-tools.js.map +1 -0
  400. package/build/tools/page-tools.d.ts +78 -0
  401. package/build/tools/page-tools.d.ts.map +1 -0
  402. package/build/tools/page-tools.js +255 -0
  403. package/build/tools/page-tools.js.map +1 -0
  404. package/build/tools/plugin-tools.d.ts +16 -0
  405. package/build/tools/plugin-tools.d.ts.map +1 -0
  406. package/build/tools/plugin-tools.js +17 -0
  407. package/build/tools/plugin-tools.js.map +1 -0
  408. package/build/tools/replay-abort.test.d.ts +2 -0
  409. package/build/tools/replay-abort.test.d.ts.map +1 -0
  410. package/build/tools/replay-abort.test.js +259 -0
  411. package/build/tools/replay-abort.test.js.map +1 -0
  412. package/build/tools/replay-add-conditional.test.d.ts +2 -0
  413. package/build/tools/replay-add-conditional.test.d.ts.map +1 -0
  414. package/build/tools/replay-add-conditional.test.js +205 -0
  415. package/build/tools/replay-add-conditional.test.js.map +1 -0
  416. package/build/tools/replay-background-run.test.d.ts +2 -0
  417. package/build/tools/replay-background-run.test.d.ts.map +1 -0
  418. package/build/tools/replay-background-run.test.js +176 -0
  419. package/build/tools/replay-background-run.test.js.map +1 -0
  420. package/build/tools/replay-capture-variables.test.d.ts +2 -0
  421. package/build/tools/replay-capture-variables.test.d.ts.map +1 -0
  422. package/build/tools/replay-capture-variables.test.js +331 -0
  423. package/build/tools/replay-capture-variables.test.js.map +1 -0
  424. package/build/tools/replay-conditional-idb-and-launch.test.d.ts +11 -0
  425. package/build/tools/replay-conditional-idb-and-launch.test.d.ts.map +1 -0
  426. package/build/tools/replay-conditional-idb-and-launch.test.js +429 -0
  427. package/build/tools/replay-conditional-idb-and-launch.test.js.map +1 -0
  428. package/build/tools/replay-conditional-timeout.test.d.ts +14 -0
  429. package/build/tools/replay-conditional-timeout.test.d.ts.map +1 -0
  430. package/build/tools/replay-conditional-timeout.test.js +126 -0
  431. package/build/tools/replay-conditional-timeout.test.js.map +1 -0
  432. package/build/tools/replay-declare.test.d.ts +2 -0
  433. package/build/tools/replay-declare.test.d.ts.map +1 -0
  434. package/build/tools/replay-declare.test.js +106 -0
  435. package/build/tools/replay-declare.test.js.map +1 -0
  436. package/build/tools/replay-declared-browser-cleanup.test.d.ts +2 -0
  437. package/build/tools/replay-declared-browser-cleanup.test.d.ts.map +1 -0
  438. package/build/tools/replay-declared-browser-cleanup.test.js +103 -0
  439. package/build/tools/replay-declared-browser-cleanup.test.js.map +1 -0
  440. package/build/tools/replay-declared-profile.test.d.ts +2 -0
  441. package/build/tools/replay-declared-profile.test.d.ts.map +1 -0
  442. package/build/tools/replay-declared-profile.test.js +116 -0
  443. package/build/tools/replay-declared-profile.test.js.map +1 -0
  444. package/build/tools/replay-executor.d.ts +556 -0
  445. package/build/tools/replay-executor.d.ts.map +1 -0
  446. package/build/tools/replay-executor.js +2347 -0
  447. package/build/tools/replay-executor.js.map +1 -0
  448. package/build/tools/replay-folder-save.test.d.ts +2 -0
  449. package/build/tools/replay-folder-save.test.d.ts.map +1 -0
  450. package/build/tools/replay-folder-save.test.js +63 -0
  451. package/build/tools/replay-folder-save.test.js.map +1 -0
  452. package/build/tools/replay-formatters.d.ts +148 -0
  453. package/build/tools/replay-formatters.d.ts.map +1 -0
  454. package/build/tools/replay-formatters.js +856 -0
  455. package/build/tools/replay-formatters.js.map +1 -0
  456. package/build/tools/replay-kill-chrome-on-finish.test.d.ts +2 -0
  457. package/build/tools/replay-kill-chrome-on-finish.test.d.ts.map +1 -0
  458. package/build/tools/replay-kill-chrome-on-finish.test.js +175 -0
  459. package/build/tools/replay-kill-chrome-on-finish.test.js.map +1 -0
  460. package/build/tools/replay-multi-connection.test.d.ts +2 -0
  461. package/build/tools/replay-multi-connection.test.d.ts.map +1 -0
  462. package/build/tools/replay-multi-connection.test.js +547 -0
  463. package/build/tools/replay-multi-connection.test.js.map +1 -0
  464. package/build/tools/replay-output-and-record-options.test.d.ts +2 -0
  465. package/build/tools/replay-output-and-record-options.test.d.ts.map +1 -0
  466. package/build/tools/replay-output-and-record-options.test.js +240 -0
  467. package/build/tools/replay-output-and-record-options.test.js.map +1 -0
  468. package/build/tools/replay-production-error-shape.test.d.ts +7 -0
  469. package/build/tools/replay-production-error-shape.test.d.ts.map +1 -0
  470. package/build/tools/replay-production-error-shape.test.js +93 -0
  471. package/build/tools/replay-production-error-shape.test.js.map +1 -0
  472. package/build/tools/replay-rebase.test.d.ts +2 -0
  473. package/build/tools/replay-rebase.test.d.ts.map +1 -0
  474. package/build/tools/replay-rebase.test.js +58 -0
  475. package/build/tools/replay-rebase.test.js.map +1 -0
  476. package/build/tools/replay-retry-classification.test.d.ts +9 -0
  477. package/build/tools/replay-retry-classification.test.d.ts.map +1 -0
  478. package/build/tools/replay-retry-classification.test.js +57 -0
  479. package/build/tools/replay-retry-classification.test.js.map +1 -0
  480. package/build/tools/replay-run-registry.d.ts +55 -0
  481. package/build/tools/replay-run-registry.d.ts.map +1 -0
  482. package/build/tools/replay-run-registry.js +65 -0
  483. package/build/tools/replay-run-registry.js.map +1 -0
  484. package/build/tools/replay-sequence-reload.test.d.ts +2 -0
  485. package/build/tools/replay-sequence-reload.test.d.ts.map +1 -0
  486. package/build/tools/replay-sequence-reload.test.js +82 -0
  487. package/build/tools/replay-sequence-reload.test.js.map +1 -0
  488. package/build/tools/replay-socket-scope.test.d.ts +2 -0
  489. package/build/tools/replay-socket-scope.test.d.ts.map +1 -0
  490. package/build/tools/replay-socket-scope.test.js +75 -0
  491. package/build/tools/replay-socket-scope.test.js.map +1 -0
  492. package/build/tools/replay-step-connection.test.d.ts +2 -0
  493. package/build/tools/replay-step-connection.test.d.ts.map +1 -0
  494. package/build/tools/replay-step-connection.test.js +292 -0
  495. package/build/tools/replay-step-connection.test.js.map +1 -0
  496. package/build/tools/replay-step-timeout.test.d.ts +2 -0
  497. package/build/tools/replay-step-timeout.test.d.ts.map +1 -0
  498. package/build/tools/replay-step-timeout.test.js +204 -0
  499. package/build/tools/replay-step-timeout.test.js.map +1 -0
  500. package/build/tools/replay-tags.test.d.ts +2 -0
  501. package/build/tools/replay-tags.test.d.ts.map +1 -0
  502. package/build/tools/replay-tags.test.js +99 -0
  503. package/build/tools/replay-tags.test.js.map +1 -0
  504. package/build/tools/replay-teardown-and-foreach.test.d.ts +13 -0
  505. package/build/tools/replay-teardown-and-foreach.test.d.ts.map +1 -0
  506. package/build/tools/replay-teardown-and-foreach.test.js +318 -0
  507. package/build/tools/replay-teardown-and-foreach.test.js.map +1 -0
  508. package/build/tools/replay-tool-name-validation.test.d.ts +2 -0
  509. package/build/tools/replay-tool-name-validation.test.d.ts.map +1 -0
  510. package/build/tools/replay-tool-name-validation.test.js +280 -0
  511. package/build/tools/replay-tool-name-validation.test.js.map +1 -0
  512. package/build/tools/replay-tools.d.ts +288 -0
  513. package/build/tools/replay-tools.d.ts.map +1 -0
  514. package/build/tools/replay-tools.js +2994 -0
  515. package/build/tools/replay-tools.js.map +1 -0
  516. package/build/tools/request-abort.test.d.ts +2 -0
  517. package/build/tools/request-abort.test.d.ts.map +1 -0
  518. package/build/tools/request-abort.test.js +130 -0
  519. package/build/tools/request-abort.test.js.map +1 -0
  520. package/build/tools/request-tools.d.ts +56 -0
  521. package/build/tools/request-tools.d.ts.map +1 -0
  522. package/build/tools/request-tools.js +174 -0
  523. package/build/tools/request-tools.js.map +1 -0
  524. package/build/tools/screenshot-tools.d.ts +135 -0
  525. package/build/tools/screenshot-tools.d.ts.map +1 -0
  526. package/build/tools/screenshot-tools.js +636 -0
  527. package/build/tools/screenshot-tools.js.map +1 -0
  528. package/build/tools/server-tools.d.ts +88 -0
  529. package/build/tools/server-tools.d.ts.map +1 -0
  530. package/build/tools/server-tools.js +485 -0
  531. package/build/tools/server-tools.js.map +1 -0
  532. package/build/tools/source-tools.d.ts +46 -0
  533. package/build/tools/source-tools.d.ts.map +1 -0
  534. package/build/tools/source-tools.js +47 -0
  535. package/build/tools/source-tools.js.map +1 -0
  536. package/build/tools/storage-tools.d.ts +141 -0
  537. package/build/tools/storage-tools.d.ts.map +1 -0
  538. package/build/tools/storage-tools.js +894 -0
  539. package/build/tools/storage-tools.js.map +1 -0
  540. package/build/tools/storage-tools.test.d.ts +13 -0
  541. package/build/tools/storage-tools.test.d.ts.map +1 -0
  542. package/build/tools/storage-tools.test.js +707 -0
  543. package/build/tools/storage-tools.test.js.map +1 -0
  544. package/build/tools/tab-tools.d.ts +42 -0
  545. package/build/tools/tab-tools.d.ts.map +1 -0
  546. package/build/tools/tab-tools.js +296 -0
  547. package/build/tools/tab-tools.js.map +1 -0
  548. package/build/tools/wait-tools.d.ts +85 -0
  549. package/build/tools/wait-tools.d.ts.map +1 -0
  550. package/build/tools/wait-tools.js +236 -0
  551. package/build/tools/wait-tools.js.map +1 -0
  552. package/build/tools/wait-tools.test.d.ts +9 -0
  553. package/build/tools/wait-tools.test.d.ts.map +1 -0
  554. package/build/tools/wait-tools.test.js +193 -0
  555. package/build/tools/wait-tools.test.js.map +1 -0
  556. package/build/types.d.ts +133 -0
  557. package/build/types.d.ts.map +1 -0
  558. package/build/types.js +5 -0
  559. package/build/types.js.map +1 -0
  560. package/build/ui-verifier.d.ts +88 -0
  561. package/build/ui-verifier.d.ts.map +1 -0
  562. package/build/ui-verifier.js +540 -0
  563. package/build/ui-verifier.js.map +1 -0
  564. package/build/utils/abort.d.ts +68 -0
  565. package/build/utils/abort.d.ts.map +1 -0
  566. package/build/utils/abort.js +159 -0
  567. package/build/utils/abort.js.map +1 -0
  568. package/build/utils/abort.test.d.ts +2 -0
  569. package/build/utils/abort.test.d.ts.map +1 -0
  570. package/build/utils/abort.test.js +174 -0
  571. package/build/utils/abort.test.js.map +1 -0
  572. package/build/utils/modal-detection-core.d.ts +150 -0
  573. package/build/utils/modal-detection-core.d.ts.map +1 -0
  574. package/build/utils/modal-detection-core.js +332 -0
  575. package/build/utils/modal-detection-core.js.map +1 -0
  576. package/build/utils/modal-detection-core.test.d.ts +5 -0
  577. package/build/utils/modal-detection-core.test.d.ts.map +1 -0
  578. package/build/utils/modal-detection-core.test.js +492 -0
  579. package/build/utils/modal-detection-core.test.js.map +1 -0
  580. package/build/utils/modal-detector.d.ts +29 -0
  581. package/build/utils/modal-detector.d.ts.map +1 -0
  582. package/build/utils/modal-detector.js +281 -0
  583. package/build/utils/modal-detector.js.map +1 -0
  584. package/build/utils/modal-dismissal.d.ts +32 -0
  585. package/build/utils/modal-dismissal.d.ts.map +1 -0
  586. package/build/utils/modal-dismissal.js +190 -0
  587. package/build/utils/modal-dismissal.js.map +1 -0
  588. package/build/utils/port-check.d.ts +31 -0
  589. package/build/utils/port-check.d.ts.map +1 -0
  590. package/build/utils/port-check.js +87 -0
  591. package/build/utils/port-check.js.map +1 -0
  592. package/build/utils/port-check.test.d.ts +2 -0
  593. package/build/utils/port-check.test.d.ts.map +1 -0
  594. package/build/utils/port-check.test.js +66 -0
  595. package/build/utils/port-check.test.js.map +1 -0
  596. package/build/utils/selector-resolver.d.ts +66 -0
  597. package/build/utils/selector-resolver.d.ts.map +1 -0
  598. package/build/utils/selector-resolver.js +200 -0
  599. package/build/utils/selector-resolver.js.map +1 -0
  600. package/build/validation-helpers.d.ts +41 -0
  601. package/build/validation-helpers.d.ts.map +1 -0
  602. package/build/validation-helpers.js +236 -0
  603. package/build/validation-helpers.js.map +1 -0
  604. package/build/window-sizing.d.ts +26 -0
  605. package/build/window-sizing.d.ts.map +1 -0
  606. package/build/window-sizing.js +87 -0
  607. package/build/window-sizing.js.map +1 -0
  608. package/build/window-sizing.test.d.ts +8 -0
  609. package/build/window-sizing.test.d.ts.map +1 -0
  610. package/build/window-sizing.test.js +113 -0
  611. package/build/window-sizing.test.js.map +1 -0
  612. package/docs/README.md +74 -0
  613. package/docs/automation.md +562 -0
  614. package/docs/debugging.md +229 -0
  615. package/docs/installation.md +133 -0
  616. package/docs/instructions.md +227 -0
  617. package/docs/mcp-instructions.md +15 -0
  618. package/docs/message-system.md +327 -0
  619. package/docs/messages.md +3480 -0
  620. package/docs/parser-plugins.md +132 -0
  621. package/docs/replay.md +1411 -0
  622. package/docs/troubleshooting.md +272 -0
  623. package/docs/working_with_llm_notes.md +5 -0
  624. package/package.json +87 -0
  625. package/skills/devharness/SKILL.md +195 -0
  626. package/skills/devharness/references/sequences.md +613 -0
  627. package/skills/devharness/references/tool-categories.md +99 -0
@@ -0,0 +1,1828 @@
1
+ /**
2
+ * CDP Connection Manager
3
+ * Handles connection to Chrome DevTools Protocol (Chrome or Node.js)
4
+ */
5
+ import CDP from 'chrome-remote-interface';
6
+ import { debugLog } from './debug-logger.js';
7
+ /**
8
+ * Thrown by evaluateExpression() when the evaluated code itself threw
9
+ * (CDP reports this via `exceptionDetails` on an otherwise-successful
10
+ * Runtime.evaluate/Debugger.evaluateOnCallFrame response, NOT as a rejected
11
+ * promise) - e.g. `Math.max(...bigArray)` throwing a stack-exhaustion
12
+ * RangeError. This is an ordinary outcome of evaluating arbitrary user code
13
+ * and must be surfaced as a normal tool error rather than silently ignored.
14
+ */
15
+ export class EvaluateExpressionExceptionError extends Error {
16
+ expression;
17
+ exceptionType;
18
+ exceptionMessage;
19
+ exceptionStack;
20
+ constructor(expression, exceptionType, exceptionMessage, exceptionStack) {
21
+ super(`${exceptionType}: ${exceptionMessage}`);
22
+ this.expression = expression;
23
+ this.exceptionType = exceptionType;
24
+ this.exceptionMessage = exceptionMessage;
25
+ this.exceptionStack = exceptionStack;
26
+ this.name = 'EvaluateExpressionExceptionError';
27
+ }
28
+ }
29
+ /**
30
+ * Thrown by evaluateExpression() when the underlying CDP call does not
31
+ * return within the bounded client-side timeout. This is the backstop for
32
+ * cases where the renderer/execution context stops responding entirely (no
33
+ * result, no exception, no CDP-level timeout) - see the RangeError
34
+ * stack-exhaustion hang this was written to fix.
35
+ */
36
+ export class EvaluateExpressionTimeoutError extends Error {
37
+ expression;
38
+ timeoutMs;
39
+ constructor(expression, timeoutMs) {
40
+ super(`Evaluation timed out after ${timeoutMs}ms - the execution context may be unresponsive`);
41
+ this.expression = expression;
42
+ this.timeoutMs = timeoutMs;
43
+ this.name = 'EvaluateExpressionTimeoutError';
44
+ }
45
+ }
46
+ /**
47
+ * Thrown by evaluateExpression() when the expression returned a Promise that
48
+ * is still pending while the debugger is paused. The paused event loop can
49
+ * never run the microtask queue, so the promise can never settle - awaiting
50
+ * it would only burn the full client-side timeout. Failing fast with an
51
+ * explanation is strictly better. (Promises that already settled - e.g.
52
+ * `Promise.resolve(x)` or an async IIFE with no real await - ARE recovered
53
+ * via their [[PromiseState]]/[[PromiseResult]] internal properties and never
54
+ * reach this error.)
55
+ */
56
+ export class EvaluateExpressionPendingPromiseError extends Error {
57
+ expression;
58
+ constructor(expression) {
59
+ super('Expression returned a pending Promise while the debugger is paused - the event loop is stopped, so it can never settle');
60
+ this.expression = expression;
61
+ this.name = 'EvaluateExpressionPendingPromiseError';
62
+ }
63
+ }
64
+ /** Truncate a long expression for inclusion in error messages/telemetry. */
65
+ function truncateExpression(expression, maxLength = 200) {
66
+ return expression.length > maxLength ? `${expression.slice(0, maxLength)}...` : expression;
67
+ }
68
+ export class CDPManager {
69
+ client = null;
70
+ state = {
71
+ connected: false,
72
+ paused: false,
73
+ breakpoints: new Map(),
74
+ };
75
+ scriptIdToUrl = new Map();
76
+ urlToScriptId = new Map(); // Support multiple scripts per URL (inline HTML scripts)
77
+ pauseResolvers = [];
78
+ scriptWaitResolvers = [];
79
+ sourceMapHandler = null;
80
+ logpointLimitExceeded = null;
81
+ consoleMessageCallback = null;
82
+ pauseCallback = null;
83
+ resumeCallback = null;
84
+ // DOMDebugger state for advanced breakpoints
85
+ domBreakpoints = new Map();
86
+ eventBreakpoints = new Map();
87
+ xhrBreakpoints = new Map();
88
+ advancedBpCounter = 0;
89
+ constructor(sourceMapHandler) {
90
+ this.sourceMapHandler = sourceMapHandler || null;
91
+ }
92
+ /**
93
+ * Set a callback to receive console messages from Runtime.consoleAPICalled
94
+ * This is used for Node.js debugging where Puppeteer's page.on('console') isn't available
95
+ */
96
+ setConsoleMessageCallback(callback) {
97
+ this.consoleMessageCallback = callback;
98
+ }
99
+ /**
100
+ * Set a callback to be called when debugger pauses at a breakpoint
101
+ */
102
+ setPauseCallback(callback) {
103
+ this.pauseCallback = callback;
104
+ }
105
+ /**
106
+ * Set a callback to be called when debugger resumes from a breakpoint
107
+ */
108
+ setResumeCallback(callback) {
109
+ this.resumeCallback = callback;
110
+ }
111
+ /**
112
+ * Find script IDs by URL, with fallback matching strategies:
113
+ * 1. Exact match
114
+ * 2. Base URL match (strips query params)
115
+ * 3. Filename/path suffix match (e.g., "click.js" matches "/controls/click.js")
116
+ *
117
+ * This allows breakpoints to work with partial URLs and across page reloads
118
+ * Returns the most recently loaded script (highest scriptId) when multiple matches exist
119
+ */
120
+ findScriptIds(url) {
121
+ // First, try exact match
122
+ const exactMatch = this.urlToScriptId.get(url);
123
+ if (exactMatch && exactMatch.length > 0) {
124
+ return { scriptIds: exactMatch, matchedUrl: url };
125
+ }
126
+ // Second, try matching by base URL (strip query params)
127
+ // If multiple matches, prefer the one with the highest scriptId (most recent)
128
+ const baseUrl = url.split('?')[0];
129
+ let bestMatch = null;
130
+ let highestScriptId = -1;
131
+ for (const [loadedUrl, scriptIds] of this.urlToScriptId.entries()) {
132
+ const loadedBaseUrl = loadedUrl.split('?')[0];
133
+ if (loadedBaseUrl === baseUrl && scriptIds.length > 0) {
134
+ // Get the highest numeric scriptId from this entry
135
+ const maxId = Math.max(...scriptIds.map(id => parseInt(id, 10) || 0));
136
+ if (maxId > highestScriptId) {
137
+ highestScriptId = maxId;
138
+ bestMatch = { scriptIds, matchedUrl: loadedUrl };
139
+ }
140
+ }
141
+ }
142
+ if (bestMatch) {
143
+ return bestMatch;
144
+ }
145
+ // Third, try filename/path suffix match
146
+ // This handles cases like "click.js" matching "http://localhost/controls/click.js"
147
+ // or "controls/click.js" matching "http://localhost/controls/click.js"
148
+ for (const [loadedUrl, scriptIds] of this.urlToScriptId.entries()) {
149
+ const loadedBaseUrl = loadedUrl.split('?')[0];
150
+ // Check if the loaded URL ends with the provided URL (after stripping protocol/host)
151
+ if (loadedBaseUrl.endsWith('/' + baseUrl) || loadedBaseUrl.endsWith(baseUrl)) {
152
+ if (scriptIds.length > 0) {
153
+ const maxId = Math.max(...scriptIds.map(id => parseInt(id, 10) || 0));
154
+ if (maxId > highestScriptId) {
155
+ highestScriptId = maxId;
156
+ bestMatch = { scriptIds, matchedUrl: loadedUrl };
157
+ }
158
+ }
159
+ }
160
+ }
161
+ return bestMatch;
162
+ }
163
+ /**
164
+ * Check if a script with the given URL (or matching suffix) is loaded
165
+ * This is useful to detect Vite-style serving where .ts files are loaded directly
166
+ */
167
+ isScriptLoaded(url) {
168
+ return this.findScriptIds(url) !== null;
169
+ }
170
+ /**
171
+ * Get the URL for a script by its ID
172
+ */
173
+ getScriptUrl(scriptId) {
174
+ return this.scriptIdToUrl.get(scriptId);
175
+ }
176
+ /**
177
+ * Connect to a Chrome or Node.js debugger instance
178
+ * @param host - The debugger host (default: localhost)
179
+ * @param port - The debugger port (default: 9222)
180
+ * @param targetId - Optional target ID to connect to a specific page/tab
181
+ */
182
+ async connect(host = 'localhost', port = 9222, targetId) {
183
+ try {
184
+ // If targetId is provided, connect to that specific target
185
+ // Otherwise, connect to the default target
186
+ const cdpOptions = { host, port };
187
+ if (targetId) {
188
+ cdpOptions.target = targetId;
189
+ }
190
+ this.client = await CDP(cdpOptions);
191
+ const { Debugger, Runtime, DOM } = this.client;
192
+ // Set up event listeners BEFORE enabling domains
193
+ // This ensures we capture scriptParsed events for already-loaded scripts
194
+ // (which are emitted immediately when Debugger.enable() is called)
195
+ Debugger.scriptParsed((params) => {
196
+ this.scriptIdToUrl.set(params.scriptId, params.url);
197
+ // Support multiple scripts per URL (inline HTML script blocks)
198
+ const existingScripts = this.urlToScriptId.get(params.url) || [];
199
+ this.urlToScriptId.set(params.url, [...existingScripts, params.scriptId]);
200
+ // Register source map for lazy loading (loaded on-demand when needed)
201
+ if (params.sourceMapURL && this.sourceMapHandler) {
202
+ this.sourceMapHandler.registerSourceMap(params.url, params.sourceMapURL);
203
+ }
204
+ // Check if any waitForScript calls are waiting for this script
205
+ const matchingResolvers = [];
206
+ this.scriptWaitResolvers.forEach((waiter, index) => {
207
+ const matches = typeof waiter.pattern === 'string'
208
+ ? params.url.includes(waiter.pattern)
209
+ : waiter.pattern.test(params.url);
210
+ if (matches) {
211
+ waiter.resolve(params.url);
212
+ matchingResolvers.push(index);
213
+ }
214
+ });
215
+ // Remove matched resolvers (in reverse order to maintain indices)
216
+ matchingResolvers.reverse().forEach(index => {
217
+ this.scriptWaitResolvers.splice(index, 1);
218
+ });
219
+ });
220
+ Debugger.paused((params) => {
221
+ debugLog('cdp-manager', `Debugger.paused event received, resolvers count: ${this.pauseResolvers.length}`);
222
+ this.state.paused = true;
223
+ this.state.currentCallFrames = params.callFrames;
224
+ // Resolve all pending pause promises
225
+ const resolvers = this.pauseResolvers.splice(0);
226
+ debugLog('cdp-manager', `Resolving ${resolvers.length} pause resolvers`);
227
+ resolvers.forEach(resolve => resolve());
228
+ // Inject clickable console link when paused at breakpoint
229
+ if (params.callFrames && params.callFrames.length > 0) {
230
+ const location = params.callFrames[0].location;
231
+ const url = this.scriptIdToUrl.get(location.scriptId) || 'unknown';
232
+ this.injectConsoleLink(url, location.lineNumber, '⏸️ Paused at');
233
+ }
234
+ // Notify pause callback (e.g., to pause port monitoring)
235
+ if (this.pauseCallback) {
236
+ this.pauseCallback();
237
+ }
238
+ });
239
+ Debugger.resumed(() => {
240
+ this.state.paused = false;
241
+ this.state.currentCallFrames = undefined;
242
+ // Notify resume callback (e.g., to resume port monitoring)
243
+ if (this.resumeCallback) {
244
+ this.resumeCallback();
245
+ }
246
+ });
247
+ // Listen for breakpoint resolution - updates pending breakpoints when script loads
248
+ Debugger.breakpointResolved((params) => {
249
+ const { breakpointId, location } = params;
250
+ const existingBp = this.state.breakpoints.get(breakpointId);
251
+ if (existingBp && existingBp.status === 'pending') {
252
+ // Update the breakpoint from pending to resolved
253
+ existingBp.status = 'resolved';
254
+ existingBp.location = location;
255
+ this.state.breakpoints.set(breakpointId, existingBp);
256
+ }
257
+ });
258
+ // Listen for console messages via CDP (works for both Chrome and Node.js)
259
+ // This enables console capture for Node.js where Puppeteer isn't available
260
+ Runtime.consoleAPICalled((params) => {
261
+ if (this.consoleMessageCallback) {
262
+ this.consoleMessageCallback(params);
263
+ }
264
+ });
265
+ // Enable domains AFTER setting up event listeners
266
+ // This ensures we capture all events including scriptParsed for already-loaded scripts
267
+ await Debugger.enable();
268
+ await Runtime.enable();
269
+ // Enable DOM domain (required for DOMDebugger breakpoints to work)
270
+ // Note: DOM domain is not available in Node.js, only in browsers
271
+ try {
272
+ await DOM.enable();
273
+ }
274
+ catch {
275
+ // DOM domain not available (e.g., Node.js runtime)
276
+ }
277
+ // Detect runtime type
278
+ this.state.runtimeType = await this.detectRuntimeType();
279
+ this.state.connected = true;
280
+ }
281
+ catch (error) {
282
+ throw new Error(`Failed to connect to debugger: ${error}`);
283
+ }
284
+ }
285
+ /**
286
+ * Disconnect from the debugger
287
+ */
288
+ async disconnect() {
289
+ if (this.client) {
290
+ const wasPaused = this.state.paused;
291
+ try {
292
+ await this.client.close();
293
+ }
294
+ finally {
295
+ this.client = null;
296
+ this.state.connected = false;
297
+ this.state.paused = false;
298
+ this.state.currentCallFrames = undefined;
299
+ this.state.runtimeType = undefined;
300
+ this.state.breakpoints.clear();
301
+ this.scriptIdToUrl.clear();
302
+ this.urlToScriptId.clear();
303
+ // Clear DOMDebugger state
304
+ this.domBreakpoints.clear();
305
+ this.eventBreakpoints.clear();
306
+ this.xhrBreakpoints.clear();
307
+ // A connection torn down while paused would otherwise never fire
308
+ // Debugger.resumed, leaving anything gated on it (e.g. port monitoring)
309
+ // stuck paused forever.
310
+ if (wasPaused && this.resumeCallback) {
311
+ this.resumeCallback();
312
+ }
313
+ }
314
+ }
315
+ }
316
+ /**
317
+ * Check if connected
318
+ */
319
+ isConnected() {
320
+ return this.state.connected;
321
+ }
322
+ /**
323
+ * Check if currently paused at a breakpoint
324
+ */
325
+ isPaused() {
326
+ return this.state.paused;
327
+ }
328
+ /**
329
+ * Get the runtime type (chrome, node, or unknown)
330
+ */
331
+ getRuntimeType() {
332
+ return this.state.runtimeType || 'unknown';
333
+ }
334
+ /**
335
+ * Detect whether we're connected to Chrome/browser or Node.js
336
+ * This detection happens by checking for browser-specific global objects
337
+ */
338
+ async detectRuntimeType() {
339
+ if (!this.client) {
340
+ return 'unknown';
341
+ }
342
+ try {
343
+ const { Runtime } = this.client;
344
+ // Try to evaluate 'typeof window' - exists in browsers, not in Node.js
345
+ const windowCheck = await Runtime.evaluate({
346
+ expression: 'typeof window',
347
+ silent: true,
348
+ });
349
+ // If 'window' is defined, we're in a browser (Chrome)
350
+ if (windowCheck.result.type === 'object' ||
351
+ windowCheck.result.value === 'object') {
352
+ return 'chrome';
353
+ }
354
+ // Try to evaluate 'typeof process' - exists in Node.js, not in browsers
355
+ const processCheck = await Runtime.evaluate({
356
+ expression: 'typeof process',
357
+ silent: true,
358
+ });
359
+ // If 'process' is an object, we're in Node.js
360
+ if (processCheck.result.type === 'object' ||
361
+ processCheck.result.value === 'object') {
362
+ return 'node';
363
+ }
364
+ return 'unknown';
365
+ }
366
+ catch (error) {
367
+ console.error('Failed to detect runtime type:', error);
368
+ return 'unknown';
369
+ }
370
+ }
371
+ /**
372
+ * Set a breakpoint at a specific file and line
373
+ */
374
+ async setBreakpoint(url, lineNumber, columnNumber, condition) {
375
+ if (!this.state.connected) {
376
+ throw new Error('Not connected to debugger');
377
+ }
378
+ const { Debugger } = this.client;
379
+ // IMPORTANT: CDP uses 0-based line and column numbers internally
380
+ // User provides 1-based numbers (line 1 = first line, column 1 = first column)
381
+ // We must convert before calling CDP API
382
+ const cdpLineNumber = lineNumber - 1; // Convert 1-based → 0-based
383
+ const cdpColumnNumber = columnNumber !== undefined ? columnNumber - 1 : undefined; // Convert 1-based → 0-based
384
+ // Try to find actual URL if the provided one has query params that may differ
385
+ // This handles cache-busting params like ?v=123 that change on rebuild
386
+ let actualUrl = url;
387
+ const match = this.findScriptIds(url);
388
+ if (match && match.matchedUrl !== url) {
389
+ actualUrl = match.matchedUrl;
390
+ }
391
+ // Set breakpoint using 0-based CDP numbers
392
+ let result;
393
+ try {
394
+ result = await Debugger.setBreakpointByUrl({
395
+ url: actualUrl,
396
+ lineNumber: cdpLineNumber,
397
+ columnNumber: cdpColumnNumber,
398
+ condition,
399
+ });
400
+ }
401
+ catch (error) {
402
+ // Handle "Breakpoint at specified location already exists" error
403
+ if (error?.message?.includes('already exists')) {
404
+ // Find and remove the existing breakpoint at this location
405
+ for (const [bpId, bp] of this.state.breakpoints.entries()) {
406
+ if (bp.originalLocation?.url === url &&
407
+ bp.originalLocation?.lineNumber === lineNumber &&
408
+ (columnNumber === undefined || bp.originalLocation?.columnNumber === columnNumber)) {
409
+ await this.removeBreakpoint(bpId);
410
+ break;
411
+ }
412
+ }
413
+ // Retry setting the breakpoint
414
+ result = await Debugger.setBreakpointByUrl({
415
+ url: actualUrl,
416
+ lineNumber: cdpLineNumber,
417
+ columnNumber: cdpColumnNumber,
418
+ condition,
419
+ });
420
+ }
421
+ else {
422
+ throw error;
423
+ }
424
+ }
425
+ // Check if breakpoint was resolved to any location
426
+ // Per CDP spec, setBreakpointByUrl can return empty locations if script isn't loaded yet
427
+ // The breakpoint is still valid and will activate when the script loads
428
+ if (!result.locations || result.locations.length === 0) {
429
+ // Breakpoint is pending - script not loaded yet but breakpoint is queued
430
+ const breakpointInfo = {
431
+ breakpointId: result.breakpointId,
432
+ // For pending breakpoints, we don't have a resolved location yet
433
+ // Use a placeholder that indicates pending state
434
+ location: {
435
+ scriptId: '',
436
+ lineNumber: cdpLineNumber, // Store the requested 0-based line
437
+ columnNumber: cdpColumnNumber,
438
+ },
439
+ originalLocation: { url, lineNumber, columnNumber },
440
+ status: 'pending',
441
+ };
442
+ this.state.breakpoints.set(result.breakpointId, breakpointInfo);
443
+ return breakpointInfo;
444
+ }
445
+ // Warn if multiple locations (rare but possible)
446
+ if (result.locations.length > 1) {
447
+ console.error(`[cdp-tools] Warning: Breakpoint matched ${result.locations.length} locations. Using first match.`);
448
+ }
449
+ // Store breakpoint info
450
+ // - location: Actual location from CDP (0-based)
451
+ // - originalLocation: User-requested location (1-based, what user asked for)
452
+ const breakpointInfo = {
453
+ breakpointId: result.breakpointId,
454
+ location: result.locations[0],
455
+ originalLocation: { url, lineNumber, columnNumber },
456
+ status: 'resolved',
457
+ };
458
+ this.state.breakpoints.set(result.breakpointId, breakpointInfo);
459
+ return breakpointInfo;
460
+ }
461
+ /**
462
+ * Remove a breakpoint
463
+ */
464
+ async removeBreakpoint(breakpointId) {
465
+ if (!this.state.connected) {
466
+ throw new Error('Not connected to debugger');
467
+ }
468
+ const { Debugger } = this.client;
469
+ await Debugger.removeBreakpoint({ breakpointId });
470
+ this.state.breakpoints.delete(breakpointId);
471
+ }
472
+ /**
473
+ * Get all active breakpoints
474
+ */
475
+ getBreakpoints() {
476
+ return Array.from(this.state.breakpoints.values());
477
+ }
478
+ /**
479
+ * Get breakpoint counts (total, regular, logpoints)
480
+ */
481
+ getBreakpointCounts() {
482
+ const all = this.getBreakpoints();
483
+ const logpoints = all.filter(bp => bp.isLogpoint).length;
484
+ const regularBreakpoints = all.length - logpoints;
485
+ return {
486
+ total: all.length,
487
+ breakpoints: regularBreakpoints,
488
+ logpoints,
489
+ };
490
+ }
491
+ /**
492
+ * Wait for a script matching the given URL pattern to load
493
+ * @param urlPattern - String (substring match) or RegExp to match against script URLs
494
+ * @param timeout - Maximum time to wait in milliseconds (default: 10000)
495
+ * @returns The URL of the matched script
496
+ * @throws Error if timeout expires before script loads
497
+ */
498
+ async waitForScript(urlPattern, timeout = 10000) {
499
+ if (!this.state.connected) {
500
+ throw new Error('Not connected to debugger');
501
+ }
502
+ // First, check if script is already loaded
503
+ for (const url of this.urlToScriptId.keys()) {
504
+ const matches = typeof urlPattern === 'string'
505
+ ? url.includes(urlPattern)
506
+ : urlPattern.test(url);
507
+ if (matches) {
508
+ return url; // Script already loaded
509
+ }
510
+ }
511
+ // Script not loaded yet, wait for it
512
+ return new Promise((resolve, reject) => {
513
+ const timeoutId = setTimeout(() => {
514
+ // Remove this waiter from the list
515
+ const index = this.scriptWaitResolvers.findIndex(w => w.resolve === resolveWrapper);
516
+ if (index >= 0) {
517
+ this.scriptWaitResolvers.splice(index, 1);
518
+ }
519
+ reject(new Error(`Timeout: Script matching "${urlPattern}" did not load within ${timeout}ms`));
520
+ }, timeout);
521
+ const resolveWrapper = (url) => {
522
+ clearTimeout(timeoutId);
523
+ resolve(url);
524
+ };
525
+ this.scriptWaitResolvers.push({
526
+ pattern: urlPattern,
527
+ resolve: resolveWrapper,
528
+ });
529
+ });
530
+ }
531
+ /**
532
+ * Get list of all loaded scripts
533
+ * @returns Array of script URLs
534
+ */
535
+ getLoadedScripts() {
536
+ return Array.from(this.urlToScriptId.keys());
537
+ }
538
+ /**
539
+ * Check if a script matching the pattern is loaded
540
+ * @param urlPattern - String (substring match) or RegExp
541
+ * @returns The matched URL or null
542
+ */
543
+ findLoadedScript(urlPattern) {
544
+ for (const url of this.urlToScriptId.keys()) {
545
+ const matches = typeof urlPattern === 'string'
546
+ ? url.includes(urlPattern)
547
+ : urlPattern.test(url);
548
+ if (matches) {
549
+ return url;
550
+ }
551
+ }
552
+ return null;
553
+ }
554
+ /**
555
+ * Synchronize breakpoint state with CDP's actual breakpoints
556
+ * Use this to recover from state desynchronization
557
+ */
558
+ async syncBreakpoints() {
559
+ if (!this.state.connected) {
560
+ throw new Error('Not connected to debugger');
561
+ }
562
+ // This is a future enhancement - for now, just return current counts
563
+ // Full implementation would query CDP for all active breakpoints
564
+ // and reconcile with state.breakpoints Map
565
+ return {
566
+ synced: this.state.breakpoints.size,
567
+ removed: 0,
568
+ };
569
+ }
570
+ /**
571
+ * Diagnose why a breakpoint failed to set (empty locations array)
572
+ * Performs lazy validation to determine exact cause
573
+ */
574
+ async diagnoseBreakpointFailure(url, lineNumber) {
575
+ // Check if we have this script loaded (with fallback to base URL matching)
576
+ const match = this.findScriptIds(url);
577
+ if (!match) {
578
+ return {
579
+ cause: 'script_not_found',
580
+ message: `Script not loaded: ${url}`,
581
+ scriptUrl: url,
582
+ requestedLine: lineNumber,
583
+ suggestion: 'The script has not been loaded by Chrome yet. Use reloadPage() or navigateTo() to ensure the script loads.'
584
+ };
585
+ }
586
+ const { scriptIds } = match;
587
+ // Script exists - check each scriptId to find which contains the requested line
588
+ try {
589
+ const { Debugger } = this.client;
590
+ // Try each script to find one that contains the requested line
591
+ for (const scriptId of scriptIds) {
592
+ const source = await Debugger.getScriptSource({ scriptId });
593
+ const totalLines = source.scriptSource.split('\n').length;
594
+ // Check if this script contains the requested line
595
+ if (lineNumber <= totalLines) {
596
+ // This script contains the line - check if it's executable
597
+ return {
598
+ cause: 'line_not_executable',
599
+ message: `Line ${lineNumber} is not executable code`,
600
+ scriptUrl: url,
601
+ requestedLine: lineNumber,
602
+ totalLines: totalLines,
603
+ suggestion: 'This line may be a comment, blank line, or non-executable declaration. Try setting the breakpoint on a nearby line with executable code (function call, assignment, etc.).'
604
+ };
605
+ }
606
+ }
607
+ // Line number exceeds all scripts - get the maximum lines from all scripts
608
+ let maxLines = 0;
609
+ for (const scriptId of scriptIds) {
610
+ const source = await Debugger.getScriptSource({ scriptId });
611
+ const lineCount = source.scriptSource.split('\n').length;
612
+ maxLines = Math.max(maxLines, lineCount);
613
+ }
614
+ return {
615
+ cause: 'line_out_of_bounds',
616
+ message: `Line ${lineNumber} is out of bounds`,
617
+ scriptUrl: url,
618
+ requestedLine: lineNumber,
619
+ totalLines: maxLines,
620
+ suggestion: scriptIds.length > 1
621
+ ? `This URL has ${scriptIds.length} inline scripts. The largest has ${maxLines} lines. Use searchCode() to find the correct script and line.`
622
+ : `The script only has ${maxLines} lines. Use getSourceCode() to view the file and find valid line numbers.`
623
+ };
624
+ }
625
+ catch (error) {
626
+ // Fallback if we can't get script source
627
+ return {
628
+ cause: 'script_not_found',
629
+ message: `Unable to access script: ${url}`,
630
+ scriptUrl: url,
631
+ requestedLine: lineNumber,
632
+ suggestion: 'The script may have been unloaded. Try reloadPage().'
633
+ };
634
+ }
635
+ }
636
+ /**
637
+ * Resume execution
638
+ */
639
+ async resume() {
640
+ if (!this.state.connected) {
641
+ throw new Error('Not connected to debugger');
642
+ }
643
+ const { Debugger } = this.client;
644
+ await Debugger.resume();
645
+ }
646
+ /**
647
+ * Pause execution
648
+ */
649
+ async pause() {
650
+ if (!this.state.connected) {
651
+ throw new Error('Not connected to debugger');
652
+ }
653
+ const { Debugger } = this.client;
654
+ await Debugger.pause();
655
+ }
656
+ /**
657
+ * Handle logpoint execution limit exceeded
658
+ * This should be called by the LogpointExecutionTracker when a logpoint hits its limit
659
+ */
660
+ async handleLogpointLimitExceeded(metadata) {
661
+ // Store the metadata
662
+ this.logpointLimitExceeded = metadata;
663
+ // Pause execution
664
+ await this.pause();
665
+ }
666
+ /**
667
+ * Get information about the logpoint that exceeded its limit (if any)
668
+ */
669
+ getLogpointLimitExceeded() {
670
+ return this.logpointLimitExceeded;
671
+ }
672
+ /**
673
+ * Clear the logpoint limit exceeded state
674
+ */
675
+ clearLogpointLimitExceeded() {
676
+ this.logpointLimitExceeded = null;
677
+ }
678
+ /**
679
+ * Step over (next line)
680
+ */
681
+ async stepOver() {
682
+ if (!this.state.connected) {
683
+ throw new Error('Not connected to debugger');
684
+ }
685
+ const { Debugger } = this.client;
686
+ await Debugger.stepOver();
687
+ }
688
+ /**
689
+ * Step into function
690
+ */
691
+ async stepInto() {
692
+ if (!this.state.connected) {
693
+ throw new Error('Not connected to debugger');
694
+ }
695
+ const { Debugger } = this.client;
696
+ await Debugger.stepInto();
697
+ }
698
+ /**
699
+ * Step out of function
700
+ */
701
+ async stepOut() {
702
+ if (!this.state.connected) {
703
+ throw new Error('Not connected to debugger');
704
+ }
705
+ const { Debugger } = this.client;
706
+ await Debugger.stepOut();
707
+ }
708
+ /**
709
+ * Get current call stack
710
+ */
711
+ getCallStack() {
712
+ if (!this.state.paused || !this.state.currentCallFrames) {
713
+ return undefined;
714
+ }
715
+ return this.state.currentCallFrames.map((frame) => ({
716
+ callFrameId: frame.callFrameId,
717
+ functionName: frame.functionName || '(anonymous)',
718
+ location: frame.location,
719
+ url: this.scriptIdToUrl.get(frame.location.scriptId) || 'unknown',
720
+ scopeChain: frame.scopeChain,
721
+ }));
722
+ }
723
+ /**
724
+ * Get variables for a specific call frame
725
+ */
726
+ /**
727
+ * Estimate token count for a value (rough approximation: ~4 chars per token)
728
+ */
729
+ estimateTokens(value) {
730
+ const str = typeof value === 'string' ? value : JSON.stringify(value);
731
+ return Math.ceil(str.length / 4);
732
+ }
733
+ /**
734
+ * Calculate effective token budget accounting for response overhead.
735
+ * The final MCP response includes:
736
+ * - Message template text (~100 tokens)
737
+ * - JSON code block markers and wrapper (~50 tokens)
738
+ * - JSON.stringify indentation adds ~30% to data size
739
+ */
740
+ calculateEffectiveBudget(maxTokens) {
741
+ const FIXED_OVERHEAD = 200; // Message template + code block + wrapper
742
+ const PROPORTIONAL_OVERHEAD = 1.3; // JSON indentation adds ~30%
743
+ return Math.floor((maxTokens - FIXED_OVERHEAD) / PROPORTIONAL_OVERHEAD);
744
+ }
745
+ async getVariables(callFrameId, includeGlobal = false, filter, expandObjects = true, maxDepth = 2, maxTokens = 1000) {
746
+ if (!this.state.connected) {
747
+ throw new Error('Not connected to debugger');
748
+ }
749
+ const { Runtime } = this.client;
750
+ const callFrame = this.state.currentCallFrames?.find((frame) => frame.callFrameId === callFrameId);
751
+ if (!callFrame) {
752
+ throw new Error(`Call frame ${callFrameId} not found`);
753
+ }
754
+ let filterRegex = null;
755
+ if (filter) {
756
+ try {
757
+ filterRegex = new RegExp(filter, 'i');
758
+ }
759
+ catch {
760
+ throw new Error(`Invalid filter regex: ${filter}`);
761
+ }
762
+ }
763
+ const requestedDepth = maxDepth;
764
+ // Collect property metadata first (without expanding)
765
+ const propertyMeta = [];
766
+ for (const scope of callFrame.scopeChain) {
767
+ if (scope.type === 'global' && !includeGlobal) {
768
+ continue;
769
+ }
770
+ const properties = await Runtime.getProperties({
771
+ objectId: scope.object.objectId,
772
+ ownProperties: true,
773
+ });
774
+ for (const prop of properties.result) {
775
+ if (!prop.value)
776
+ continue;
777
+ if (filterRegex && !filterRegex.test(prop.name))
778
+ continue;
779
+ propertyMeta.push({ prop, scopeType: scope.type });
780
+ }
781
+ }
782
+ const totalCount = propertyMeta.length;
783
+ const effectiveBudget = this.calculateEffectiveBudget(maxTokens);
784
+ const hasFilter = !!filter;
785
+ // Helper to group variables by scope
786
+ const groupByScope = (variables) => {
787
+ const grouped = {};
788
+ for (const v of variables) {
789
+ const scopeType = v.scopeType || 'unknown';
790
+ if (!grouped[scopeType])
791
+ grouped[scopeType] = [];
792
+ grouped[scopeType].push({ name: v.name, value: v.value, type: v.type });
793
+ }
794
+ return grouped;
795
+ };
796
+ // Helper to compute variables at a given depth
797
+ const computeAtDepth = async (depth) => {
798
+ const variables = [];
799
+ const shouldExpand = expandObjects && depth > 0;
800
+ for (const { prop, scopeType } of propertyMeta) {
801
+ variables.push({
802
+ name: prop.name,
803
+ value: await this.formatValue(prop.value, shouldExpand, depth),
804
+ type: prop.value.type,
805
+ scopeType,
806
+ });
807
+ }
808
+ return { variables, tokens: this.estimateTokens(variables) };
809
+ };
810
+ // Helper to get names-only data
811
+ const getNamesOnly = (variables) => {
812
+ const namesByScope = {};
813
+ for (const v of variables) {
814
+ const scopeType = v.scopeType || 'unknown';
815
+ if (!namesByScope[scopeType])
816
+ namesByScope[scopeType] = [];
817
+ namesByScope[scopeType].push(v.name);
818
+ }
819
+ return namesByScope;
820
+ };
821
+ // Helper to get counts-only data
822
+ const getCountsOnly = (variables) => {
823
+ const countsByScope = {};
824
+ for (const v of variables) {
825
+ const scopeType = v.scopeType || 'unknown';
826
+ countsByScope[scopeType] = (countsByScope[scopeType] || 0) + 1;
827
+ }
828
+ return countsByScope;
829
+ };
830
+ // Step 1: Try full data at requested depth
831
+ if (expandObjects && maxDepth > 0) {
832
+ const requestedResult = await computeAtDepth(maxDepth);
833
+ if (requestedResult.tokens <= effectiveBudget) {
834
+ return {
835
+ data: groupByScope(requestedResult.variables),
836
+ totalCount,
837
+ usedDepth: maxDepth,
838
+ requestedDepth,
839
+ responseType: 'full',
840
+ filterInsufficient: false,
841
+ };
842
+ }
843
+ }
844
+ // Step 2: Try full data at depth 0
845
+ const depth0Result = await computeAtDepth(0);
846
+ if (depth0Result.tokens <= effectiveBudget) {
847
+ // If we got here after trying requested depth, it means depth was reduced
848
+ const wasDepthReduced = expandObjects && maxDepth > 0;
849
+ return {
850
+ data: groupByScope(depth0Result.variables),
851
+ totalCount,
852
+ usedDepth: 0,
853
+ requestedDepth,
854
+ responseType: wasDepthReduced ? 'depth_reduced' : 'full',
855
+ filterInsufficient: false,
856
+ };
857
+ }
858
+ // Step 3: Try names-only
859
+ const namesOnly = getNamesOnly(depth0Result.variables);
860
+ const namesTokens = this.estimateTokens(namesOnly);
861
+ if (namesTokens <= effectiveBudget) {
862
+ return {
863
+ data: namesOnly,
864
+ totalCount,
865
+ usedDepth: 0,
866
+ requestedDepth,
867
+ responseType: 'names_only',
868
+ filterInsufficient: hasFilter, // Filter was provided but still too large
869
+ };
870
+ }
871
+ // Step 4: Fall back to counts-only (always fits)
872
+ const countsOnly = getCountsOnly(depth0Result.variables);
873
+ return {
874
+ data: countsOnly,
875
+ totalCount,
876
+ usedDepth: 0,
877
+ requestedDepth,
878
+ responseType: 'counts_only',
879
+ filterInsufficient: hasFilter,
880
+ };
881
+ }
882
+ /**
883
+ * Bounded upper limit on how long evaluateExpression() will wait for CDP to
884
+ * respond, in milliseconds. Exported as an instance property (rather than a
885
+ * module constant) so tests can override it on a per-manager basis without
886
+ * needing real wall-clock waits. Kept comfortably below the 180s default
887
+ * Puppeteer protocolTimeout that would otherwise leak through as a raw,
888
+ * untyped "Runtime.callFunctionOn timed out" error on this path.
889
+ */
890
+ evaluateExpressionTimeoutMs = 10_000;
891
+ /**
892
+ * Evaluate an expression in the current context.
893
+ *
894
+ * Two distinct failure modes are handled explicitly (this method used to
895
+ * handle neither, which is how a stack-exhaustion RangeError thrown by
896
+ * evaluated code could hang a caller forever with no result, no error, and
897
+ * no timeout):
898
+ *
899
+ * 1. The evaluated code throws. CDP reports this via `exceptionDetails` on
900
+ * an otherwise-successful response - it is NOT a rejected promise - so
901
+ * it must be checked explicitly and surfaced as
902
+ * EvaluateExpressionExceptionError instead of being formatted as if it
903
+ * were a normal return value.
904
+ * 2. The CDP call itself never resolves (a wedged renderer/execution
905
+ * context). A bounded client-side timeout (racing the CDP call against
906
+ * a timer) guarantees this method always settles, throwing
907
+ * EvaluateExpressionTimeoutError instead of hanging indefinitely. We
908
+ * also pass CDP's own `timeout` param so well-behaved renderers abort
909
+ * the runaway script themselves and return exceptionDetails cleanly
910
+ * (case 1) rather than relying solely on our client-side timer.
911
+ */
912
+ async evaluateExpression(expression, callFrameId, expandObjects = true, maxDepth = 2, options) {
913
+ const detailed = await this.evaluateExpressionDetailed(expression, callFrameId, expandObjects, maxDepth, { ...options, captureRaw: false });
914
+ return detailed.formatted;
915
+ }
916
+ /**
917
+ * Like evaluateExpression(), but also attempts an exact by-value capture of
918
+ * the result when options.captureRaw is set (used for `saveAs` and the
919
+ * structured `_meta.inspect.value`, so captured data does not round-trip
920
+ * through display formatting).
921
+ *
922
+ * Promise handling (bug-015), all empirically verified against Chrome 150:
923
+ * - Running page, no callFrameId: `awaitPromise: true` is passed to
924
+ * Runtime.evaluate, so async results resolve to their settled value and
925
+ * rejections surface as exceptionDetails (-> ExceptionError). A promise
926
+ * that never settles is bounded by the client-side timeout.
927
+ * - Paused (callFrameId given, OR a global evaluation while the debugger is
928
+ * paused - Runtime.evaluate with awaitPromise HANGS in that state, and
929
+ * Debugger.evaluateOnCallFrame silently ignores awaitPromise): we never
930
+ * pass awaitPromise. Instead, a Promise result is resolved through its
931
+ * [[PromiseState]]/[[PromiseResult]] internal properties, which works
932
+ * without running the event loop: already-settled promises yield their
933
+ * exact value (or ExceptionError if rejected); a pending promise fails
934
+ * fast with EvaluateExpressionPendingPromiseError instead of burning the
935
+ * timeout, because it can never settle while the event loop is stopped.
936
+ */
937
+ async evaluateExpressionDetailed(expression, callFrameId, expandObjects = true, maxDepth = 2, options) {
938
+ if (!this.state.connected) {
939
+ throw new Error('Not connected to debugger');
940
+ }
941
+ const awaitPromise = options?.awaitPromise !== false;
942
+ const captureRaw = options?.captureRaw === true;
943
+ const timeoutMs = this.evaluateExpressionTimeoutMs;
944
+ // Leave CDP's own timeout slightly shorter than ours so a well-behaved
945
+ // renderer's own abort (which yields a clean exceptionDetails response)
946
+ // has a chance to win the race against our client-side backstop.
947
+ const cdpTimeoutMs = Math.max(1000, timeoutMs - 1000);
948
+ let timer;
949
+ const timeoutGuard = new Promise((_, reject) => {
950
+ timer = setTimeout(() => reject(new EvaluateExpressionTimeoutError(truncateExpression(expression), timeoutMs)), timeoutMs);
951
+ });
952
+ // The whole pipeline (evaluate + promise recovery + formatting + raw
953
+ // capture) runs inside the client-side timeout race: the follow-up CDP
954
+ // calls talk to the same possibly-wedged target as the evaluation itself.
955
+ const work = (async () => {
956
+ let result;
957
+ if (callFrameId) {
958
+ const { Debugger } = this.client;
959
+ // awaitPromise is deliberately NOT passed: V8 ignores it on
960
+ // evaluateOnCallFrame (verified), so it cannot be relied on.
961
+ result = await Debugger.evaluateOnCallFrame({ callFrameId, expression, timeout: cdpTimeoutMs });
962
+ }
963
+ else {
964
+ const { Runtime } = this.client;
965
+ // awaitPromise while the debugger is paused hangs the CDP call
966
+ // (event loop stopped -> promise can never settle), so only pass it
967
+ // when the page is actually running.
968
+ const canAwaitInProtocol = awaitPromise && !this.state.paused;
969
+ result = await Runtime.evaluate({
970
+ expression,
971
+ timeout: cdpTimeoutMs,
972
+ ...(canAwaitInProtocol ? { awaitPromise: true } : {}),
973
+ });
974
+ }
975
+ if (result.exceptionDetails) {
976
+ throw this.toEvaluateExpressionExceptionError(expression, result.exceptionDetails);
977
+ }
978
+ let remote = result.result;
979
+ // Paused-context promise recovery (see doc comment above).
980
+ if (awaitPromise && remote?.subtype === 'promise' && remote.objectId) {
981
+ remote = await this.resolveSettledPromiseWhilePaused(expression, remote);
982
+ }
983
+ const formatted = await this.formatValue(remote, expandObjects, maxDepth);
984
+ if (!captureRaw) {
985
+ return { formatted, rawCaptured: false };
986
+ }
987
+ const capture = await this.captureRawValue(remote);
988
+ return { formatted, ...capture };
989
+ })();
990
+ try {
991
+ return await Promise.race([work, timeoutGuard]);
992
+ }
993
+ finally {
994
+ clearTimeout(timer);
995
+ }
996
+ }
997
+ /** Map CDP exceptionDetails to a typed error (thrown code or awaited promise rejection). */
998
+ toEvaluateExpressionExceptionError(expression, details) {
999
+ const exceptionObject = details.exception;
1000
+ const exceptionType = exceptionObject?.className || exceptionObject?.subtype || 'Error';
1001
+ // `description` on an Error RemoteObject is "Message\n at stack...".
1002
+ // A promise rejected with a primitive has no description - the reason is
1003
+ // carried as exception.value (details.text is just "Uncaught (in promise)").
1004
+ const description = exceptionObject?.description;
1005
+ const exceptionMessage = description?.split('\n')[0] ||
1006
+ (exceptionObject && exceptionObject.value !== undefined ? String(exceptionObject.value) : undefined) ||
1007
+ details.text ||
1008
+ 'Unknown error';
1009
+ const exceptionStack = description?.includes('\n') ? description : undefined;
1010
+ return new EvaluateExpressionExceptionError(truncateExpression(expression), exceptionType, exceptionMessage, exceptionStack);
1011
+ }
1012
+ /**
1013
+ * Resolve a Promise RemoteObject without running the event loop, via its
1014
+ * [[PromiseState]]/[[PromiseResult]] internal properties (readable even
1015
+ * while the debugger is paused). Returns the settled value's RemoteObject;
1016
+ * throws for rejected/pending promises.
1017
+ */
1018
+ async resolveSettledPromiseWhilePaused(expression, promiseRemote) {
1019
+ const { Runtime } = this.client;
1020
+ const props = await Runtime.getProperties({ objectId: promiseRemote.objectId, ownProperties: true });
1021
+ const internal = props.internalProperties || [];
1022
+ const state = internal.find((p) => p.name === '[[PromiseState]]')?.value?.value;
1023
+ const resultRemote = internal.find((p) => p.name === '[[PromiseResult]]')?.value;
1024
+ if (state === 'fulfilled') {
1025
+ return resultRemote ?? { type: 'undefined' };
1026
+ }
1027
+ if (state === 'rejected') {
1028
+ const type = resultRemote?.className || resultRemote?.subtype || 'Error';
1029
+ const description = resultRemote?.description;
1030
+ const message = description?.split('\n')[0] ||
1031
+ (resultRemote && resultRemote.value !== undefined ? String(resultRemote.value) : 'Promise rejected');
1032
+ throw new EvaluateExpressionExceptionError(truncateExpression(expression), type, message, description?.includes('\n') ? description : undefined);
1033
+ }
1034
+ // pending (or internal props unavailable): it cannot settle while paused.
1035
+ throw new EvaluateExpressionPendingPromiseError(truncateExpression(expression));
1036
+ }
1037
+ /**
1038
+ * Attempt an exact by-value capture of an evaluation result, WITHOUT
1039
+ * re-executing the expression: primitives come straight off the
1040
+ * RemoteObject; plain objects/arrays are serialized in place via
1041
+ * Runtime.callFunctionOn(returnByValue). Values whose by-value form is
1042
+ * lossy or impossible (DOM nodes, Date, Map/Set, functions, window - CDP
1043
+ * serializes these to {} or errors) report rawCaptured: false so callers
1044
+ * can fall back to the display-derived reconstruction.
1045
+ */
1046
+ async captureRawValue(remote) {
1047
+ if (!remote || remote.type === 'undefined') {
1048
+ return { rawValue: undefined, rawCaptured: true };
1049
+ }
1050
+ if (remote.type === 'string' || remote.type === 'number' || remote.type === 'boolean') {
1051
+ if ('value' in remote) {
1052
+ return { rawValue: remote.value, rawCaptured: true };
1053
+ }
1054
+ // Unserializable numbers (NaN, Infinity, -0) arrive as
1055
+ // unserializableValue with no `value` - let the caller fall back.
1056
+ return { rawCaptured: false };
1057
+ }
1058
+ if (remote.type === 'object') {
1059
+ if (remote.subtype === 'null') {
1060
+ return { rawValue: null, rawCaptured: true };
1061
+ }
1062
+ // Only plain JSON-like shapes serialize faithfully by value. Anything
1063
+ // with a specialized subtype/class (date, map, set, node, typedarray,
1064
+ // error, ...) collapses to {} under CDP's by-value serialization, which
1065
+ // is WORSE than the display-derived fallback.
1066
+ const className = remote.className;
1067
+ const jsonLike = (remote.subtype === undefined || remote.subtype === 'array') &&
1068
+ (className === undefined || className === 'Object' || className === 'Array');
1069
+ if (jsonLike && remote.objectId) {
1070
+ try {
1071
+ const { Runtime } = this.client;
1072
+ const cf = await Runtime.callFunctionOn({
1073
+ objectId: remote.objectId,
1074
+ functionDeclaration: 'function() { return this; }',
1075
+ returnByValue: true,
1076
+ });
1077
+ if (!cf.exceptionDetails) {
1078
+ return { rawValue: cf.result?.value, rawCaptured: true };
1079
+ }
1080
+ }
1081
+ catch {
1082
+ // Non-serializable (e.g. window: "Object reference chain is too
1083
+ // long") - fall through to the display-derived fallback.
1084
+ }
1085
+ }
1086
+ }
1087
+ return { rawCaptured: false };
1088
+ }
1089
+ /**
1090
+ * Get available variables at a specific source location
1091
+ * Useful for validating logpoint expressions
1092
+ */
1093
+ async getScopeVariablesAtLocation(url, lineNumber) {
1094
+ if (!this.state.connected) {
1095
+ throw new Error('Not connected to debugger');
1096
+ }
1097
+ // Temporarily set a breakpoint to inspect scope
1098
+ const tempBreakpoint = await this.setBreakpoint(url, lineNumber);
1099
+ try {
1100
+ // Wait for the breakpoint to be hit (with timeout)
1101
+ // Note: This requires the code to actually execute
1102
+ // For static analysis, we'd need a different approach
1103
+ // For now, return null to indicate we can't determine scope without execution
1104
+ // This would require the debugger to be paused at that location
1105
+ if (!this.state.paused || !this.state.currentCallFrames) {
1106
+ // Remove the temporary breakpoint
1107
+ await this.removeBreakpoint(tempBreakpoint.breakpointId);
1108
+ return null;
1109
+ }
1110
+ // Get the call frame
1111
+ const callFrame = this.state.currentCallFrames[0];
1112
+ // Extract variable names from all scopes
1113
+ const { Runtime } = this.client;
1114
+ const scopes = [];
1115
+ const allVariables = [];
1116
+ for (const scope of callFrame.scopeChain) {
1117
+ if (scope.type === 'global')
1118
+ continue; // Skip global scope
1119
+ const properties = await Runtime.getProperties({
1120
+ objectId: scope.object.objectId,
1121
+ ownProperties: true,
1122
+ });
1123
+ const variableNames = properties.result
1124
+ .filter((prop) => prop.value && !prop.name.startsWith('[['))
1125
+ .map((prop) => prop.name);
1126
+ scopes.push({
1127
+ type: scope.type,
1128
+ variables: variableNames,
1129
+ });
1130
+ allVariables.push(...variableNames);
1131
+ }
1132
+ // Remove the temporary breakpoint
1133
+ await this.removeBreakpoint(tempBreakpoint.breakpointId);
1134
+ return {
1135
+ variables: [...new Set(allVariables)], // Deduplicate
1136
+ scopes,
1137
+ };
1138
+ }
1139
+ catch (error) {
1140
+ // Clean up the breakpoint if something goes wrong
1141
+ try {
1142
+ await this.removeBreakpoint(tempBreakpoint.breakpointId);
1143
+ }
1144
+ catch (e) {
1145
+ // Ignore cleanup errors
1146
+ }
1147
+ throw error;
1148
+ }
1149
+ }
1150
+ /**
1151
+ * Validate logpoint expressions at a specific location
1152
+ * Sets temp breakpoint, waits for execution, tests expressions
1153
+ *
1154
+ * @param url File URL (e.g., http://localhost:3000/app.js)
1155
+ * @param lineNumber Line number (1-based, will be converted to 0-based for CDP)
1156
+ * @param columnNumber Optional column number (1-based, will be converted to 0-based for CDP)
1157
+ * @param expressions Array of expressions to validate (e.g., ["user.name", "user.id"])
1158
+ * @param timeout Max wait time for execution in milliseconds
1159
+ * @returns Validation results with pass/fail for each expression, plus available variables
1160
+ */
1161
+ async validateLogpointAtActualLocation(url, lineNumber, columnNumber, expressions, timeout = 2000) {
1162
+ if (!this.state.connected) {
1163
+ throw new Error('Not connected to debugger');
1164
+ }
1165
+ // Note: lineNumber and columnNumber are 1-based (user input)
1166
+ // setBreakpoint will convert them to 0-based for CDP
1167
+ const tempBreakpoint = await this.setBreakpoint(url, lineNumber, columnNumber);
1168
+ try {
1169
+ // Wait for execution with timeout
1170
+ await new Promise(resolve => setTimeout(resolve, timeout));
1171
+ // Check if we paused at the breakpoint
1172
+ if (!this.state.paused || !this.state.currentCallFrames) {
1173
+ // Code didn't execute - remove breakpoint and return
1174
+ await this.removeBreakpoint(tempBreakpoint.breakpointId);
1175
+ return {
1176
+ executed: false,
1177
+ allValid: false,
1178
+ results: expressions.map(expr => ({
1179
+ expression: expr,
1180
+ valid: false,
1181
+ error: 'Code has not executed yet - cannot validate without execution'
1182
+ })),
1183
+ };
1184
+ }
1185
+ // Get actual location from CDP (0-based)
1186
+ const actualLocation = tempBreakpoint.location;
1187
+ const actualLineUser = actualLocation.lineNumber + 1; // Convert 0-based → 1-based
1188
+ const actualColumnUser = actualLocation.columnNumber !== undefined
1189
+ ? actualLocation.columnNumber + 1 // Convert 0-based → 1-based
1190
+ : undefined;
1191
+ // Get call frame for evaluation
1192
+ const callFrame = this.state.currentCallFrames[0];
1193
+ // Collect all available variables
1194
+ const { Runtime } = this.client;
1195
+ const availableVariables = [];
1196
+ for (const scope of callFrame.scopeChain) {
1197
+ if (scope.type === 'global')
1198
+ continue; // Skip global
1199
+ const properties = await Runtime.getProperties({
1200
+ objectId: scope.object.objectId,
1201
+ ownProperties: true,
1202
+ });
1203
+ properties.result
1204
+ .filter((prop) => prop.value && !prop.name.startsWith('[['))
1205
+ .forEach((prop) => availableVariables.push(prop.name));
1206
+ }
1207
+ // Evaluate each expression
1208
+ const results = [];
1209
+ for (const expr of expressions) {
1210
+ try {
1211
+ const value = await this.evaluateExpression(expr, callFrame.callFrameId);
1212
+ results.push({
1213
+ expression: expr,
1214
+ valid: true,
1215
+ value,
1216
+ });
1217
+ }
1218
+ catch (error) {
1219
+ results.push({
1220
+ expression: expr,
1221
+ valid: false,
1222
+ error: String(error),
1223
+ });
1224
+ }
1225
+ }
1226
+ // Resume execution
1227
+ await this.resume();
1228
+ // Remove temp breakpoint
1229
+ await this.removeBreakpoint(tempBreakpoint.breakpointId);
1230
+ const allValid = results.every(r => r.valid);
1231
+ return {
1232
+ executed: true,
1233
+ allValid,
1234
+ results,
1235
+ availableVariables: [...new Set(availableVariables)], // Deduplicate
1236
+ actualLocation: {
1237
+ line: actualLineUser,
1238
+ column: actualColumnUser,
1239
+ },
1240
+ };
1241
+ }
1242
+ catch (error) {
1243
+ // Clean up on error
1244
+ try {
1245
+ if (this.state.paused) {
1246
+ await this.resume();
1247
+ }
1248
+ await this.removeBreakpoint(tempBreakpoint.breakpointId);
1249
+ }
1250
+ catch (e) {
1251
+ // Ignore cleanup errors
1252
+ }
1253
+ throw error;
1254
+ }
1255
+ }
1256
+ /**
1257
+ * Search for the best location to set a logpoint by trying nearby lines/columns
1258
+ * Returns suggestions ranked by how many expressions are valid
1259
+ *
1260
+ * @param url File URL
1261
+ * @param lineNumber Desired line number (1-based)
1262
+ * @param columnNumber Desired column number (1-based, optional)
1263
+ * @param expressions Array of expressions to validate
1264
+ * @param searchRadius Number of lines to search in each direction (default: 2)
1265
+ * @param timeout Timeout per location test in milliseconds (default: 1000ms)
1266
+ * @returns Array of suggestions sorted by score (best first), max 3 results
1267
+ */
1268
+ async findBestLogpointLocation(url, lineNumber, columnNumber, expressions, searchRadius = 2, timeout = 1000) {
1269
+ const candidates = [];
1270
+ // Try requested line first, then ±1, ±2, etc.
1271
+ for (let offset = 0; offset <= searchRadius; offset++) {
1272
+ const lines = offset === 0 ? [lineNumber] : [lineNumber - offset, lineNumber + offset];
1273
+ for (const line of lines) {
1274
+ if (line < 1)
1275
+ continue; // Skip negative lines
1276
+ try {
1277
+ const validation = await this.validateLogpointAtActualLocation(url, line, columnNumber, expressions, timeout);
1278
+ if (validation.executed) {
1279
+ const validCount = validation.results.filter(r => r.valid).length;
1280
+ const score = Math.round((validCount / expressions.length) * 100);
1281
+ candidates.push({
1282
+ line: validation.actualLocation?.line || line,
1283
+ column: validation.actualLocation?.column,
1284
+ score,
1285
+ validExpressions: validation.results.filter(r => r.valid).map(r => r.expression),
1286
+ invalidExpressions: validation.results.filter(r => !r.valid).map(r => r.expression),
1287
+ availableVariables: validation.availableVariables || [],
1288
+ reason: score === 100 ? 'All expressions available in scope' :
1289
+ score > 0 ? `${validCount}/${expressions.length} expressions available` :
1290
+ 'No expressions available'
1291
+ });
1292
+ }
1293
+ }
1294
+ catch (e) {
1295
+ // Skip locations that error
1296
+ continue;
1297
+ }
1298
+ }
1299
+ }
1300
+ // Sort by score (highest first), then by proximity to original line
1301
+ candidates.sort((a, b) => {
1302
+ if (b.score !== a.score) {
1303
+ return b.score - a.score; // Higher score first
1304
+ }
1305
+ // If scores equal, prefer closer to original line
1306
+ return Math.abs(a.line - lineNumber) - Math.abs(b.line - lineNumber);
1307
+ });
1308
+ // Return top 3
1309
+ return candidates.slice(0, 3);
1310
+ }
1311
+ /**
1312
+ * Get source code from a file at a specific line range
1313
+ */
1314
+ async getSourceCode(url, startLine, endLine) {
1315
+ if (!this.state.connected) {
1316
+ throw new Error('Not connected to debugger');
1317
+ }
1318
+ const { Debugger } = this.client;
1319
+ // Find the script IDs for this URL (with fallback matching for partial URLs)
1320
+ const match = this.findScriptIds(url);
1321
+ if (!match) {
1322
+ throw new Error(`Script not found for URL: ${url}. Make sure the script has been loaded/parsed.`);
1323
+ }
1324
+ const scriptIds = match.scriptIds;
1325
+ // If multiple scripts, find the one containing the requested line range
1326
+ let scriptId;
1327
+ let fullSource;
1328
+ let totalLines;
1329
+ if (scriptIds.length === 1) {
1330
+ // Only one script - use it
1331
+ scriptId = scriptIds[0];
1332
+ const result = await Debugger.getScriptSource({ scriptId });
1333
+ fullSource = result.scriptSource;
1334
+ totalLines = fullSource.split('\n').length;
1335
+ }
1336
+ else {
1337
+ // Multiple scripts - find the one containing the requested line
1338
+ const targetLine = startLine || 1;
1339
+ let found = false;
1340
+ for (const sid of scriptIds) {
1341
+ const result = await Debugger.getScriptSource({ scriptId: sid });
1342
+ const source = result.scriptSource;
1343
+ const lineCount = source.split('\n').length;
1344
+ // Check if this script contains the requested line
1345
+ if (targetLine <= lineCount) {
1346
+ scriptId = sid;
1347
+ fullSource = source;
1348
+ totalLines = lineCount;
1349
+ found = true;
1350
+ break;
1351
+ }
1352
+ }
1353
+ if (!found) {
1354
+ throw new Error(`Line ${targetLine} not found in any script for URL: ${url}. This URL has ${scriptIds.length} inline scripts.`);
1355
+ }
1356
+ }
1357
+ // Split into lines
1358
+ const lines = fullSource.split('\n');
1359
+ // Determine the range
1360
+ const start = startLine ? Math.max(1, startLine) : 1;
1361
+ const end = endLine ? Math.min(totalLines, endLine) : (startLine ? Math.min(totalLines, startLine + 9) : totalLines);
1362
+ // Extract the requested lines (convert to 0-indexed)
1363
+ const extractedLines = lines.slice(start - 1, end);
1364
+ // Format with line numbers, truncating source map lines
1365
+ const formattedCode = extractedLines
1366
+ .map((line, index) => {
1367
+ const lineNum = start + index;
1368
+ let displayLine = line;
1369
+ // Detect and truncate inline source maps (they can be huge)
1370
+ if (line.startsWith('//# sourceMappingURL=data:')) {
1371
+ const truncateAt = 60;
1372
+ displayLine = line.substring(0, truncateAt) + `... [source map truncated, ${Math.round(line.length / 1024)}kb]`;
1373
+ }
1374
+ return `${String(lineNum).padStart(4, ' ')} | ${displayLine}`;
1375
+ })
1376
+ .join('\n');
1377
+ // Check if source map is available
1378
+ const hasSourceMap = this.sourceMapHandler?.hasSourceMap(url) || false;
1379
+ return {
1380
+ code: formattedCode,
1381
+ totalLines: totalLines,
1382
+ hasSourceMap,
1383
+ };
1384
+ }
1385
+ /**
1386
+ * Inject a clickable console link in the browser
1387
+ */
1388
+ async injectConsoleLink(url, lineNumber, message) {
1389
+ if (!this.state.connected) {
1390
+ return;
1391
+ }
1392
+ const { Runtime } = this.client;
1393
+ const consoleExpression = `
1394
+ console.log(
1395
+ '${message} %c${url}:${lineNumber}%c',
1396
+ 'color: #0066cc; text-decoration: underline; cursor: pointer; font-weight: bold',
1397
+ );
1398
+ `;
1399
+ try {
1400
+ await Runtime.evaluate({ expression: consoleExpression });
1401
+ }
1402
+ catch (error) {
1403
+ // Ignore errors if console injection fails
1404
+ }
1405
+ }
1406
+ /**
1407
+ * Get detailed information about current pause state
1408
+ */
1409
+ getPausedInfo() {
1410
+ if (!this.state.paused) {
1411
+ return { paused: false };
1412
+ }
1413
+ const callStack = this.getCallStack();
1414
+ const location = callStack && callStack.length > 0 ? {
1415
+ url: callStack[0].url,
1416
+ lineNumber: callStack[0].location.lineNumber,
1417
+ columnNumber: callStack[0].location.columnNumber,
1418
+ functionName: callStack[0].functionName,
1419
+ } : undefined;
1420
+ return {
1421
+ paused: true,
1422
+ location,
1423
+ callStack,
1424
+ };
1425
+ }
1426
+ /**
1427
+ * Wait for debugger to pause (for race detection)
1428
+ * Returns a promise that resolves when debugger pauses, or rejects on timeout
1429
+ */
1430
+ waitForPause(timeoutMs = 30000) {
1431
+ debugLog('cdp-manager', `waitForPause called, already paused: ${this.state.paused}, timeout: ${timeoutMs}ms`);
1432
+ if (this.state.paused) {
1433
+ debugLog('cdp-manager', 'waitForPause: already paused, resolving immediately');
1434
+ return Promise.resolve();
1435
+ }
1436
+ return new Promise((resolve, reject) => {
1437
+ let resolved = false;
1438
+ const wrappedResolve = () => {
1439
+ if (resolved)
1440
+ return;
1441
+ resolved = true;
1442
+ debugLog('cdp-manager', 'waitForPause: wrappedResolve called, resolving promise');
1443
+ clearTimeout(timeout);
1444
+ resolve();
1445
+ };
1446
+ const timeout = setTimeout(() => {
1447
+ if (resolved)
1448
+ return;
1449
+ resolved = true;
1450
+ debugLog('cdp-manager', `waitForPause: timeout after ${timeoutMs}ms, state.paused=${this.state.paused}`);
1451
+ // Remove resolver from list
1452
+ const index = this.pauseResolvers.indexOf(wrappedResolve);
1453
+ if (index > -1) {
1454
+ this.pauseResolvers.splice(index, 1);
1455
+ }
1456
+ reject(new Error('Timeout waiting for pause'));
1457
+ }, timeoutMs);
1458
+ this.pauseResolvers.push(wrappedResolve);
1459
+ debugLog('cdp-manager', `waitForPause: resolver added, total resolvers: ${this.pauseResolvers.length}`);
1460
+ // Re-check after adding resolver to handle race condition
1461
+ // where pause happened between initial check and push
1462
+ if (this.state.paused) {
1463
+ debugLog('cdp-manager', 'waitForPause: paused detected after adding resolver, resolving');
1464
+ wrappedResolve();
1465
+ }
1466
+ });
1467
+ }
1468
+ /**
1469
+ * Format a CDP RemoteObject value for display
1470
+ * @param value The CDP RemoteObject to format
1471
+ * @param expandObjects Whether to expand object/array contents (default: false for backward compatibility)
1472
+ * @param maxDepth Maximum depth for object expansion (default: 2)
1473
+ * @param currentDepth Current recursion depth (internal use)
1474
+ */
1475
+ async formatValue(value, expandObjects = false, maxDepth = 2, currentDepth = 0) {
1476
+ if (value.type === 'undefined')
1477
+ return 'undefined';
1478
+ if (value.type === 'string')
1479
+ return `"${value.value}"`;
1480
+ if (value.type === 'number' || value.type === 'boolean')
1481
+ return String(value.value);
1482
+ if (value.type === 'object') {
1483
+ if (value.subtype === 'null')
1484
+ return 'null';
1485
+ // Detect and handle DOM nodes specially (never expand)
1486
+ const className = value.className || '';
1487
+ if (className.startsWith('HTML') || className.includes('Element') || value.subtype === 'node') {
1488
+ return `[${className || 'DOMNode'}]`;
1489
+ }
1490
+ // If expansion is disabled or we've hit max depth, return description
1491
+ if (!expandObjects || currentDepth >= maxDepth) {
1492
+ if (value.subtype === 'array')
1493
+ return `Array(${value.description})`;
1494
+ return value.description || value.className || 'Object';
1495
+ }
1496
+ // Expand object/array contents with smart size limits
1497
+ if (value.objectId) {
1498
+ try {
1499
+ const { Runtime } = this.client;
1500
+ const properties = await Runtime.getProperties({
1501
+ objectId: value.objectId,
1502
+ ownProperties: true,
1503
+ });
1504
+ if (value.subtype === 'array') {
1505
+ // Smart array handling based on size
1506
+ const numericProps = properties.result.filter((p) => !isNaN(parseInt(p.name, 10)));
1507
+ const arrayLength = numericProps.length;
1508
+ // For large arrays, truncate to first 10 elements
1509
+ if (arrayLength > 20) {
1510
+ const arrayElements = [];
1511
+ let itemsShown = 0;
1512
+ for (const prop of numericProps.slice(0, 10)) {
1513
+ const index = parseInt(prop.name, 10);
1514
+ if (prop.value) {
1515
+ arrayElements[index] = await this.formatValue(prop.value, expandObjects, maxDepth, currentDepth + 1);
1516
+ itemsShown++;
1517
+ }
1518
+ }
1519
+ // Add truncation indicator
1520
+ arrayElements.push(`... ${arrayLength - itemsShown} more items (use evaluateExpression to inspect)`);
1521
+ return arrayElements;
1522
+ }
1523
+ // Small arrays - show all elements
1524
+ const arrayElements = [];
1525
+ for (const prop of properties.result) {
1526
+ const index = parseInt(prop.name, 10);
1527
+ if (!isNaN(index) && prop.value) {
1528
+ arrayElements[index] = await this.formatValue(prop.value, expandObjects, maxDepth, currentDepth + 1);
1529
+ }
1530
+ }
1531
+ return arrayElements;
1532
+ }
1533
+ else {
1534
+ // Smart object handling based on property count
1535
+ const validProps = properties.result.filter((p) => p.value && !p.name.startsWith('[['));
1536
+ const propCount = validProps.length;
1537
+ // For very large objects, show summary with first few keys
1538
+ if (propCount > 50) {
1539
+ const firstKeys = validProps.slice(0, 5).map((p) => p.name);
1540
+ return `[Object with ${propCount} properties] {${firstKeys.join(', ')}, ...} - use evaluateExpression to inspect`;
1541
+ }
1542
+ // For moderately large objects (10-50 props), limit depth
1543
+ if (propCount > 10 && currentDepth > 0) {
1544
+ const firstKeys = validProps.slice(0, 10).map((p) => p.name);
1545
+ return `[Object with ${propCount} properties] {${firstKeys.join(', ')}${propCount > 10 ? ', ...' : ''}}`;
1546
+ }
1547
+ // Small objects - expand normally
1548
+ const objectProps = {};
1549
+ for (const prop of validProps) {
1550
+ objectProps[prop.name] = await this.formatValue(prop.value, expandObjects, maxDepth, currentDepth + 1);
1551
+ }
1552
+ return objectProps;
1553
+ }
1554
+ }
1555
+ catch (error) {
1556
+ // If expansion fails, fall back to description
1557
+ if (value.subtype === 'array')
1558
+ return `Array(${value.description})`;
1559
+ return value.description || value.className || 'Object';
1560
+ }
1561
+ }
1562
+ // No objectId, can't expand
1563
+ if (value.subtype === 'array')
1564
+ return `Array(${value.description})`;
1565
+ return value.description || value.className || 'Object';
1566
+ }
1567
+ if (value.type === 'function') {
1568
+ // Extract just the function name/signature, not the full source
1569
+ const desc = value.description || '';
1570
+ const match = desc.match(/^(function\s+\w+|async\s+function\s+\w+|\w+)\s*\(/);
1571
+ if (match) {
1572
+ return `[Function: ${match[1]}]`;
1573
+ }
1574
+ return '[Function]';
1575
+ }
1576
+ return String(value.value);
1577
+ }
1578
+ /**
1579
+ * Expand a CDP RemoteObject by its objectId
1580
+ * This is used to get full object details for console messages
1581
+ * @param objectId - The CDP object ID to expand
1582
+ * @param maxDepth - Maximum depth for nested object expansion (default: 2)
1583
+ */
1584
+ async expandObjectById(objectId, maxDepth = 2) {
1585
+ if (!this.state.connected || !this.client) {
1586
+ throw new Error('Not connected to debugger');
1587
+ }
1588
+ const { Runtime } = this.client;
1589
+ // Get the object properties
1590
+ const properties = await Runtime.getProperties({
1591
+ objectId,
1592
+ ownProperties: true,
1593
+ });
1594
+ // Build the expanded object
1595
+ const result = {};
1596
+ const validProps = properties.result.filter((p) => p.value && !p.name.startsWith('[['));
1597
+ for (const prop of validProps.slice(0, 20)) { // Limit to 20 properties for console output
1598
+ result[prop.name] = await this.formatValue(prop.value, true, maxDepth, 0);
1599
+ }
1600
+ if (validProps.length > 20) {
1601
+ result['...'] = `(${validProps.length - 20} more properties)`;
1602
+ }
1603
+ return result;
1604
+ }
1605
+ /**
1606
+ * Get all loaded scripts
1607
+ */
1608
+ getAllScripts() {
1609
+ return Array.from(this.scriptIdToUrl.entries()).map(([scriptId, url]) => ({
1610
+ scriptId,
1611
+ url,
1612
+ }));
1613
+ }
1614
+ /**
1615
+ * Search within a specific script using regex
1616
+ */
1617
+ async searchInScript(scriptId, pattern, caseSensitive = false, isRegex = true) {
1618
+ if (!this.state.connected) {
1619
+ throw new Error('Not connected to debugger');
1620
+ }
1621
+ const { Debugger } = this.client;
1622
+ try {
1623
+ const result = await Debugger.searchInContent({
1624
+ scriptId,
1625
+ query: pattern,
1626
+ caseSensitive,
1627
+ isRegex,
1628
+ });
1629
+ return (result.result || []).map((match) => ({
1630
+ lineNumber: match.lineNumber,
1631
+ lineContent: match.lineContent,
1632
+ }));
1633
+ }
1634
+ catch (error) {
1635
+ // Script might not support search or other error
1636
+ return [];
1637
+ }
1638
+ }
1639
+ /**
1640
+ * Get a specific line from a script by scriptId and line number
1641
+ * Returns the full line content (useful for getting full webpack eval lines)
1642
+ */
1643
+ async getScriptLine(scriptId, lineNumber) {
1644
+ if (!this.state.connected) {
1645
+ return null;
1646
+ }
1647
+ const { Debugger } = this.client;
1648
+ try {
1649
+ const result = await Debugger.getScriptSource({ scriptId });
1650
+ const lines = result.scriptSource.split('\n');
1651
+ // lineNumber is 0-indexed from searchInContent
1652
+ if (lineNumber >= 0 && lineNumber < lines.length) {
1653
+ return lines[lineNumber];
1654
+ }
1655
+ return null;
1656
+ }
1657
+ catch (error) {
1658
+ return null;
1659
+ }
1660
+ }
1661
+ // ============================================
1662
+ // DOMDebugger Methods (DOM/Event/XHR Breakpoints)
1663
+ // ============================================
1664
+ /**
1665
+ * Generate a unique ID for advanced breakpoints
1666
+ */
1667
+ generateAdvancedBpId(prefix) {
1668
+ return `${prefix}-${++this.advancedBpCounter}`;
1669
+ }
1670
+ /**
1671
+ * Resolve a CSS selector to a CDP nodeId
1672
+ * @param selector - CSS selector to resolve
1673
+ * @returns nodeId for the element
1674
+ */
1675
+ async resolveSelector(selector) {
1676
+ if (!this.state.connected) {
1677
+ throw new Error('Not connected to debugger');
1678
+ }
1679
+ const { DOM } = this.client;
1680
+ // Get document root
1681
+ const { root } = await DOM.getDocument();
1682
+ // Query selector to get nodeId
1683
+ const { nodeId } = await DOM.querySelector({
1684
+ nodeId: root.nodeId,
1685
+ selector,
1686
+ });
1687
+ if (!nodeId || nodeId === 0) {
1688
+ throw new Error(`Element not found: ${selector}`);
1689
+ }
1690
+ return nodeId;
1691
+ }
1692
+ /**
1693
+ * Set a DOM breakpoint on a node
1694
+ * Pauses execution when the specified DOM mutation occurs
1695
+ * @param nodeId - The node ID from resolveSelector
1696
+ * @param type - Type of DOM change to break on
1697
+ * @param selector - Original CSS selector (for reference/display)
1698
+ */
1699
+ async setDOMBreakpoint(nodeId, type, selector) {
1700
+ if (!this.state.connected) {
1701
+ throw new Error('Not connected to debugger');
1702
+ }
1703
+ const { DOMDebugger } = this.client;
1704
+ await DOMDebugger.setDOMBreakpoint({ nodeId, type });
1705
+ const id = this.generateAdvancedBpId('dom-bp');
1706
+ const info = {
1707
+ breakpointId: id,
1708
+ type: 'dom',
1709
+ nodeId,
1710
+ domBreakpointType: type,
1711
+ selector,
1712
+ };
1713
+ this.domBreakpoints.set(id, info);
1714
+ return info;
1715
+ }
1716
+ /**
1717
+ * Remove a DOM breakpoint by its ID
1718
+ */
1719
+ async removeDOMBreakpoint(breakpointId) {
1720
+ const bp = this.domBreakpoints.get(breakpointId);
1721
+ if (!bp) {
1722
+ throw new Error(`DOM breakpoint not found: ${breakpointId}`);
1723
+ }
1724
+ if (!this.state.connected) {
1725
+ throw new Error('Not connected to debugger');
1726
+ }
1727
+ const { DOMDebugger } = this.client;
1728
+ await DOMDebugger.removeDOMBreakpoint({
1729
+ nodeId: bp.nodeId,
1730
+ type: bp.domBreakpointType,
1731
+ });
1732
+ this.domBreakpoints.delete(breakpointId);
1733
+ }
1734
+ /**
1735
+ * Get all DOM breakpoints
1736
+ */
1737
+ getDOMBreakpoints() {
1738
+ return Array.from(this.domBreakpoints.values());
1739
+ }
1740
+ /**
1741
+ * Set an event listener breakpoint
1742
+ * Pauses execution when the specified event fires
1743
+ * @param eventName - DOM event name (e.g., 'click', 'submit', 'input')
1744
+ * @param targetName - Optional EventTarget interface filter (e.g., 'HTMLInputElement')
1745
+ */
1746
+ async setEventListenerBreakpoint(eventName, targetName) {
1747
+ if (!this.state.connected) {
1748
+ throw new Error('Not connected to debugger');
1749
+ }
1750
+ const { DOMDebugger } = this.client;
1751
+ await DOMDebugger.setEventListenerBreakpoint({ eventName, targetName });
1752
+ const id = this.generateAdvancedBpId('event-bp');
1753
+ const info = {
1754
+ breakpointId: id,
1755
+ type: 'event',
1756
+ eventName,
1757
+ targetName,
1758
+ };
1759
+ this.eventBreakpoints.set(id, info);
1760
+ return info;
1761
+ }
1762
+ /**
1763
+ * Remove an event listener breakpoint by its ID
1764
+ */
1765
+ async removeEventListenerBreakpoint(breakpointId) {
1766
+ const bp = this.eventBreakpoints.get(breakpointId);
1767
+ if (!bp) {
1768
+ throw new Error(`Event listener breakpoint not found: ${breakpointId}`);
1769
+ }
1770
+ if (!this.state.connected) {
1771
+ throw new Error('Not connected to debugger');
1772
+ }
1773
+ const { DOMDebugger } = this.client;
1774
+ await DOMDebugger.removeEventListenerBreakpoint({
1775
+ eventName: bp.eventName,
1776
+ targetName: bp.targetName,
1777
+ });
1778
+ this.eventBreakpoints.delete(breakpointId);
1779
+ }
1780
+ /**
1781
+ * Get all event listener breakpoints
1782
+ */
1783
+ getEventListenerBreakpoints() {
1784
+ return Array.from(this.eventBreakpoints.values());
1785
+ }
1786
+ /**
1787
+ * Set an XHR/Fetch breakpoint
1788
+ * Pauses execution when an XHR or Fetch request URL contains the pattern
1789
+ * @param urlPattern - URL substring to match
1790
+ */
1791
+ async setXHRBreakpoint(urlPattern) {
1792
+ if (!this.state.connected) {
1793
+ throw new Error('Not connected to debugger');
1794
+ }
1795
+ const { DOMDebugger } = this.client;
1796
+ await DOMDebugger.setXHRBreakpoint({ url: urlPattern });
1797
+ const id = this.generateAdvancedBpId('xhr-bp');
1798
+ const info = {
1799
+ breakpointId: id,
1800
+ type: 'xhr',
1801
+ urlPattern,
1802
+ };
1803
+ this.xhrBreakpoints.set(id, info);
1804
+ return info;
1805
+ }
1806
+ /**
1807
+ * Remove an XHR/Fetch breakpoint by its ID
1808
+ */
1809
+ async removeXHRBreakpoint(breakpointId) {
1810
+ const bp = this.xhrBreakpoints.get(breakpointId);
1811
+ if (!bp) {
1812
+ throw new Error(`XHR breakpoint not found: ${breakpointId}`);
1813
+ }
1814
+ if (!this.state.connected) {
1815
+ throw new Error('Not connected to debugger');
1816
+ }
1817
+ const { DOMDebugger } = this.client;
1818
+ await DOMDebugger.removeXHRBreakpoint({ url: bp.urlPattern });
1819
+ this.xhrBreakpoints.delete(breakpointId);
1820
+ }
1821
+ /**
1822
+ * Get all XHR/Fetch breakpoints
1823
+ */
1824
+ getXHRBreakpoints() {
1825
+ return Array.from(this.xhrBreakpoints.values());
1826
+ }
1827
+ }
1828
+ //# sourceMappingURL=cdp-manager.js.map