explorbot 0.0.1 → 0.0.5

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 (423) hide show
  1. package/README.md +80 -26
  2. package/bin/explorbot-cli.ts +679 -0
  3. package/boat/api-tester/src/ai/chief/styles.ts +15 -0
  4. package/boat/api-tester/src/ai/chief.ts +335 -0
  5. package/boat/api-tester/src/ai/curler-tools.ts +278 -0
  6. package/boat/api-tester/src/ai/curler.ts +306 -0
  7. package/boat/api-tester/src/api-client.ts +28 -0
  8. package/boat/api-tester/src/apibot.ts +203 -0
  9. package/boat/api-tester/src/cli.ts +301 -0
  10. package/boat/api-tester/src/config.ts +190 -0
  11. package/dist/bin/explorbot-cli.js +19 -98
  12. package/dist/boat/api-tester/bin/apibot-cli.js +0 -1
  13. package/dist/boat/api-tester/src/ai/chief/styles.js +0 -1
  14. package/dist/boat/api-tester/src/ai/chief.js +0 -1
  15. package/dist/boat/api-tester/src/ai/curler-tools.js +0 -1
  16. package/dist/boat/api-tester/src/ai/curler.js +0 -1
  17. package/dist/boat/api-tester/src/api-client.js +0 -1
  18. package/dist/boat/api-tester/src/apibot.js +0 -1
  19. package/dist/boat/api-tester/src/cli.js +0 -1
  20. package/dist/boat/api-tester/src/config.js +0 -1
  21. package/dist/src/action-result.js +0 -1
  22. package/dist/src/action.js +0 -1
  23. package/dist/src/activity.js +0 -1
  24. package/dist/src/ai/agent.js +0 -1
  25. package/dist/src/ai/bosun.js +0 -1
  26. package/dist/src/ai/captain/idle-mode.js +0 -1
  27. package/dist/src/ai/captain/mixin.js +0 -1
  28. package/dist/src/ai/captain/test-mode.js +0 -1
  29. package/dist/src/ai/captain/web-mode.js +0 -1
  30. package/dist/src/ai/captain.js +0 -1
  31. package/dist/src/ai/conversation.js +0 -1
  32. package/dist/src/ai/experience-compactor.js +0 -1
  33. package/dist/src/ai/fisherman-tools.js +0 -1
  34. package/dist/src/ai/fisherman.js +0 -1
  35. package/dist/src/ai/historian.js +0 -1
  36. package/dist/src/ai/navigator.js +0 -1
  37. package/dist/src/ai/pilot.js +0 -1
  38. package/dist/src/ai/planner/session-dedup.js +0 -1
  39. package/dist/src/ai/planner/styles.js +0 -1
  40. package/dist/src/ai/planner/subpages.js +0 -1
  41. package/dist/src/ai/planner.js +0 -1
  42. package/dist/src/ai/provider.js +0 -1
  43. package/dist/src/ai/quartermaster.js +0 -1
  44. package/dist/src/ai/researcher/cache.js +0 -1
  45. package/dist/src/ai/researcher/coordinates.js +0 -1
  46. package/dist/src/ai/researcher/deep-analysis.js +0 -1
  47. package/dist/src/ai/researcher/fingerprint-worker.js +0 -1
  48. package/dist/src/ai/researcher/focus.js +0 -1
  49. package/dist/src/ai/researcher/locators.js +0 -1
  50. package/dist/src/ai/researcher/mixin.js +0 -1
  51. package/dist/src/ai/researcher/parser.js +0 -1
  52. package/dist/src/ai/researcher/research-result.js +0 -1
  53. package/dist/src/ai/researcher.js +0 -1
  54. package/dist/src/ai/rules.js +0 -1
  55. package/dist/src/ai/task-agent.js +0 -1
  56. package/dist/src/ai/tester.js +0 -1
  57. package/dist/src/ai/tools.js +0 -1
  58. package/dist/src/api/api-client.js +0 -1
  59. package/dist/src/api/request-result.js +0 -1
  60. package/dist/src/api/request-store.js +0 -1
  61. package/dist/src/api/spec-reader.js +0 -1
  62. package/dist/src/api/xhr-capture.js +0 -1
  63. package/dist/src/browser-server.js +0 -1
  64. package/dist/src/command-handler.js +0 -1
  65. package/dist/src/commands/add-rule-command.js +0 -1
  66. package/dist/src/commands/base-command.js +0 -1
  67. package/dist/src/commands/clean-command.js +0 -1
  68. package/dist/src/commands/context-aria-command.js +0 -1
  69. package/dist/src/commands/context-command.js +0 -1
  70. package/dist/src/commands/context-data-command.js +0 -1
  71. package/dist/src/commands/context-experience-command.js +0 -1
  72. package/dist/src/commands/context-html-command.js +0 -1
  73. package/dist/src/commands/context-knowledge-command.js +0 -1
  74. package/dist/src/commands/debug-command.js +0 -1
  75. package/dist/src/commands/drill-command.js +0 -1
  76. package/dist/src/commands/exit-command.js +0 -1
  77. package/dist/src/commands/explore-command.js +2 -2
  78. package/dist/src/commands/freesail-command.js +0 -1
  79. package/dist/src/commands/help-command.js +0 -1
  80. package/dist/src/commands/index.js +0 -1
  81. package/dist/src/commands/init-command.js +115 -0
  82. package/dist/src/commands/knows-command.js +0 -1
  83. package/dist/src/commands/learn-command.js +0 -1
  84. package/dist/src/commands/navigate-command.js +0 -1
  85. package/dist/src/commands/path-command.js +0 -1
  86. package/dist/src/commands/plan-clear-command.js +0 -1
  87. package/dist/src/commands/plan-command.js +0 -1
  88. package/dist/src/commands/plan-edit-command.js +0 -1
  89. package/dist/src/commands/plan-load-command.js +0 -1
  90. package/dist/src/commands/plan-reload-command.js +0 -1
  91. package/dist/src/commands/plan-save-command.js +0 -1
  92. package/dist/src/commands/research-command.js +0 -1
  93. package/dist/src/commands/start-command.js +0 -1
  94. package/dist/src/commands/status-command.js +0 -1
  95. package/dist/src/commands/test-command.js +0 -1
  96. package/dist/src/components/ActivityPane.js +0 -1
  97. package/dist/src/components/AddKnowledge.js +0 -1
  98. package/dist/src/components/AddRule.js +0 -1
  99. package/dist/src/components/App.js +0 -1
  100. package/dist/src/components/Autocomplete.js +0 -1
  101. package/dist/src/components/InputPane.js +0 -1
  102. package/dist/src/components/InputReadline.js +0 -1
  103. package/dist/src/components/LogPane.js +0 -1
  104. package/dist/src/components/PlanEditor.js +0 -1
  105. package/dist/src/components/PlanPane.js +0 -1
  106. package/dist/src/components/SessionTimer.js +0 -1
  107. package/dist/src/components/StateTransitionPane.js +0 -1
  108. package/dist/src/components/StatusPane.js +0 -1
  109. package/dist/src/components/TaskPane.js +0 -1
  110. package/dist/src/components/Welcome.js +0 -1
  111. package/dist/src/components/WelcomeChecklist.js +0 -1
  112. package/dist/src/components/WelcomeCommands.js +0 -1
  113. package/dist/src/components/autocomplete-store.js +0 -1
  114. package/dist/src/components/parse-keypress.js +0 -1
  115. package/dist/src/config.js +0 -1
  116. package/dist/src/execution-controller.js +0 -1
  117. package/dist/src/experience-tracker.js +0 -1
  118. package/dist/src/explorbot.js +0 -1
  119. package/dist/src/explorer.js +0 -1
  120. package/dist/src/index.js +0 -1
  121. package/dist/src/knowledge-tracker.js +2 -2
  122. package/dist/src/observability.js +0 -1
  123. package/dist/src/reporter.js +0 -1
  124. package/dist/src/state-manager.js +0 -1
  125. package/dist/src/stats.js +0 -1
  126. package/dist/src/test-plan.js +0 -1
  127. package/dist/src/utils/aria.js +0 -1
  128. package/dist/src/utils/cli-name.js +16 -0
  129. package/dist/src/utils/code-extractor.js +0 -1
  130. package/dist/src/utils/context-formatter.js +0 -1
  131. package/dist/src/utils/error-page.js +0 -1
  132. package/dist/src/utils/expandable.js +0 -1
  133. package/dist/src/utils/hooks-runner.js +0 -1
  134. package/dist/src/utils/html-diff.js +0 -1
  135. package/dist/src/utils/html.js +0 -1
  136. package/dist/src/utils/logger.js +0 -1
  137. package/dist/src/utils/loop.js +0 -1
  138. package/dist/src/utils/markdown-parser.js +0 -1
  139. package/dist/src/utils/markdown-query.js +0 -1
  140. package/dist/src/utils/markdown-terminal.js +0 -1
  141. package/dist/src/utils/research-parser.js +0 -1
  142. package/dist/src/utils/retry.js +0 -1
  143. package/dist/src/utils/rules-loader.js +0 -1
  144. package/dist/src/utils/strings.js +0 -1
  145. package/dist/src/utils/test-plan-markdown.js +0 -1
  146. package/dist/src/utils/throttle.js +0 -1
  147. package/dist/src/utils/unique-names.js +0 -1
  148. package/dist/src/utils/url-matcher.js +0 -1
  149. package/dist/src/utils/web-element.js +0 -1
  150. package/dist/src/utils/xpath.js +0 -1
  151. package/package.json +27 -3
  152. package/src/action-result.ts +694 -0
  153. package/src/action.ts +445 -0
  154. package/src/activity.ts +111 -0
  155. package/src/ai/agent.ts +3 -0
  156. package/src/ai/bosun.ts +557 -0
  157. package/src/ai/captain/idle-mode.ts +116 -0
  158. package/src/ai/captain/mixin.ts +22 -0
  159. package/src/ai/captain/test-mode.ts +262 -0
  160. package/src/ai/captain/web-mode.ts +136 -0
  161. package/src/ai/captain.ts +504 -0
  162. package/src/ai/conversation.ts +205 -0
  163. package/src/ai/experience-compactor.ts +284 -0
  164. package/src/ai/fisherman-tools.ts +181 -0
  165. package/src/ai/fisherman.ts +223 -0
  166. package/src/ai/historian.ts +457 -0
  167. package/src/ai/navigator.ts +572 -0
  168. package/src/ai/pilot.ts +776 -0
  169. package/src/ai/planner/session-dedup.ts +35 -0
  170. package/src/ai/planner/styles.ts +17 -0
  171. package/src/ai/planner/subpages.ts +141 -0
  172. package/src/ai/planner.ts +536 -0
  173. package/src/ai/provider.ts +613 -0
  174. package/src/ai/quartermaster.ts +286 -0
  175. package/src/ai/researcher/cache.ts +103 -0
  176. package/src/ai/researcher/coordinates.ts +238 -0
  177. package/src/ai/researcher/deep-analysis.ts +415 -0
  178. package/src/ai/researcher/fingerprint-worker.ts +59 -0
  179. package/src/ai/researcher/focus.ts +42 -0
  180. package/src/ai/researcher/locators.ts +282 -0
  181. package/src/ai/researcher/mixin.ts +4 -0
  182. package/src/ai/researcher/parser.ts +186 -0
  183. package/src/ai/researcher/research-result.ts +115 -0
  184. package/src/ai/researcher.ts +857 -0
  185. package/src/ai/rules.ts +376 -0
  186. package/src/ai/task-agent.ts +141 -0
  187. package/src/ai/tester.ts +939 -0
  188. package/src/ai/tools.ts +1117 -0
  189. package/src/api/api-client.ts +109 -0
  190. package/src/api/request-result.ts +212 -0
  191. package/src/api/request-store.ts +130 -0
  192. package/src/api/spec-reader.ts +174 -0
  193. package/src/api/xhr-capture.ts +100 -0
  194. package/src/browser-server.ts +74 -0
  195. package/src/command-handler.ts +454 -0
  196. package/src/commands/add-rule-command.ts +63 -0
  197. package/src/commands/base-command.ts +27 -0
  198. package/src/commands/clean-command.ts +73 -0
  199. package/src/commands/context-aria-command.ts +22 -0
  200. package/src/commands/context-command.ts +67 -0
  201. package/src/commands/context-data-command.ts +30 -0
  202. package/src/commands/context-experience-command.ts +48 -0
  203. package/src/commands/context-html-command.ts +33 -0
  204. package/src/commands/context-knowledge-command.ts +43 -0
  205. package/src/commands/debug-command.ts +13 -0
  206. package/src/commands/drill-command.ts +34 -0
  207. package/src/commands/exit-command.ts +32 -0
  208. package/src/commands/explore-command.ts +129 -0
  209. package/src/commands/freesail-command.ts +95 -0
  210. package/src/commands/help-command.ts +8 -0
  211. package/src/commands/index.ts +69 -0
  212. package/src/commands/init-command.ts +128 -0
  213. package/src/commands/knows-command.ts +68 -0
  214. package/src/commands/learn-command.ts +44 -0
  215. package/src/commands/navigate-command.ts +18 -0
  216. package/src/commands/path-command.ts +83 -0
  217. package/src/commands/plan-clear-command.ts +14 -0
  218. package/src/commands/plan-command.ts +41 -0
  219. package/src/commands/plan-edit-command.ts +9 -0
  220. package/src/commands/plan-load-command.ts +18 -0
  221. package/src/commands/plan-reload-command.ts +28 -0
  222. package/src/commands/plan-save-command.ts +25 -0
  223. package/src/commands/research-command.ts +45 -0
  224. package/src/commands/start-command.ts +13 -0
  225. package/src/commands/status-command.tsx +23 -0
  226. package/src/commands/test-command.ts +84 -0
  227. package/src/components/ActivityPane.tsx +80 -0
  228. package/src/components/AddKnowledge.tsx +169 -0
  229. package/src/components/AddRule.tsx +174 -0
  230. package/src/components/App.tsx +377 -0
  231. package/src/components/Autocomplete.tsx +63 -0
  232. package/src/components/InputPane.tsx +259 -0
  233. package/src/components/InputReadline.tsx +704 -0
  234. package/src/components/LogPane.tsx +187 -0
  235. package/src/components/PlanEditor.tsx +150 -0
  236. package/src/components/PlanPane.tsx +71 -0
  237. package/src/components/SessionTimer.tsx +35 -0
  238. package/src/components/StateTransitionPane.tsx +149 -0
  239. package/src/components/StatusPane.tsx +62 -0
  240. package/src/components/TaskPane.tsx +119 -0
  241. package/src/components/Welcome.tsx +83 -0
  242. package/src/components/WelcomeChecklist.tsx +118 -0
  243. package/src/components/WelcomeCommands.tsx +102 -0
  244. package/src/components/autocomplete-store.ts +35 -0
  245. package/src/components/parse-keypress.ts +170 -0
  246. package/src/config.ts +490 -0
  247. package/src/execution-controller.ts +109 -0
  248. package/src/experience-tracker.ts +350 -0
  249. package/src/explorbot.ts +405 -0
  250. package/src/explorer.ts +713 -0
  251. package/src/index.tsx +62 -0
  252. package/src/knowledge-tracker.ts +230 -0
  253. package/src/observability.ts +150 -0
  254. package/src/reporter.ts +224 -0
  255. package/src/state-manager.ts +556 -0
  256. package/src/stats.ts +53 -0
  257. package/src/test-plan.ts +432 -0
  258. package/src/utils/aria.ts +629 -0
  259. package/src/utils/cli-name.ts +13 -0
  260. package/src/utils/code-extractor.ts +22 -0
  261. package/src/utils/context-formatter.ts +239 -0
  262. package/src/utils/error-page.ts +23 -0
  263. package/src/utils/expandable.ts +38 -0
  264. package/src/utils/hooks-runner.ts +79 -0
  265. package/src/utils/html-diff.ts +918 -0
  266. package/src/utils/html.ts +1316 -0
  267. package/src/utils/logger.ts +534 -0
  268. package/src/utils/loop.ts +176 -0
  269. package/src/utils/markdown-parser.ts +127 -0
  270. package/src/utils/markdown-query.ts +466 -0
  271. package/src/utils/markdown-terminal.ts +43 -0
  272. package/src/utils/research-parser.ts +11 -0
  273. package/src/utils/retry.ts +73 -0
  274. package/src/utils/rules-loader.ts +118 -0
  275. package/src/utils/strings.ts +13 -0
  276. package/src/utils/test-plan-markdown.ts +332 -0
  277. package/src/utils/throttle.ts +18 -0
  278. package/src/utils/unique-names.ts +14 -0
  279. package/src/utils/url-matcher.ts +45 -0
  280. package/src/utils/web-element.ts +145 -0
  281. package/src/utils/xpath.ts +129 -0
  282. package/dist/bin/explorbot-cli.js.map +0 -1
  283. package/dist/boat/api-tester/bin/apibot-cli.js.map +0 -1
  284. package/dist/boat/api-tester/example/apibot.config.js +0 -31
  285. package/dist/boat/api-tester/example/apibot.config.js.map +0 -1
  286. package/dist/boat/api-tester/src/ai/chief/styles.js.map +0 -1
  287. package/dist/boat/api-tester/src/ai/chief.js.map +0 -1
  288. package/dist/boat/api-tester/src/ai/curler-tools.js.map +0 -1
  289. package/dist/boat/api-tester/src/ai/curler.js.map +0 -1
  290. package/dist/boat/api-tester/src/api-client.js.map +0 -1
  291. package/dist/boat/api-tester/src/apibot.js.map +0 -1
  292. package/dist/boat/api-tester/src/cli.js.map +0 -1
  293. package/dist/boat/api-tester/src/config.js.map +0 -1
  294. package/dist/prompts/audit-rules.md +0 -124
  295. package/dist/src/action-result.js.map +0 -1
  296. package/dist/src/action.js.map +0 -1
  297. package/dist/src/activity.js.map +0 -1
  298. package/dist/src/ai/agent.js.map +0 -1
  299. package/dist/src/ai/bosun.js.map +0 -1
  300. package/dist/src/ai/captain/idle-mode.js.map +0 -1
  301. package/dist/src/ai/captain/mixin.js.map +0 -1
  302. package/dist/src/ai/captain/test-mode.js.map +0 -1
  303. package/dist/src/ai/captain/web-mode.js.map +0 -1
  304. package/dist/src/ai/captain.js.map +0 -1
  305. package/dist/src/ai/conversation.js.map +0 -1
  306. package/dist/src/ai/experience-compactor.js.map +0 -1
  307. package/dist/src/ai/fisherman-tools.js.map +0 -1
  308. package/dist/src/ai/fisherman.js.map +0 -1
  309. package/dist/src/ai/historian.js.map +0 -1
  310. package/dist/src/ai/navigator.js.map +0 -1
  311. package/dist/src/ai/pilot.js.map +0 -1
  312. package/dist/src/ai/planner/session-dedup.js.map +0 -1
  313. package/dist/src/ai/planner/styles.js.map +0 -1
  314. package/dist/src/ai/planner/subpages.js.map +0 -1
  315. package/dist/src/ai/planner.js.map +0 -1
  316. package/dist/src/ai/provider.js.map +0 -1
  317. package/dist/src/ai/quartermaster.js.map +0 -1
  318. package/dist/src/ai/researcher/cache.js.map +0 -1
  319. package/dist/src/ai/researcher/coordinates.js.map +0 -1
  320. package/dist/src/ai/researcher/deep-analysis.js.map +0 -1
  321. package/dist/src/ai/researcher/fingerprint-worker.js.map +0 -1
  322. package/dist/src/ai/researcher/focus.js.map +0 -1
  323. package/dist/src/ai/researcher/locators.js.map +0 -1
  324. package/dist/src/ai/researcher/mixin.js.map +0 -1
  325. package/dist/src/ai/researcher/parser.js.map +0 -1
  326. package/dist/src/ai/researcher/research-result.js.map +0 -1
  327. package/dist/src/ai/researcher.js.map +0 -1
  328. package/dist/src/ai/rules.js.map +0 -1
  329. package/dist/src/ai/task-agent.js.map +0 -1
  330. package/dist/src/ai/tester.js.map +0 -1
  331. package/dist/src/ai/tools.js.map +0 -1
  332. package/dist/src/api/api-client.js.map +0 -1
  333. package/dist/src/api/request-result.js.map +0 -1
  334. package/dist/src/api/request-store.js.map +0 -1
  335. package/dist/src/api/spec-reader.js.map +0 -1
  336. package/dist/src/api/xhr-capture.js.map +0 -1
  337. package/dist/src/browser-server.js.map +0 -1
  338. package/dist/src/command-handler.js.map +0 -1
  339. package/dist/src/commands/add-rule-command.js.map +0 -1
  340. package/dist/src/commands/base-command.js.map +0 -1
  341. package/dist/src/commands/clean-command.js.map +0 -1
  342. package/dist/src/commands/context-aria-command.js.map +0 -1
  343. package/dist/src/commands/context-command.js.map +0 -1
  344. package/dist/src/commands/context-data-command.js.map +0 -1
  345. package/dist/src/commands/context-experience-command.js.map +0 -1
  346. package/dist/src/commands/context-html-command.js.map +0 -1
  347. package/dist/src/commands/context-knowledge-command.js.map +0 -1
  348. package/dist/src/commands/debug-command.js.map +0 -1
  349. package/dist/src/commands/drill-command.js.map +0 -1
  350. package/dist/src/commands/exit-command.js.map +0 -1
  351. package/dist/src/commands/explore-command.js.map +0 -1
  352. package/dist/src/commands/freesail-command.js.map +0 -1
  353. package/dist/src/commands/help-command.js.map +0 -1
  354. package/dist/src/commands/index.js.map +0 -1
  355. package/dist/src/commands/knows-command.js.map +0 -1
  356. package/dist/src/commands/learn-command.js.map +0 -1
  357. package/dist/src/commands/navigate-command.js.map +0 -1
  358. package/dist/src/commands/path-command.js.map +0 -1
  359. package/dist/src/commands/plan-clear-command.js.map +0 -1
  360. package/dist/src/commands/plan-command.js.map +0 -1
  361. package/dist/src/commands/plan-edit-command.js.map +0 -1
  362. package/dist/src/commands/plan-load-command.js.map +0 -1
  363. package/dist/src/commands/plan-reload-command.js.map +0 -1
  364. package/dist/src/commands/plan-save-command.js.map +0 -1
  365. package/dist/src/commands/research-command.js.map +0 -1
  366. package/dist/src/commands/start-command.js.map +0 -1
  367. package/dist/src/commands/status-command.js.map +0 -1
  368. package/dist/src/commands/test-command.js.map +0 -1
  369. package/dist/src/components/ActivityPane.js.map +0 -1
  370. package/dist/src/components/AddKnowledge.js.map +0 -1
  371. package/dist/src/components/AddRule.js.map +0 -1
  372. package/dist/src/components/App.js.map +0 -1
  373. package/dist/src/components/Autocomplete.js.map +0 -1
  374. package/dist/src/components/InputPane.js.map +0 -1
  375. package/dist/src/components/InputReadline.js.map +0 -1
  376. package/dist/src/components/LogPane.js.map +0 -1
  377. package/dist/src/components/PlanEditor.js.map +0 -1
  378. package/dist/src/components/PlanPane.js.map +0 -1
  379. package/dist/src/components/SessionTimer.js.map +0 -1
  380. package/dist/src/components/StateTransitionPane.js.map +0 -1
  381. package/dist/src/components/StatusPane.js.map +0 -1
  382. package/dist/src/components/TaskPane.js.map +0 -1
  383. package/dist/src/components/Welcome.js.map +0 -1
  384. package/dist/src/components/WelcomeChecklist.js.map +0 -1
  385. package/dist/src/components/WelcomeCommands.js.map +0 -1
  386. package/dist/src/components/autocomplete-store.js.map +0 -1
  387. package/dist/src/components/parse-keypress.js.map +0 -1
  388. package/dist/src/config.js.map +0 -1
  389. package/dist/src/execution-controller.js.map +0 -1
  390. package/dist/src/experience-tracker.js.map +0 -1
  391. package/dist/src/explorbot.js.map +0 -1
  392. package/dist/src/explorer.js.map +0 -1
  393. package/dist/src/index.js.map +0 -1
  394. package/dist/src/knowledge-tracker.js.map +0 -1
  395. package/dist/src/observability.js.map +0 -1
  396. package/dist/src/reporter.js.map +0 -1
  397. package/dist/src/state-manager.js.map +0 -1
  398. package/dist/src/stats.js.map +0 -1
  399. package/dist/src/test-plan.js.map +0 -1
  400. package/dist/src/utils/aria.js.map +0 -1
  401. package/dist/src/utils/code-extractor.js.map +0 -1
  402. package/dist/src/utils/context-formatter.js.map +0 -1
  403. package/dist/src/utils/error-page.js.map +0 -1
  404. package/dist/src/utils/expandable.js.map +0 -1
  405. package/dist/src/utils/hooks-runner.js.map +0 -1
  406. package/dist/src/utils/html-diff.js.map +0 -1
  407. package/dist/src/utils/html.js.map +0 -1
  408. package/dist/src/utils/logger.js.map +0 -1
  409. package/dist/src/utils/loop.js.map +0 -1
  410. package/dist/src/utils/markdown-parser.js.map +0 -1
  411. package/dist/src/utils/markdown-query.js.map +0 -1
  412. package/dist/src/utils/markdown-terminal.js.map +0 -1
  413. package/dist/src/utils/research-parser.js.map +0 -1
  414. package/dist/src/utils/retry.js.map +0 -1
  415. package/dist/src/utils/rules-loader.js.map +0 -1
  416. package/dist/src/utils/strings.js.map +0 -1
  417. package/dist/src/utils/test-plan-markdown.js.map +0 -1
  418. package/dist/src/utils/throttle.js.map +0 -1
  419. package/dist/src/utils/unique-names.js.map +0 -1
  420. package/dist/src/utils/url-matcher.js.map +0 -1
  421. package/dist/src/utils/web-element.js.map +0 -1
  422. package/dist/src/utils/xpath.js.map +0 -1
  423. package/prompts/audit-rules.md +0 -124
