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,701 @@
1
+ /**
2
+ * Command Recorder for capturing and replaying tool invocations
3
+ *
4
+ * Records all tool calls automatically and allows creating named sequences
5
+ * by selecting specific command indices from the history.
6
+ */
7
+ import { promises as fs, existsSync } from 'fs';
8
+ import { walkSequenceFiles } from './helpers/sequence-tree.js';
9
+ import { ServerFileWatcher } from './server-watcher.js';
10
+ import { join, dirname } from 'path';
11
+ import { debugLog, isHistoryLogEnabled, logToHistoryFile } from './debug-logger.js';
12
+ import { sanitizeReference } from './reference-validator.js';
13
+ import { getOutputPath } from './helpers/paths.js';
14
+ import { atomicWriteFile } from './atomic-write.js';
15
+ import { getIssueSequencesDir, getIssuesBySequenceFile } from './issue-tracker.js';
16
+ export class CommandRecorder {
17
+ history = [];
18
+ sequences = new Map();
19
+ /** Bumped per created sequence: two created in the same millisecond used to
20
+ * share an id, and the second silently evicted the first from the map. */
21
+ sequenceSeq = 0;
22
+ commandCounter = 0;
23
+ maxHistorySize = 1000; // Keep last 1000 commands
24
+ activeSequence = null;
25
+ historyViewedWhilePaused = false;
26
+ /**
27
+ * Where each in-memory sequence came from, for the ones that came from disk.
28
+ * A sequence built from history has no entry and is never touched by the
29
+ * watcher - it exists nowhere else, so there is nothing to reload it from.
30
+ */
31
+ sequenceSources = new Map();
32
+ sequenceWatcher = null;
33
+ watchedDirs = new Set();
34
+ /**
35
+ * Get the sequences directory for a specific scope
36
+ */
37
+ getSequencesDir(global = false) {
38
+ return getOutputPath('sequences', { global });
39
+ }
40
+ /**
41
+ * Watch the sequences directories and reload edited files, the way a managed
42
+ * dev server is restarted when its sources change.
43
+ *
44
+ * Memory used to shadow disk for the lifetime of the session: a sequence
45
+ * loaded once kept running its original version however many times you edited
46
+ * the file, with nothing in the run output to say so. `runAll` reloads the
47
+ * whole tree first, so the same sequence behaved differently depending on how
48
+ * it was invoked - which is how the stale copy stayed invisible.
49
+ *
50
+ * Idempotent, and safe to call before the directories exist: it attaches to
51
+ * whichever are present and callers re-invoke it after a save or load.
52
+ */
53
+ startSequenceWatch() {
54
+ // The sequences directories, plus wherever loaded files actually live - a
55
+ // sequence can be loaded by absolute path, or out of an issue's own folder.
56
+ const dirs = [
57
+ this.getSequencesDir(false),
58
+ this.getSequencesDir(true),
59
+ ...[...this.sequenceSources.values()].map(s => dirname(s.path)),
60
+ ].filter((dir, i, all) => all.indexOf(dir) === i && existsSync(dir));
61
+ if (dirs.length === 0)
62
+ return;
63
+ // Nothing new to cover: leave the running watcher alone.
64
+ if (this.sequenceWatcher && dirs.every(dir => this.watchedDirs.has(dir)))
65
+ return;
66
+ this.sequenceWatcher?.stop();
67
+ this.watchedDirs = new Set(dirs);
68
+ this.sequenceWatcher = new ServerFileWatcher({
69
+ paths: dirs,
70
+ // Sequences live UNDER .cdp-tools, which the default exclude list drops -
71
+ // taking every sequence with it.
72
+ excludeDirNames: ['node_modules', '.git'],
73
+ onChange: () => { void this.reloadChangedSequences(); },
74
+ });
75
+ this.sequenceWatcher.start();
76
+ }
77
+ /** Stop watching (tests, shutdown). */
78
+ stopSequenceWatch() {
79
+ this.sequenceWatcher?.stop();
80
+ this.sequenceWatcher = null;
81
+ this.watchedDirs.clear();
82
+ }
83
+ /**
84
+ * Re-read every disk-backed sequence whose file is newer than the copy in
85
+ * memory. Returns the names actually reloaded.
86
+ *
87
+ * A file that has gone missing or will not parse leaves the in-memory copy
88
+ * alone: a watcher fires mid-write as readily as after one, and dropping a
89
+ * good sequence because it was caught half-written would be worse than the
90
+ * staleness this exists to fix.
91
+ */
92
+ async reloadChangedSequences(ids) {
93
+ const reloaded = [];
94
+ const scope = ids
95
+ ? [...this.sequenceSources].filter(([id]) => ids.includes(id))
96
+ : [...this.sequenceSources];
97
+ for (const [id, source] of scope) {
98
+ let mtimeMs;
99
+ try {
100
+ mtimeMs = (await fs.stat(source.path)).mtimeMs;
101
+ }
102
+ catch {
103
+ continue;
104
+ }
105
+ if (mtimeMs <= source.mtimeMs)
106
+ continue;
107
+ let parsed;
108
+ try {
109
+ parsed = await this.parseSequenceFile(source.path);
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ this.sequences.delete(id);
115
+ this.sequenceSources.delete(id);
116
+ this.registerLoadedSequence(parsed);
117
+ this.sequenceSources.set(parsed.id, { path: source.path, mtimeMs });
118
+ reloaded.push(parsed.name);
119
+ await debugLog('command-recorder', `Reloaded edited sequence "${parsed.name}" from ${source.path}`);
120
+ }
121
+ return reloaded;
122
+ }
123
+ /**
124
+ * The current copy of a sequence, re-read first if its file has changed
125
+ * since it was loaded.
126
+ *
127
+ * The watcher debounces (400ms), and an edit followed immediately by a run
128
+ * is the normal rhythm - so a run does not wait for the watcher to catch up,
129
+ * it asks. One stat on the way past.
130
+ */
131
+ async getFreshSequence(id) {
132
+ if (this.sequenceSources.has(id)) {
133
+ const [reloadedName] = await this.reloadChangedSequences([id]);
134
+ if (reloadedName) {
135
+ return [...this.sequences.values()].find(s => s.name === reloadedName);
136
+ }
137
+ }
138
+ return this.sequences.get(id);
139
+ }
140
+ /** Remember which file a sequence came from, so the watcher can refresh it. */
141
+ async trackSequenceSource(sequence, filepath) {
142
+ try {
143
+ this.sequenceSources.set(sequence.id, { path: filepath, mtimeMs: (await fs.stat(filepath)).mtimeMs });
144
+ }
145
+ catch {
146
+ // Unreadable stat just means this one is not watched.
147
+ }
148
+ this.startSequenceWatch();
149
+ }
150
+ /**
151
+ * Set active sequence state (for step-through)
152
+ */
153
+ setActiveSequence(state) {
154
+ this.activeSequence = state;
155
+ // Reset history viewed flag when sequence state changes
156
+ this.historyViewedWhilePaused = false;
157
+ }
158
+ /**
159
+ * Mark that history was viewed while paused (enables insert)
160
+ */
161
+ markHistoryViewed() {
162
+ if (this.activeSequence) {
163
+ this.historyViewedWhilePaused = true;
164
+ }
165
+ }
166
+ /**
167
+ * Reset history viewed flag (called when other actions are taken)
168
+ */
169
+ resetHistoryViewed() {
170
+ this.historyViewedWhilePaused = false;
171
+ }
172
+ /**
173
+ * Check if history was viewed while paused
174
+ */
175
+ wasHistoryViewed() {
176
+ return this.historyViewedWhilePaused;
177
+ }
178
+ /**
179
+ * Get active sequence state
180
+ */
181
+ getActiveSequence() {
182
+ return this.activeSequence;
183
+ }
184
+ /**
185
+ * Update current step in active sequence
186
+ */
187
+ updateActiveSequenceStep(step) {
188
+ if (this.activeSequence) {
189
+ this.activeSequence.currentStep = step;
190
+ }
191
+ }
192
+ /**
193
+ * Get commands recorded since sequence was paused
194
+ */
195
+ getCommandsSincePause() {
196
+ if (!this.activeSequence)
197
+ return [];
198
+ return this.history.filter(cmd => cmd.index > this.activeSequence.historyIndexAtPause);
199
+ }
200
+ /**
201
+ * Get current history index (for tracking pause point)
202
+ */
203
+ getCurrentHistoryIndex() {
204
+ return this.history.length > 0 ? this.history[this.history.length - 1].index : -1;
205
+ }
206
+ /**
207
+ * Record a command (always-on, automatic)
208
+ */
209
+ async recordCommand(tool, params, options) {
210
+ // Reset history viewed flag when any command is recorded
211
+ // (user must view history again before inserting)
212
+ this.historyViewedWhilePaused = false;
213
+ const paramsClone = JSON.parse(JSON.stringify(params));
214
+ // Keep the connectionReason the call was actually made with (bug-018).
215
+ // It used to be deleted here "to make sequences reusable", which meant a
216
+ // recording that drove two browsers could not be replayed against two
217
+ // browsers - every step fell back to the run-level connection and the
218
+ // sequence silently collapsed into one browser. Reusability is preserved at
219
+ // `create` time instead: a sequence whose steps all share one connection has
220
+ // it hoisted back off the steps (see normalizeStepConnections), so a
221
+ // run-level connectionReason still overrides. Only genuinely
222
+ // multi-connection sequences keep it per-step.
223
+ //
224
+ // Sanitized so a recorded reference always matches the stored connection
225
+ // reference ("Duo Owner Console" -> "duo-owner-console"), which is what
226
+ // connection lookup and the launchChrome `reference` below use.
227
+ if (typeof paramsClone.connectionReason === 'string') {
228
+ paramsClone.connectionReason = sanitizeReference(paramsClone.connectionReason);
229
+ }
230
+ // Sanitize 'reference' param for tools that create connections
231
+ // This ensures recorded sequences use the same reference format as the actual connection
232
+ if (paramsClone.reference && ['launchChrome', 'connectDebugger'].includes(tool)) {
233
+ paramsClone.reference = sanitizeReference(paramsClone.reference);
234
+ }
235
+ // Sanitize 'newReference' for tab rename operations
236
+ if (paramsClone.newReference && tool === 'tab' && paramsClone.action === 'rename') {
237
+ paramsClone.newReference = sanitizeReference(paramsClone.newReference);
238
+ }
239
+ const command = {
240
+ index: this.commandCounter++,
241
+ timestamp: Date.now(),
242
+ tool,
243
+ params: paramsClone,
244
+ ...(options?.delay !== undefined && { delay: options.delay }),
245
+ ...(options?.comment && { comment: options.comment }),
246
+ };
247
+ this.history.push(command);
248
+ // Trim history if it exceeds max size
249
+ if (this.history.length > this.maxHistorySize) {
250
+ this.history.shift();
251
+ }
252
+ await debugLog('command-recorder', `Recorded #${command.index}: ${tool}`);
253
+ // Write to history.log in replay-compatible format when history logging is enabled
254
+ if (isHistoryLogEnabled()) {
255
+ const historyEntry = { tool, params: paramsClone };
256
+ await logToHistoryFile(JSON.stringify(historyEntry));
257
+ }
258
+ }
259
+ /**
260
+ * Get command history (most recent first)
261
+ */
262
+ getHistory(limit = 50) {
263
+ return [...this.history].reverse().slice(0, limit);
264
+ }
265
+ /**
266
+ * Get a specific command by index
267
+ */
268
+ getCommand(index) {
269
+ return this.history.find(cmd => cmd.index === index);
270
+ }
271
+ /**
272
+ * Create a sequence from command indices
273
+ */
274
+ async createSequence(name, commandIndices, options) {
275
+ // Validate all indices exist and get commands
276
+ const commands = [];
277
+ for (const idx of commandIndices) {
278
+ const cmd = this.getCommand(idx);
279
+ if (!cmd) {
280
+ await debugLog('command-recorder', `Invalid command index: ${idx}`);
281
+ return null;
282
+ }
283
+ // Strip index and timestamp for the sequence, but keep delay and comment.
284
+ // params is DEEP-CLONED: the sequence is edited after creation (hoisting a
285
+ // uniform connectionReason off the steps, rebasing URLs), and sharing the
286
+ // object with the history entry made those edits silently rewrite history.
287
+ commands.push({
288
+ tool: cmd.tool,
289
+ params: JSON.parse(JSON.stringify(cmd.params)),
290
+ ...(cmd.delay !== undefined && { delay: cmd.delay }),
291
+ ...(cmd.comment && { comment: cmd.comment }),
292
+ });
293
+ }
294
+ // Extract startUrl from commands if not explicitly provided
295
+ let startUrl = options?.startUrl;
296
+ if (!startUrl) {
297
+ // Look for the first navigate goto action
298
+ for (const cmd of commands) {
299
+ if (cmd.tool === 'navigate' && cmd.params.action === 'goto' && cmd.params.url) {
300
+ startUrl = cmd.params.url;
301
+ break;
302
+ }
303
+ }
304
+ }
305
+ const sequence = {
306
+ id: `seq-${Date.now()}-${++this.sequenceSeq}`,
307
+ name,
308
+ ...(options?.description && { description: options.description }),
309
+ ...(options?.expectedOutcome && { expectedOutcome: options.expectedOutcome }),
310
+ ...(startUrl && { startUrl }),
311
+ commands,
312
+ createdAt: Date.now(),
313
+ };
314
+ // Validate before anything destructive happens (see options.validate).
315
+ if (options?.validate && !options.validate(sequence)) {
316
+ await debugLog('command-recorder', `Rejected sequence "${name}" (validation failed) - existing sequence left intact`);
317
+ return null;
318
+ }
319
+ // Dedupe by name so re-creating a sequence replaces the old in-memory copy
320
+ // rather than leaving a stale one that loadSequence({name}) would resolve (#75).
321
+ this.removeSequenceByName(name);
322
+ this.sequences.set(sequence.id, sequence);
323
+ await debugLog('command-recorder', `Created sequence "${name}" with ${commands.length} commands${startUrl ? `, startUrl: ${startUrl}` : ''}`);
324
+ return sequence;
325
+ }
326
+ /**
327
+ * Create a sequence directly from commands (not from history)
328
+ */
329
+ async createSequenceFromCommands(name, commands, options) {
330
+ // Extract startUrl from commands if not explicitly provided
331
+ let startUrl = options?.startUrl;
332
+ if (!startUrl) {
333
+ for (const cmd of commands) {
334
+ if (cmd.tool === 'navigate' && cmd.params.action === 'goto' && cmd.params.url) {
335
+ startUrl = cmd.params.url;
336
+ break;
337
+ }
338
+ }
339
+ }
340
+ const sequence = {
341
+ id: `seq-${Date.now()}-${++this.sequenceSeq}`,
342
+ name,
343
+ ...(options?.description && { description: options.description }),
344
+ ...(options?.expectedOutcome && { expectedOutcome: options.expectedOutcome }),
345
+ ...(startUrl && { startUrl }),
346
+ commands,
347
+ createdAt: Date.now(),
348
+ };
349
+ // Dedupe by name (see createSequence / #75).
350
+ this.removeSequenceByName(name);
351
+ this.sequences.set(sequence.id, sequence);
352
+ await debugLog('command-recorder', `Created sequence "${name}" from commands with ${commands.length} commands${startUrl ? `, startUrl: ${startUrl}` : ''}`);
353
+ return sequence;
354
+ }
355
+ /**
356
+ * Check if a sequence with the given name exists
357
+ */
358
+ sequenceNameExists(name) {
359
+ for (const seq of this.sequences.values()) {
360
+ if (seq.name === name)
361
+ return true;
362
+ }
363
+ return false;
364
+ }
365
+ /**
366
+ * List all saved sequences
367
+ */
368
+ listSequences() {
369
+ return Array.from(this.sequences.values());
370
+ }
371
+ /**
372
+ * Get a specific sequence by ID
373
+ */
374
+ getSequence(sequenceId) {
375
+ return this.sequences.get(sequenceId);
376
+ }
377
+ /**
378
+ * Delete a sequence
379
+ */
380
+ deleteSequence(sequenceId) {
381
+ return this.sequences.delete(sequenceId);
382
+ }
383
+ /**
384
+ * Remove ALL in-memory sequences with the given name. Used on create/reload so a
385
+ * name maps to exactly one sequence — otherwise `loadSequence({name})` resolves the
386
+ * oldest insertion-order match and re-created sequences are silently ignored (#75).
387
+ */
388
+ removeSequenceByName(name) {
389
+ for (const [id, seq] of this.sequences.entries()) {
390
+ if (seq.name === name) {
391
+ this.sequences.delete(id);
392
+ }
393
+ }
394
+ }
395
+ /**
396
+ * Clear all sequences
397
+ */
398
+ async clearAllSequences() {
399
+ this.sequences.clear();
400
+ await debugLog('command-recorder', 'All sequences cleared');
401
+ }
402
+ /**
403
+ * Clear command history
404
+ */
405
+ async clearHistory() {
406
+ this.history = [];
407
+ this.commandCounter = 0;
408
+ await debugLog('command-recorder', 'Command history cleared');
409
+ }
410
+ /**
411
+ * Get statistics
412
+ */
413
+ getStats() {
414
+ return {
415
+ historyCount: this.history.length,
416
+ sequenceCount: this.sequences.size,
417
+ oldestCommandIndex: this.history.length > 0 ? this.history[0].index : null,
418
+ newestCommandIndex: this.history.length > 0 ? this.history[this.history.length - 1].index : null,
419
+ };
420
+ }
421
+ /**
422
+ * Save a sequence to disk
423
+ * @param sequenceId - ID of the sequence to save
424
+ * @param global - If true, save to global ~/.cdp-tools/sequences/, otherwise working directory
425
+ */
426
+ async saveSequenceToDisk(sequenceId, global = false, overwrite = false) {
427
+ const sequence = this.getSequence(sequenceId);
428
+ if (!sequence) {
429
+ await debugLog('command-recorder', `Sequence ${sequenceId} not found`);
430
+ return null;
431
+ }
432
+ const targetDir = this.getSequencesDir(global);
433
+ try {
434
+ // Sanitize filename - use name directly
435
+ // Note: atomicWriteFile handles directory creation
436
+ const safeFilename = sequence.name.replace(/[^a-z0-9-_]/gi, '-').toLowerCase();
437
+ // Save back into the folder the sequence came from. Writing to the root
438
+ // instead forks it: the foldered original goes stale while a second copy
439
+ // appears at top level, which runAll then runs twice.
440
+ let filename = `${safeFilename}.json`;
441
+ const existing = (await this.listSavedSequencesOnDisk())
442
+ .find(e => e.location === (global ? 'global' : 'working-dir') &&
443
+ (e.id === sequence.id || e.name === sequence.name));
444
+ const existingDir = existing?.filename.includes('/')
445
+ ? existing.filename.slice(0, existing.filename.lastIndexOf('/'))
446
+ : '';
447
+ if (existingDir)
448
+ filename = `${existingDir}/${filename}`;
449
+ const filepath = join(targetDir, filename);
450
+ // Check for conflict
451
+ try {
452
+ await fs.access(filepath);
453
+ // File exists
454
+ if (!overwrite) {
455
+ return { success: false, error: 'File already exists', conflict: true, filepath };
456
+ }
457
+ }
458
+ catch {
459
+ // File doesn't exist, proceed
460
+ }
461
+ // Add usage comment to exported file
462
+ const exportData = {
463
+ _comment: 'CDP Tools replay sequence. Load with: replay({ action: "load", filename: "<this-file>" }), then run with: replay({ action: "run", name: "<this-file>" })',
464
+ ...sequence
465
+ };
466
+ await atomicWriteFile(filepath, JSON.stringify(exportData, null, 2));
467
+ // Now disk-backed: watch it, and record THIS write's mtime so the save
468
+ // does not read back as an external edit.
469
+ await this.trackSequenceSource(sequence, filepath);
470
+ await debugLog('command-recorder', `Saved sequence "${sequence.name}" to ${filepath}`);
471
+ return { success: true, filepath };
472
+ }
473
+ catch (error) {
474
+ await debugLog('command-recorder', `Failed to save sequence: ${error}`);
475
+ return { success: false, error: error.message || String(error) };
476
+ }
477
+ }
478
+ /**
479
+ * Find best matching filename from saved sequences (searches both working dir and global)
480
+ * Supports: exact match, with/without .json, name prefix (returns latest by timestamp)
481
+ * Returns the full path to the matched file
482
+ */
483
+ async findMatchingFilename(searchTerm) {
484
+ const savedSequences = await this.listSavedSequencesOnDisk();
485
+ if (savedSequences.length === 0)
486
+ return null;
487
+ const term = searchTerm.toLowerCase();
488
+ const termWithoutJson = term.endsWith('.json') ? term.slice(0, -5) : term;
489
+ // `filename` is relative to the sequences root, so it may carry a folder
490
+ // ('spine/spine-01.json'). Match the bare basename too, otherwise moving a
491
+ // file into a folder breaks every existing load call that names it directly.
492
+ const base = (f) => f.toLowerCase().split('/').pop() || f.toLowerCase();
493
+ // 1. Exact filename match (with or without .json, full path or basename)
494
+ const exactMatch = savedSequences.find(s => s.filename.toLowerCase() === term ||
495
+ s.filename.toLowerCase() === termWithoutJson + '.json' ||
496
+ base(s.filename) === term ||
497
+ base(s.filename) === termWithoutJson + '.json');
498
+ if (exactMatch) {
499
+ return { filename: exactMatch.filename, fullPath: exactMatch.fullPath, matchType: 'exact', location: exactMatch.location };
500
+ }
501
+ // 2. Exact name match
502
+ const nameMatch = savedSequences.find(s => s.name.toLowerCase() === termWithoutJson);
503
+ if (nameMatch) {
504
+ return { filename: nameMatch.filename, fullPath: nameMatch.fullPath, matchType: 'name', location: nameMatch.location };
505
+ }
506
+ // 3. Filename starts with search term (gets latest by sorting)
507
+ const prefixMatches = savedSequences.filter(s => s.filename.toLowerCase().startsWith(termWithoutJson) ||
508
+ base(s.filename).startsWith(termWithoutJson));
509
+ if (prefixMatches.length > 0) {
510
+ // Sort by filename descending to get latest timestamp
511
+ prefixMatches.sort((a, b) => b.filename.localeCompare(a.filename));
512
+ return { filename: prefixMatches[0].filename, fullPath: prefixMatches[0].fullPath, matchType: 'prefix', location: prefixMatches[0].location };
513
+ }
514
+ // 4. Name starts with search term
515
+ const namePrefixMatches = savedSequences.filter(s => s.name.toLowerCase().startsWith(termWithoutJson));
516
+ if (namePrefixMatches.length > 0) {
517
+ namePrefixMatches.sort((a, b) => b.filename.localeCompare(a.filename));
518
+ return { filename: namePrefixMatches[0].filename, fullPath: namePrefixMatches[0].fullPath, matchType: 'name-prefix', location: namePrefixMatches[0].location };
519
+ }
520
+ // 5. Name contains search term
521
+ const containsMatches = savedSequences.filter(s => s.name.toLowerCase().includes(termWithoutJson));
522
+ if (containsMatches.length > 0) {
523
+ containsMatches.sort((a, b) => b.filename.localeCompare(a.filename));
524
+ return { filename: containsMatches[0].filename, fullPath: containsMatches[0].fullPath, matchType: 'contains', location: containsMatches[0].location };
525
+ }
526
+ return null;
527
+ }
528
+ /**
529
+ * Parse a sequence file from disk WITHOUT touching in-memory state.
530
+ * Split out from registration so a caller can validate the parsed candidate
531
+ * before anything same-named is evicted (see registerLoadedSequence).
532
+ */
533
+ async parseSequenceFile(filepath) {
534
+ const content = await fs.readFile(filepath, 'utf-8');
535
+ return JSON.parse(content);
536
+ }
537
+ /**
538
+ * Register a parsed sequence in memory, replacing any same-named copy.
539
+ * Validation (if supplied) runs BEFORE the removal, so a rejected load leaves
540
+ * the pre-existing sequence completely intact instead of deleting the good copy
541
+ * and then rejecting the bad one, leaving the user with neither.
542
+ */
543
+ registerLoadedSequence(sequence, validate) {
544
+ if (validate && !validate(sequence))
545
+ return false;
546
+ // Remove any existing sequence with the same name (may have different ID)
547
+ this.removeSequenceByName(sequence.name);
548
+ this.sequences.set(sequence.id, sequence);
549
+ return true;
550
+ }
551
+ /**
552
+ * Load a sequence from disk (supports fuzzy filename matching)
553
+ *
554
+ * @param options.validate - Called with the parsed candidate BEFORE it replaces any
555
+ * same-named in-memory sequence. Return false to reject: nothing is removed and
556
+ * nothing is stored, and this method returns null.
557
+ */
558
+ async loadSequenceFromDisk(filename, options) {
559
+ try {
560
+ // Support absolute paths directly
561
+ if (filename.startsWith('/') || filename.includes(':\\')) {
562
+ const sequence = await this.parseSequenceFile(filename);
563
+ if (!this.registerLoadedSequence(sequence, options?.validate)) {
564
+ await debugLog('command-recorder', `Rejected sequence "${sequence.name}" from ${filename} (validation failed) - existing sequence left intact`);
565
+ return null;
566
+ }
567
+ await this.trackSequenceSource(sequence, filename);
568
+ await debugLog('command-recorder', `Loaded sequence "${sequence.name}" from ${filename}`);
569
+ return sequence;
570
+ }
571
+ // Try fuzzy matching for relative filenames
572
+ const match = await this.findMatchingFilename(filename);
573
+ if (!match) {
574
+ await debugLog('command-recorder', `No matching sequence found for "${filename}"`);
575
+ return null;
576
+ }
577
+ const filepath = match.fullPath;
578
+ await debugLog('command-recorder', `Matched "${filename}" to "${match.filename}" (${match.matchType})`);
579
+ const sequence = await this.parseSequenceFile(filepath);
580
+ if (!this.registerLoadedSequence(sequence, options?.validate)) {
581
+ await debugLog('command-recorder', `Rejected sequence "${sequence.name}" from ${filepath} (validation failed) - existing sequence left intact`);
582
+ return null;
583
+ }
584
+ await this.trackSequenceSource(sequence, filepath);
585
+ await debugLog('command-recorder', `Loaded sequence "${sequence.name}" from ${filepath}`);
586
+ return sequence;
587
+ }
588
+ catch (error) {
589
+ await debugLog('command-recorder', `Failed to load sequence from ${filename}: ${error}`);
590
+ return null;
591
+ }
592
+ }
593
+ /**
594
+ * List saved sequences on disk from a specific directory
595
+ */
596
+ async listSequencesFromDir(dir, location) {
597
+ const sequences = [];
598
+ try {
599
+ // Read directory - if it doesn't exist, return empty list (no side effects)
600
+ // Walk subdirectories too: a suite is easier to run when it can be grouped
601
+ // into folders (spine/, story/, _helpers/). `filename` stays relative to the
602
+ // sequences root ('spine/spine-01.json'), so it round-trips back through load.
603
+ let jsonFiles;
604
+ try {
605
+ jsonFiles = await walkSequenceFiles(dir);
606
+ }
607
+ catch (err) {
608
+ if (err.code === 'ENOENT')
609
+ return sequences; // Directory doesn't exist yet
610
+ throw err;
611
+ }
612
+ for (const file of jsonFiles) {
613
+ try {
614
+ const filepath = join(dir, file);
615
+ const content = await fs.readFile(filepath, 'utf-8');
616
+ const sequence = JSON.parse(content);
617
+ sequences.push({
618
+ filename: file,
619
+ name: sequence.name,
620
+ id: sequence.id,
621
+ commandCount: sequence.commands?.length || 0,
622
+ location,
623
+ fullPath: filepath,
624
+ ...(sequence.description && { description: sequence.description }),
625
+ ...(sequence.expectedOutcome && { expectedOutcome: sequence.expectedOutcome }),
626
+ ...(sequence.startUrl && { startUrl: sequence.startUrl }),
627
+ });
628
+ }
629
+ catch (error) {
630
+ await debugLog('command-recorder', `Failed to parse ${file}: ${error}`);
631
+ }
632
+ }
633
+ }
634
+ catch (error) {
635
+ await debugLog('command-recorder', `Failed to list sequences from ${dir}: ${error}`);
636
+ }
637
+ return sequences;
638
+ }
639
+ /**
640
+ * List saved sequences on disk (checks both working directory and global)
641
+ */
642
+ async listSavedSequencesOnDisk() {
643
+ const workingDir = this.getSequencesDir(false);
644
+ const globalDir = this.getSequencesDir(true);
645
+ // Get sequences from both locations
646
+ const workingDirSequences = await this.listSequencesFromDir(workingDir, 'working-dir');
647
+ const globalSequences = await this.listSequencesFromDir(globalDir, 'global');
648
+ // If working dir and global are the same (fallback case), avoid duplicates
649
+ if (workingDir === globalDir) {
650
+ return workingDirSequences;
651
+ }
652
+ return [...workingDirSequences, ...globalSequences];
653
+ }
654
+ /**
655
+ * List issue sequences on disk with associated issue metadata
656
+ */
657
+ async listIssueSequencesOnDisk() {
658
+ const issuesDir = getIssueSequencesDir();
659
+ const sequences = await this.listSequencesFromDir(issuesDir, 'issues');
660
+ // Get issue metadata
661
+ const issueMap = await getIssuesBySequenceFile();
662
+ return sequences.map(seq => {
663
+ const issue = issueMap.get(seq.filename);
664
+ return {
665
+ ...seq,
666
+ issueId: issue?.id,
667
+ issueType: issue?.type,
668
+ issueStatus: issue?.status
669
+ };
670
+ });
671
+ }
672
+ /**
673
+ * Delete a sequence from disk (supports fuzzy filename matching)
674
+ */
675
+ async deleteSequenceFromDisk(filename) {
676
+ try {
677
+ // Support absolute paths directly
678
+ if (filename.startsWith('/') || filename.includes(':\\')) {
679
+ await fs.unlink(filename);
680
+ await debugLog('command-recorder', `Deleted sequence file: ${filename}`);
681
+ return true;
682
+ }
683
+ // Try fuzzy matching for relative filenames
684
+ const match = await this.findMatchingFilename(filename);
685
+ if (!match) {
686
+ await debugLog('command-recorder', `No matching sequence found for "${filename}"`);
687
+ return false;
688
+ }
689
+ const filepath = match.fullPath;
690
+ await debugLog('command-recorder', `Matched "${filename}" to "${match.filename}" (${match.matchType})`);
691
+ await fs.unlink(filepath);
692
+ await debugLog('command-recorder', `Deleted sequence file: ${filepath}`);
693
+ return true;
694
+ }
695
+ catch (error) {
696
+ await debugLog('command-recorder', `Failed to delete ${filename}: ${error}`);
697
+ return false;
698
+ }
699
+ }
700
+ }
701
+ //# sourceMappingURL=command-recorder.js.map