@@ -0,0 +1,286 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { z } from 'zod';
4
+ import type { ActionResult } from '../action-result.ts';
5
+ import { ConfigParser } from '../config.ts';
6
+ import type { StateManager, StateTransition, WebPageState } from '../state-manager.ts';
7
+ import type { Task } from '../test-plan.ts';
8
+ import { createDebug, tag } from '../utils/logger.ts';
9
+ import type { Conversation, ToolExecution } from './conversation.ts';
10
+ import type { Provider } from './provider.ts';
11
+ import { CODECEPT_TOOLS } from './tools.ts';
12
+
13
+ const debugLog = createDebug('explorbot:quartermaster');
14
+
15
+ interface AxeViolation {
16
+ id: string;
17
+ impact: 'critical' | 'serious' | 'moderate' | 'minor';
18
+ description: string;
19
+ helpUrl: string;
20
+ nodes: Array<{ html: string; target: string[] }>;
21
+ }
22
+
23
+ interface PageAnalysis {
24
+ url: string;
25
+ stateHash: string;
26
+ timestamp: string;
27
+ axeViolations: AxeViolation[];
28
+ }
29
+
30
+ interface SemanticIssue {
31
+ type: 'unclear_intention' | 'confusing_naming' | 'hard_to_interact';
32
+ element: string;
33
+ issue: string;
34
+ suggestion: string;
35
+ }
36
+
37
+ interface AnalysisReport {
38
+ scenario: string;
39
+ timestamp: string;
40
+ url: string;
41
+ axeViolations: AxeViolation[];
42
+ semanticIssues: SemanticIssue[];
43
+ }
44
+
45
+ export class Quartermaster {
46
+ private provider: Provider;
47
+ private model?: any;
48
+ private outputDir: string;
49
+ private pageAnalyses: Map<string, PageAnalysis> = new Map();
50
+ private unsubscribe: (() => void) | null = null;
51
+ private playwrightHelper: any = null;
52
+ private pendingAnalyses: Promise<void>[] = [];
53
+
54
+ constructor(provider: Provider, options?: { model?: any }) {
55
+ this.provider = provider;
56
+ this.model = options?.model;
57
+
58
+ const configParser = ConfigParser.getInstance();
59
+ this.outputDir = join(configParser.getOutputDir(), 'a11y');
60
+ }
61
+
62
+ start(playwrightHelper: any, stateManager: StateManager): void {
63
+ this.playwrightHelper = playwrightHelper;
64
+ this.ensureDirectory();
65
+
66
+ this.unsubscribe = stateManager.onStateChange((event) => {
67
+ this.onPageChange(event);
68
+ });
69
+
70
+ debugLog('Quartermaster started, listening for page changes');
71
+ }
72
+
73
+ stop(): void {
74
+ if (this.unsubscribe) {
75
+ this.unsubscribe();
76
+ this.unsubscribe = null;
77
+ }
78
+ debugLog('Quartermaster stopped');
79
+ }
80
+
81
+ private ensureDirectory(): void {
82
+ if (!existsSync(this.outputDir)) {
83
+ mkdirSync(this.outputDir, { recursive: true });
84
+ }
85
+ }
86
+
87
+ private onPageChange(event: StateTransition): void {
88
+ const state = event.toState;
89
+ if (!state?.hash) return;
90
+ if (this.pageAnalyses.has(state.hash)) return;
91
+
92
+ const analysisPromise = this.runAxeAnalysis(state).catch((err) => {
93
+ debugLog('Axe analysis failed:', err);
94
+ });
95
+
96
+ this.pendingAnalyses.push(analysisPromise);
97
+ }
98
+
99
+ private async runAxeAnalysis(state: WebPageState): Promise<void> {
100
+ const page = this.playwrightHelper?.page;
101
+ if (!page || !state.hash) {
102
+ debugLog('No page available for axe analysis');
103
+ return;
104
+ }
105
+
106
+ try {
107
+ const { AxeBuilder } = await import('@axe-core/playwright');
108
+ const results = await new AxeBuilder({ page }).analyze();
109
+
110
+ const violations: AxeViolation[] = results.violations.map((v) => ({
111
+ id: v.id,
112
+ impact: v.impact as AxeViolation['impact'],
113
+ description: v.description,
114
+ helpUrl: v.helpUrl,
115
+ nodes: v.nodes.map((n) => ({
116
+ html: n.html,
117
+ target: n.target as string[],
118
+ })),
119
+ }));
120
+
121
+ this.pageAnalyses.set(state.hash, {
122
+ url: state.url,
123
+ stateHash: state.hash,
124
+ timestamp: new Date().toISOString(),
125
+ axeViolations: violations,
126
+ });
127
+
128
+ debugLog(`Axe analysis complete for ${state.url}: ${violations.length} violations`);
129
+ } catch (error) {
130
+ debugLog('Axe analysis error:', error);
131
+ }
132
+ }
133
+
134
+ async analyzeSession(task: Task, initialState: ActionResult, conversation: Conversation): Promise<AnalysisReport | null> {
135
+ await Promise.all(this.pendingAnalyses);
136
+ this.pendingAnalyses = [];
137
+
138
+ try {
139
+ const stateHash = initialState.getStateHash();
140
+ const pageAnalysis = this.pageAnalyses.get(stateHash);
141
+
142
+ const toolExecutions = conversation.getToolExecutions();
143
+ const codeceptExecutions = toolExecutions.filter((e) => CODECEPT_TOOLS.includes(e.toolName as any));
144
+
145
+ if (codeceptExecutions.length === 0 && !pageAnalysis?.axeViolations.length) {
146
+ debugLog('No interactions or violations to analyze');
147
+ return null;
148
+ }
149
+
150
+ const axeViolations = pageAnalysis?.axeViolations || [];
151
+ const semanticIssues = await this.generateSemanticAnalysis(axeViolations, codeceptExecutions, initialState);
152
+
153
+ if (axeViolations.length === 0 && semanticIssues.length === 0) {
154
+ debugLog('No issues found');
155
+ return null;
156
+ }
157
+
158
+ const report: AnalysisReport = {
159
+ scenario: task.description,
160
+ timestamp: new Date().toISOString(),
161
+ url: initialState.url || '',
162
+ axeViolations,
163
+ semanticIssues,
164
+ };
165
+
166
+ this.saveReport(stateHash, report);
167
+ this.addNotesToTask(task, report);
168
+
169
+ tag('substep').log(`Quartermaster: ${axeViolations.length} technical + ${semanticIssues.length} semantic issues`);
170
+ return report;
171
+ } catch (error) {
172
+ debugLog('Quartermaster analysis failed:', error);
173
+ return null;
174
+ }
175
+ }
176
+
177
+ private async generateSemanticAnalysis(axeViolations: AxeViolation[], executions: ToolExecution[], initialState: ActionResult): Promise<SemanticIssue[]> {
178
+ const failedExecs = executions.filter((e) => !e.wasSuccessful);
179
+ if (failedExecs.length === 0 && axeViolations.length === 0) return [];
180
+
181
+ const axeSummary = axeViolations
182
+ .slice(0, 10)
183
+ .map((v) => `[${v.impact}] ${v.id}: ${v.description}`)
184
+ .join('\n');
185
+
186
+ const failedActions = failedExecs
187
+ .slice(0, 10)
188
+ .map((e) => {
189
+ const locator = e.input?.locator || e.output?.locator || '';
190
+ const error = e.output?.message || 'Failed';
191
+ return `${e.toolName}("${locator}"): ${error}`;
192
+ })
193
+ .join('\n');
194
+
195
+ const ariaSnapshot = initialState.getCompactARIA().slice(0, 3000);
196
+
197
+ const schema = z.object({
198
+ issues: z.array(
199
+ z.object({
200
+ type: z.enum(['unclear_intention', 'confusing_naming', 'hard_to_interact']).describe('Type of semantic issue'),
201
+ element: z.string().describe('Short element identifier: tag with key attributes, e.g. <button class="nav-toggle"> or <a href="/login" role="link">'),
202
+ issue: z.string().describe('What is confusing or problematic'),
203
+ suggestion: z.string().describe('Actionable improvement suggestion'),
204
+ })
205
+ ),
206
+ });
207
+
208
+ const prompt = `Analyze this page for semantic UX issues that automated tools cannot detect.
209
+
210
+ ## Technical Violations (axe-core)
211
+ ${axeSummary || 'None'}
212
+
213
+ ## Failed Agent Interactions
214
+ ${failedActions || 'None'}
215
+
216
+ ## Page Structure (ARIA)
217
+ ${ariaSnapshot}
218
+
219
+ Focus on issues that require human judgment:
220
+ - **unclear_intention**: Button/link text doesn't match actual behavior
221
+ - **confusing_naming**: Ambiguous labels, inconsistent terminology
222
+ - **hard_to_interact**: Controls requiring non-obvious sequences
223
+
224
+ Provide 0-5 high-signal issues. Skip obvious technical violations already covered by axe-core.
225
+ Focus on what would confuse a real user or caused the agent to make mistakes.`;
226
+
227
+ const response = await this.provider.generateObject(
228
+ [
229
+ { role: 'system', content: 'You are a UX expert analyzing pages for semantic issues that confuse users.' },
230
+ { role: 'user', content: prompt },
231
+ ],
232
+ schema,
233
+ this.model
234
+ );
235
+
236
+ return response?.object?.issues || [];
237
+ }
238
+
239
+ private addNotesToTask(task: Task, report: AnalysisReport): void {
240
+ const criticalViolations = report.axeViolations.filter((v) => v.impact === 'critical' || v.impact === 'serious');
241
+ for (const v of criticalViolations.slice(0, 3)) {
242
+ const nodeHtml = v.nodes[0]?.html.slice(0, 100) || '';
243
+ task.addNote(`🔴 A11Y [${v.impact}] ${v.id}: ${v.description} — ${nodeHtml}`);
244
+ }
245
+
246
+ for (const issue of report.semanticIssues.slice(0, 3)) {
247
+ task.addNote(`💡 UX [${issue.type}] ${issue.element}: ${issue.suggestion}`);
248
+ }
249
+ }
250
+
251
+ private saveReport(stateHash: string, report: AnalysisReport): void {
252
+ const filePath = join(this.outputDir, `${stateHash}.md`);
253
+ const content = this.formatReportMarkdown(report);
254
+ writeFileSync(filePath, content, 'utf8');
255
+ debugLog(`Saved a11y report to ${filePath}`);
256
+ }
257
+
258
+ private formatReportMarkdown(report: AnalysisReport): string {
259
+ let content = `## A11Y Analysis: ${report.url}\n\n`;
260
+ content += `**Scenario**: ${report.scenario}\n`;
261
+ content += `**Date**: ${report.timestamp}\n\n`;
262
+
263
+ if (report.axeViolations.length > 0) {
264
+ content += '### Technical Violations (axe-core)\n\n';
265
+ for (const v of report.axeViolations) {
266
+ content += `- [${v.impact}] **${v.id}**: ${v.description}\n`;
267
+ content += ` [Learn more](${v.helpUrl})\n`;
268
+ for (const node of v.nodes.slice(0, 2)) {
269
+ content += ` - \`${node.target.join(' > ')}\` — \`${node.html.slice(0, 100)}\`\n`;
270
+ }
271
+ content += '\n';
272
+ }
273
+ }
274
+
275
+ if (report.semanticIssues.length > 0) {
276
+ content += '### Semantic Issues (LLM analysis)\n\n';
277
+ for (const issue of report.semanticIssues) {
278
+ content += `- **${issue.type}** - ${issue.element}\n`;
279
+ content += ` Issue: ${issue.issue}\n`;
280
+ content += ` → ${issue.suggestion}\n\n`;
281
+ }
282
+ }
283
+
284
+ return content;
285
+ }
286
+ }
@@ -0,0 +1,103 @@
1
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { outputPath } from '../../config.ts';
4
+ import { computeHtmlFingerprint } from '../../utils/html-diff.ts';
5
+ import { debugLog } from './mixin.ts';
6
+
7
+ const CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
8
+ const FINGERPRINT_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
9
+ const FINGERPRINT_WORKER_TIMEOUT_MS = 10_000;
10
+ const SIMILARITY_THRESHOLD = 90;
11
+
12
+ const memoryCache: Record<string, string> = {};
13
+ const memoryCacheTimestamps: Record<string, number> = {};
14
+
15
+ let fingerprintWorker: Worker | null = null;
16
+
17
+ function getStatesDir(): string {
18
+ return outputPath('states');
19
+ }
20
+
21
+ function getFingerprintWorker(): Worker {
22
+ if (!fingerprintWorker) {
23
+ const ext = import.meta.url.endsWith('.ts') ? '.ts' : '.js';
24
+ fingerprintWorker = new Worker(new URL(`./fingerprint-worker${ext}`, import.meta.url).href);
25
+ }
26
+ return fingerprintWorker;
27
+ }
28
+
29
+ export function getCachedResearch(hash: string): string {
30
+ if (!hash) return '';
31
+ const now = Date.now();
32
+ const timestamp = memoryCacheTimestamps[hash];
33
+ if (timestamp && now - timestamp <= CACHE_TTL_MS) {
34
+ return memoryCache[hash] || '';
35
+ }
36
+ const researchFile = outputPath('research', `${hash}.md`);
37
+ if (!existsSync(researchFile)) return '';
38
+ const stats = statSync(researchFile);
39
+ if (now - stats.mtimeMs > CACHE_TTL_MS) return '';
40
+ const cached = readFileSync(researchFile, 'utf8');
41
+ memoryCache[hash] = cached;
42
+ memoryCacheTimestamps[hash] = now;
43
+ return cached;
44
+ }
45
+
46
+ export function saveResearch(hash: string, text: string, combinedHtml?: string): string {
47
+ const researchDir = outputPath('research');
48
+ const researchFile = join(researchDir, `${hash}.md`);
49
+ if (!existsSync(researchDir)) mkdirSync(researchDir, { recursive: true });
50
+ writeFileSync(researchFile, text);
51
+ memoryCache[hash] = text;
52
+ memoryCacheTimestamps[hash] = Date.now();
53
+ debugLog(`Research saved to ${researchFile}`);
54
+
55
+ if (combinedHtml) {
56
+ const statesDir = getStatesDir();
57
+ if (!existsSync(statesDir)) mkdirSync(statesDir, { recursive: true });
58
+ const fingerprint = computeHtmlFingerprint(combinedHtml);
59
+ const fingerprintFile = join(statesDir, `${hash}.fingerprint`);
60
+ writeFileSync(fingerprintFile, fingerprint.join('\n'));
61
+ debugLog(`Fingerprint saved to ${fingerprintFile}`);
62
+ }
63
+
64
+ return researchFile;
65
+ }
66
+
67
+ export function findSimilarResearch(combinedHtml: string): Promise<string | null> {
68
+ const statesDir = getStatesDir();
69
+ if (!existsSync(statesDir)) return Promise.resolve(null);
70
+
71
+ const worker = getFingerprintWorker();
72
+
73
+ return new Promise((resolve) => {
74
+ const timeout = setTimeout(() => {
75
+ debugLog('Fingerprint worker timed out');
76
+ resolve(null);
77
+ }, FINGERPRINT_WORKER_TIMEOUT_MS);
78
+
79
+ worker.onmessage = (event: MessageEvent) => {
80
+ clearTimeout(timeout);
81
+ const { matchHash, similarity } = event.data as { matchHash: string | null; similarity: number };
82
+ if (!matchHash) {
83
+ resolve(null);
84
+ return;
85
+ }
86
+
87
+ debugLog(`Similar research found: ${matchHash} (${similarity}% similar)`);
88
+ const research = getCachedResearch(matchHash);
89
+ if (research) {
90
+ resolve(research);
91
+ return;
92
+ }
93
+ resolve(null);
94
+ };
95
+
96
+ worker.postMessage({
97
+ html: combinedHtml,
98
+ statesDir,
99
+ maxAgeMs: FINGERPRINT_MAX_AGE_MS,
100
+ threshold: SIMILARITY_THRESHOLD,
101
+ });
102
+ });
103
+ }
@@ -0,0 +1,238 @@
1
+ import dedent from 'dedent';
2
+ import type { Page } from 'playwright';
3
+ import type { ActionResult } from '../../action-result.js';
4
+ import type Explorer from '../../explorer.ts';
5
+ import { tag } from '../../utils/logger.js';
6
+ import { mdq } from '../../utils/markdown-query.ts';
7
+ import { WebElement } from '../../utils/web-element.ts';
8
+ import type { Provider } from '../provider.js';
9
+ import { type Constructor, debugLog } from './mixin.ts';
10
+ import { parseResearchSections } from './parser.ts';
11
+ import type { ResearchResult } from './research-result.ts';
12
+
13
+ export async function visuallyAnnotateContainers(page: Page, containers: Array<{ css: string; label: string }>): Promise<number> {
14
+ return page.evaluate((ctrs: Array<{ css: string; label: string }>) => {
15
+ const containerColors = ['#9b59b6', '#16a085', '#c0392b', '#2980b9'];
16
+ const drawnContainers: Array<{ label: string; color: string }> = [];
17
+ for (let i = 0; i < ctrs.length; i++) {
18
+ let el: Element | null;
19
+ try {
20
+ el = document.querySelector(ctrs[i].css);
21
+ } catch {
22
+ continue;
23
+ }
24
+ if (!el) continue;
25
+ const rect = el.getBoundingClientRect();
26
+ if (rect.width === 0 && rect.height === 0) continue;
27
+
28
+ const color = containerColors[i % containerColors.length];
29
+ const box = document.createElement('div');
30
+ box.setAttribute('data-explorbot-annotation', 'true');
31
+ box.style.cssText = `position:absolute;left:${rect.left + window.scrollX}px;top:${rect.top + window.scrollY}px;width:${rect.width}px;height:${rect.height}px;border:1px dashed ${color};z-index:99998;pointer-events:none;`;
32
+ document.body.appendChild(box);
33
+ drawnContainers.push({ label: ctrs[i].label, color });
34
+ }
35
+
36
+ if (drawnContainers.length > 0) {
37
+ const legend = document.createElement('div');
38
+ legend.setAttribute('data-explorbot-annotation', 'true');
39
+ legend.style.cssText = 'position:fixed;right:10px;bottom:10px;background:white;color:black;font-size:14px;font-family:sans-serif;padding:10px 14px;z-index:100001;pointer-events:none;border:3px solid #e63946;border-radius:6px;line-height:22px;';
40
+ const title = '<div style="font-weight:bold;margin-bottom:4px;color:#e63946;">Legend</div>';
41
+ const items = drawnContainers.map((c) => `<div><span style="display:inline-block;width:24px;border-top:3px dashed ${c.color};margin-right:8px;vertical-align:middle;"></span>${c.label}</div>`).join('');
42
+ legend.innerHTML = title + items;
43
+ document.body.appendChild(legend);
44
+ }
45
+
46
+ const colors = ['#e63946', '#2a9d8f', '#e9c46a', '#264653', '#f4a261', '#7b2cbf', '#0077b6', '#d62828'];
47
+ const elements = document.querySelectorAll('[data-explorbot-eidx]');
48
+ let count = 0;
49
+ for (const el of elements) {
50
+ const eidx = el.getAttribute('data-explorbot-eidx');
51
+ if (!eidx) continue;
52
+ const rect = el.getBoundingClientRect();
53
+ if (rect.width === 0 && rect.height === 0) continue;
54
+
55
+ const color = colors[count % colors.length];
56
+
57
+ const box = document.createElement('div');
58
+ box.setAttribute('data-explorbot-annotation', 'true');
59
+ box.style.cssText = `position:absolute;left:${rect.left + window.scrollX}px;top:${rect.top + window.scrollY}px;width:${rect.width}px;height:${rect.height}px;border:2px solid ${color};z-index:99999;pointer-events:none;`;
60
+
61
+ const label = document.createElement('div');
62
+ label.textContent = eidx;
63
+ label.style.cssText = `position:absolute;top:-14px;right:-2px;background:${color};color:white;font-size:10px;padding:0 3px;line-height:14px;font-family:monospace;z-index:100000;pointer-events:none;`;
64
+ box.appendChild(label);
65
+
66
+ document.body.appendChild(box);
67
+ count++;
68
+ }
69
+ return count;
70
+ }, containers);
71
+ }
72
+
73
+ export function WithCoordinates<T extends Constructor>(Base: T) {
74
+ return class extends Base {
75
+ declare explorer: Explorer;
76
+ declare provider: Provider;
77
+ declare actionResult: ActionResult | undefined;
78
+
79
+ analyzeScreenshotForVisualProps(): Promise<VisualAnalysisResult> {
80
+ return this._analyzeScreenshotForVisualProps();
81
+ }
82
+
83
+ async visuallyAnnotateElements(opts?: { containers?: Array<{ css: string; label: string }> }): Promise<number> {
84
+ return visuallyAnnotateContainers(this.explorer.playwrightHelper.page, opts?.containers || []);
85
+ }
86
+
87
+ private async _analyzeScreenshotForVisualProps(): Promise<VisualAnalysisResult> {
88
+ const elements = new Map<number, { coordinates: string | null; color: string | null; icon: string | null }>();
89
+ const emptyResult: VisualAnalysisResult = { elements, pagePurpose: null, primaryActions: null, focusedSection: null };
90
+ if (!this.actionResult) return emptyResult;
91
+
92
+ const image = this.actionResult.screenshot;
93
+ if (!image) return emptyResult;
94
+ tag('step').log('Analyzing annotated screenshot for visual properties');
95
+
96
+ const prompt = dedent`
97
+ This screenshot has two types of annotations:
98
+ - **Section containers**: dashed bordered boxes (no labels on them). A legend at the bottom-left maps dashed line colors to section names. Ignore containers for this task.
99
+ - **Interactive elements**: solid bordered boxes with eidx numbers in the top-right corner above the box. Adjacent elements use different colors.
100
+
101
+ For each interactive element (solid border, eidx number), report:
102
+ | eidx | Coordinates | Color | Icon |
103
+
104
+ Column definitions:
105
+ - eidx: the number shown in the colored label above the top-right corner of each solid-bordered box
106
+ - Coordinates: (X, Y) center point of the element
107
+ - Color: accent color if distinctive (red, green, blue, orange, yellow, purple, gray), otherwise -
108
+ - Icon: one-word icon description (plus, x, trash, pencil, gear, search, hamburger, ellipsis, chevron, star, check, filter), otherwise -
109
+
110
+ Ignore the legend block and dashed container borders.
111
+
112
+ Then add:
113
+
114
+ ## Page Purpose
115
+ One sentence: what this page is for from the user's perspective.
116
+
117
+ ## Primary Actions
118
+ List 3-5 most prominent interactive elements (accent buttons, CTAs, main form actions).
119
+ Format: - action description
120
+
121
+ ## Focused Section
122
+ Which section from the legend appears to be the user's primary focus area?
123
+ Look for: modal overlays, drawers, panels with shadows or elevated z-index, highlighted/active areas.
124
+ If a dialog or overlay is visible, that is the focused section.
125
+ Otherwise, pick the section with the most prominent interactive content.
126
+ Reply with the exact section name from the legend. If no legend is shown, reply with "-".
127
+ `;
128
+
129
+ try {
130
+ const aiResult = await this.provider.processImage(prompt, image.toString('base64'));
131
+ const text = aiResult.text || '';
132
+ const rows = mdq(text).query('table').toJson();
133
+ for (const row of rows) {
134
+ const eidx = Number.parseInt(row.eidx, 10);
135
+ if (Number.isNaN(eidx)) continue;
136
+ const val = (v: string) => (v && v !== '-' ? v : null);
137
+ elements.set(eidx, {
138
+ coordinates: val(row.Coordinates),
139
+ color: val(row.Color),
140
+ icon: val(row.Icon),
141
+ });
142
+ }
143
+
144
+ const pagePurposeSection = mdq(text).query('section2("Page Purpose")').text().trim();
145
+ const primaryActionsSection = mdq(text).query('section2("Primary Actions")').text().trim();
146
+ const focusedSectionRaw = mdq(text).query('section2("Focused Section")').text().trim();
147
+ const focusedSection = focusedSectionRaw && focusedSectionRaw !== '-' ? focusedSectionRaw.split('\n')[0].trim() : null;
148
+
149
+ debugLog(`Parsed visual props for ${elements.size} elements`);
150
+ return {
151
+ elements,
152
+ pagePurpose: pagePurposeSection || null,
153
+ primaryActions: primaryActionsSection
154
+ ? primaryActionsSection
155
+ .split('\n')
156
+ .filter((l) => l.trim().startsWith('-'))
157
+ .map((l) => l.trim())
158
+ : null,
159
+ focusedSection,
160
+ };
161
+ } catch (err) {
162
+ debugLog(`Screenshot visual analysis failed: ${err instanceof Error ? err.message : err}`);
163
+ }
164
+
165
+ debugLog(`Parsed visual props for ${elements.size} elements`);
166
+ return emptyResult;
167
+ }
168
+
169
+ async mergeVisualData(result: ResearchResult, visualData: Map<number, { coordinates: string | null; color: string | null; icon: string | null }>): Promise<void> {
170
+ const sections = parseResearchSections(result.text);
171
+ let merged = 0;
172
+
173
+ for (const section of sections) {
174
+ let sectionMerged = false;
175
+ for (const el of section.elements) {
176
+ let eidx = el.eidx || null;
177
+ if (!eidx) {
178
+ const locator = el.css || el.xpath || (el.aria ? `role=${el.aria.role}[name="${el.aria.text}"]` : null);
179
+ if (locator) eidx = await this.explorer.getEidxByLocator(locator, section.containerCss);
180
+ }
181
+ if (!eidx) continue;
182
+
183
+ const vis = visualData.get(eidx);
184
+ if (!vis) continue;
185
+ Object.assign(el, Object.fromEntries(Object.entries(vis).filter(([, v]) => v)));
186
+ sectionMerged = true;
187
+ merged++;
188
+ }
189
+ if (sectionMerged) result.rebuildSectionInText(section);
190
+ }
191
+ debugLog(`Merged visual props for ${merged} elements`);
192
+ }
193
+
194
+ async backfillCoordinates(result: ResearchResult): Promise<void> {
195
+ const page = this.explorer.playwrightHelper.page;
196
+ const sections = parseResearchSections(result.text);
197
+ const eidxWithoutCoords: number[] = [];
198
+ for (const section of sections) {
199
+ for (const el of section.elements) {
200
+ if (el.eidx && !el.coordinates) eidxWithoutCoords.push(el.eidx);
201
+ }
202
+ }
203
+ if (eidxWithoutCoords.length === 0) return;
204
+
205
+ const webElements = await WebElement.fromEidxList(page, eidxWithoutCoords);
206
+ if (webElements.length === 0) return;
207
+
208
+ const rectMap = new Map(webElements.map((w) => [w.eidx!, w]));
209
+ for (const section of sections) {
210
+ let changed = false;
211
+ for (const el of section.elements) {
212
+ if (el.eidx && !el.coordinates) {
213
+ const w = rectMap.get(el.eidx);
214
+ if (w) {
215
+ el.coordinates = w.coordinates;
216
+ changed = true;
217
+ }
218
+ }
219
+ }
220
+ if (changed) result.rebuildSectionInText(section);
221
+ }
222
+ }
223
+ };
224
+ }
225
+
226
+ export interface VisualAnalysisResult {
227
+ elements: Map<number, { coordinates: string | null; color: string | null; icon: string | null }>;
228
+ pagePurpose: string | null;
229
+ primaryActions: string[] | null;
230
+ focusedSection: string | null;
231
+ }
232
+
233
+ export interface CoordinateMethods {
234
+ analyzeScreenshotForVisualProps(): Promise<VisualAnalysisResult>;
235
+ mergeVisualData(result: ResearchResult, visualData: Map<number, { coordinates: string | null; color: string | null; icon: string | null }>): Promise<void>;
236
+ backfillCoordinates(result: ResearchResult): Promise<void>;
237
+ visuallyAnnotateElements(opts?: { containers?: Array<{ css: string; label: string }> }): Promise<number>;
238
+ }