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,857 @@
1
+ import { join } from 'node:path';
2
+ import dedent from 'dedent';
3
+ import { ActionResult } from '../action-result.js';
4
+ import { setActivity } from '../activity.ts';
5
+ import { ConfigParser, outputPath } from '../config.ts';
6
+ import type { ExperienceTracker } from '../experience-tracker.ts';
7
+ import type Explorer from '../explorer.ts';
8
+ import type { KnowledgeTracker } from '../knowledge-tracker.ts';
9
+ import { Observability } from '../observability.ts';
10
+ import type { StateManager } from '../state-manager.js';
11
+ import { WebPageState } from '../state-manager.js';
12
+ import { Stats } from '../stats.ts';
13
+ import { diffAriaSnapshots } from '../utils/aria.ts';
14
+ import { isErrorPage } from '../utils/error-page.ts';
15
+ import { HooksRunner } from '../utils/hooks-runner.ts';
16
+ import { isBodyEmpty } from '../utils/html.ts';
17
+ import { createDebug, pluralize, tag } from '../utils/logger.js';
18
+ import { mdq } from '../utils/markdown-query.ts';
19
+ import { withRetry } from '../utils/retry.ts';
20
+ import { executionController } from '../execution-controller.ts';
21
+ import type { Agent } from './agent.js';
22
+ import type { Navigator } from './navigator.ts';
23
+ import { ContextLengthError, type Provider } from './provider.js';
24
+ import { findSimilarResearch, getCachedResearch, saveResearch } from './researcher/cache.ts';
25
+ import { type CoordinateMethods, WithCoordinates } from './researcher/coordinates.ts';
26
+ import { type DeepAnalysisMethods, WithDeepAnalysis } from './researcher/deep-analysis.ts';
27
+ import { detectFocusFromAria, hasFocusedSection, markSectionAsFocused, pickDefaultFocusedSection } from './researcher/focus.ts';
28
+ import { type LocatorMethods, WithLocators } from './researcher/locators.ts';
29
+ import { extractValidContainers, formatResearchSummary, parseResearchSections } from './researcher/parser.ts';
30
+ import { ResearchResult } from './researcher/research-result.ts';
31
+ import { locatorRule as generalLocatorRuleText } from './rules.js';
32
+ import { RulesLoader } from '../utils/rules-loader.ts';
33
+ import { TaskAgent } from './task-agent.ts';
34
+
35
+ export type { Locator } from './researcher/locators.ts';
36
+
37
+ const debugLog = createDebug('explorbot:researcher');
38
+
39
+ export const POSSIBLE_SECTIONS = {
40
+ overlay: 'dialog, modal, drawer, popup, or active form overlay',
41
+ list: 'list area (items collection, table, cards, or list view)',
42
+ detail: 'detail area (selected item preview or full details)',
43
+ panes: 'screen is split into equal panes, describe each pane',
44
+ content: 'main area of page',
45
+ menu: 'page menu (toolbar, context actions, filters, dropdowns)',
46
+ navigation: 'main navigation (top bar, sidebar, breadcrumbs)',
47
+ };
48
+
49
+ const ResearcherBase = WithDeepAnalysis(WithCoordinates(WithLocators(TaskAgent as unknown as new (...args: any[]) => TaskAgent)));
50
+
51
+ export interface Researcher extends LocatorMethods, CoordinateMethods, DeepAnalysisMethods {}
52
+
53
+ export class Researcher extends ResearcherBase implements Agent {
54
+ protected readonly ACTION_TOOLS = ['click'];
55
+ emoji = '🔍';
56
+ declare explorer: Explorer;
57
+ declare provider: Provider;
58
+ declare stateManager: StateManager;
59
+ private experienceTracker!: ExperienceTracker;
60
+ private hasScreenshotToAnalyze = false;
61
+ declare actionResult: ActionResult | undefined;
62
+ private hooksRunner!: HooksRunner;
63
+
64
+ constructor(explorer: Explorer, provider: Provider) {
65
+ super();
66
+ this.explorer = explorer;
67
+ this.provider = provider;
68
+ this.stateManager = explorer.getStateManager();
69
+ this.experienceTracker = this.stateManager.getExperienceTracker();
70
+ this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
71
+ }
72
+
73
+ protected getNavigator(): Navigator {
74
+ throw new Error('not implemented');
75
+ }
76
+
77
+ protected getExperienceTracker(): ExperienceTracker {
78
+ return this.experienceTracker;
79
+ }
80
+
81
+ protected getKnowledgeTracker(): KnowledgeTracker {
82
+ return this.explorer.getKnowledgeTracker();
83
+ }
84
+
85
+ protected getProvider(): Provider {
86
+ return this.provider;
87
+ }
88
+
89
+ static getCachedResearch(state: WebPageState): string {
90
+ return getCachedResearch(state.hash || '');
91
+ }
92
+
93
+ getSystemMessage(): string {
94
+ const currentUrl = this.stateManager.getCurrentState()?.url;
95
+ const customPrompt = this.provider.getSystemPromptForAgent('researcher', currentUrl);
96
+ return dedent`
97
+ <role>
98
+ You are senior QA focused on exploritary testig of web application.
99
+ </role>
100
+
101
+ <wording>
102
+ In the UI map and all descriptions, name concrete UI parts (visible labels, headings, regions, ARIA roles). Do not use vague placeholders like "the page", "the element", "the button", "the input", "the link", "the form", "the table", "the list", or "the item". Do not use filler such as "comprehensive", "All required", "All elements", or "All necessary".
103
+ </wording>
104
+
105
+ ${customPrompt || ''}
106
+ `;
107
+ }
108
+
109
+ async research(state: WebPageState, opts: { screenshot?: boolean; force?: boolean; deep?: boolean; data?: boolean; fix?: boolean; _retriesLeft?: number; _skipErrorPageRetry?: boolean } = {}): Promise<string> {
110
+ const { screenshot = false, force = false, deep = false, data = false, fix = true } = opts;
111
+ const maxRetries = (this.explorer.getConfig().ai?.agents?.researcher as any)?.retries ?? 2;
112
+ let retriesLeft = opts._retriesLeft ?? maxRetries;
113
+ this.actionResult = ActionResult.fromState(state);
114
+ const stateHash = state.hash || this.actionResult.getStateHash();
115
+
116
+ if (!force && stateHash) {
117
+ const cached = getCachedResearch(stateHash);
118
+ if (cached) {
119
+ debugLog('Previous research result found');
120
+ return `!! UI MAP IS CACHED AND MAY NOT REPRESENT CURRENT STATE; REFRESH RESEARCH IF YOU NOTICE ISSUES !!\n\n${cached}`;
121
+ }
122
+ }
123
+
124
+ Stats.researches++;
125
+
126
+ const sessionName = `researcher: ${state.url}`;
127
+ return Observability.run(sessionName, { tags: ['researcher'], sessionId: stateHash }, async () => {
128
+ tag('info').log(`Researching ${state.url} to understand the context...`);
129
+ setActivity(`${this.emoji} Researching...`, 'action');
130
+
131
+ await this.ensureNavigated(state.url, screenshot && this.provider.hasVision());
132
+ await this.hooksRunner.runBeforeHook('researcher', state.url);
133
+
134
+ const annotatedCount = await this.explorer.annotateElements();
135
+ debugLog(`Annotated ${annotatedCount} interactive elements with eidx`);
136
+ this.actionResult = await this.explorer.createAction().capturePageState({ includeScreenshot: screenshot && this.provider.hasVision() });
137
+
138
+ if (isErrorPage(this.actionResult!)) {
139
+ const recovered = await this.waitForPageLoad(screenshot);
140
+ if (!recovered) {
141
+ tag('warning').log(`Detected error page at ${state.url}`);
142
+ return dedent`
143
+ ## Error Page Detected
144
+
145
+ URL: ${state.url}
146
+ Title: ${this.actionResult!.title || 'N/A'}
147
+
148
+ Research skipped. Navigate to a valid page to continue.
149
+ `;
150
+ }
151
+ }
152
+
153
+ debugLog('Researching web page:', this.actionResult!.url);
154
+
155
+ const combinedHtml = await this.actionResult!.combinedHtml();
156
+
157
+ if (!deep) {
158
+ const similar = await findSimilarResearch(combinedHtml);
159
+ if (similar) {
160
+ tag('info').log('Similar research found, reusing cached result');
161
+ if (stateHash) saveResearch(stateHash, similar, combinedHtml);
162
+ tag('multiline').log(formatResearchSummary(similar));
163
+ tag('success').log(`Research complete! ${similar.length} characters (reused)`);
164
+ await this.hooksRunner.runAfterHook('researcher', state.url);
165
+ return similar;
166
+ }
167
+ }
168
+
169
+ const isOnCurrentState = this.actionResult!.getStateHash() === this.stateManager.getCurrentState()?.hash;
170
+ this.hasScreenshotToAnalyze = screenshot && this.provider.hasVision() && isOnCurrentState;
171
+
172
+ const conversation = this.provider.startConversation(this.getSystemMessage(), 'researcher');
173
+
174
+ const prompt = await this.buildResearchPrompt();
175
+ conversation.addUserText(prompt);
176
+
177
+ let invocationResult: Awaited<ReturnType<typeof this.provider.invokeConversation>>;
178
+ try {
179
+ invocationResult = await this.provider.invokeConversation(conversation, undefined, { agentName: 'researcher' });
180
+ } catch (error) {
181
+ if (!(error instanceof ContextLengthError) || retriesLeft <= 0) {
182
+ if (error instanceof ContextLengthError) {
183
+ tag('warning').log('Output truncated. Try lowering reasoning effort or increasing maxTokens in ai.config.');
184
+ }
185
+ throw error;
186
+ }
187
+ tag('warning').log('Output truncated, retrying with focused instructions...');
188
+ retriesLeft = 0;
189
+ conversation.addUserText(this.buildFocusedRetryPrompt());
190
+ invocationResult = await this.provider.invokeConversation(conversation, undefined, { agentName: 'researcher' });
191
+ }
192
+ if (!invocationResult) throw new Error('Failed to get response from provider');
193
+
194
+ const result = new ResearchResult(invocationResult.response.text, state.url);
195
+ debugLog(`Original research response length: ${result.text.length} chars`);
196
+
197
+ const errorSection = mdq(result.text).query('section("Error Page Detected")');
198
+ if (errorSection.count() > 0) {
199
+ if (result.text.length < 500) {
200
+ if (!opts._skipErrorPageRetry && (await this.waitForPageLoad(screenshot))) {
201
+ return this.research(state, { ...opts, force: true, _skipErrorPageRetry: true });
202
+ }
203
+ tag('warning').log(`AI detected error page at ${state.url}`);
204
+ if (stateHash) saveResearch(stateHash, result.text);
205
+ await this.hooksRunner.runAfterHook('researcher', state.url);
206
+ return result.text;
207
+ }
208
+ result.text = errorSection.replace('');
209
+ }
210
+
211
+ const interrupted = () => executionController.isInterrupted();
212
+
213
+ // Stage 2: Test containers + locators
214
+ result.parseLocators();
215
+ debugLog(`Extracted ${result.locators.length} locators from research`);
216
+
217
+ if (!interrupted() && result.locators.length === 0 && retriesLeft > 0) {
218
+ tag('warning').log(`No locators parsed, retrying research (${maxRetries - retriesLeft + 1}/${maxRetries})...`);
219
+ await new Promise((r) => setTimeout(r, 1000));
220
+ return this.research(state, { ...opts, force: true, _retriesLeft: retriesLeft - 1 } as any);
221
+ }
222
+
223
+ if (!interrupted()) {
224
+ const containerLocs = result.containerLocators;
225
+ await this.testLocators(containerLocs);
226
+ const brokenContainers = containerLocs.filter((l) => l.valid === false);
227
+ if (containerLocs.length > 0 && brokenContainers.length === containerLocs.length && retriesLeft > 0) {
228
+ tag('warning').log(`All ${containerLocs.length} containers broken, retrying research (${maxRetries - retriesLeft + 1}/${maxRetries})...`);
229
+ await new Promise((r) => setTimeout(r, 2000));
230
+ return this.research(state, { ...opts, force: true, _retriesLeft: retriesLeft - 1 } as any);
231
+ }
232
+
233
+ for (const loc of result.locators) {
234
+ if (loc.container && brokenContainers.some((c) => c.locator === loc.container)) {
235
+ loc.valid = false;
236
+ loc.error = 'container broken';
237
+ }
238
+ }
239
+
240
+ const toTest = result.locators.filter((l) => l.valid === null);
241
+ await this.testLocators(toTest);
242
+
243
+ const brokenCount = result.locators.filter((l) => l.valid === false).length;
244
+ const brokenRatio = result.locators.length > 0 ? brokenCount / result.locators.length : 0;
245
+ if (brokenRatio > 0.8 && retriesLeft > 0) {
246
+ tag('warning').log(`${Math.round(brokenRatio * 100)}% locators broken, waiting 3s and retrying research (${maxRetries - retriesLeft + 1}/${maxRetries})...`);
247
+ await new Promise((r) => setTimeout(r, 3000));
248
+ return this.research(state, { ...opts, force: true, _retriesLeft: retriesLeft - 1 } as any);
249
+ }
250
+ }
251
+
252
+ // Stage 3: Fix broken sections via AI conversation continuation
253
+ if (!interrupted() && fix && result.locators.some((l) => l.valid === false)) {
254
+ await this.fixBrokenSections(result, conversation);
255
+ }
256
+
257
+ // Focused section: parse AI declaration, then ARIA fallback
258
+ const focusMatch = result.text.match(/^>\s*Focused:\s*(.+)/m);
259
+ if (focusMatch) {
260
+ result.text = result.text.replace(focusMatch[0], '');
261
+ markSectionAsFocused(result, focusMatch[1].trim());
262
+ }
263
+ if (!hasFocusedSection(result.text)) {
264
+ const sections = parseResearchSections(result.text);
265
+ const ariaSnapshot = this.actionResult?.getCompactARIA() || '';
266
+ const focusedName = detectFocusFromAria(ariaSnapshot, sections);
267
+ if (focusedName) markSectionAsFocused(result, focusedName);
268
+ }
269
+
270
+ // Stage 4: Visual analysis
271
+ if (!interrupted() && this.hasScreenshotToAnalyze) {
272
+ const validContainers = extractValidContainers(result.text);
273
+ result.parseLocators();
274
+ const freshContainerLocs = result.containerLocators;
275
+ await this.testLocators(freshContainerLocs);
276
+ const freshBroken = freshContainerLocs.filter((l) => l.valid === false).map((l) => l.locator);
277
+ const containers = validContainers.filter((c) => !freshBroken.includes(c.css));
278
+ await this.visuallyAnnotateElements({ containers });
279
+ this.actionResult = await this.explorer.createAction().caputrePageWithScreenshot();
280
+ const visualResult = await this.analyzeScreenshotForVisualProps();
281
+ if (visualResult.elements.size > 0) {
282
+ await this.mergeVisualData(result, visualResult.elements);
283
+ result.parseLocators();
284
+ }
285
+ if (visualResult.pagePurpose || visualResult.primaryActions?.length) {
286
+ const lines: string[] = ['## Primary Actions', ''];
287
+ if (visualResult.pagePurpose) lines.push(visualResult.pagePurpose, '');
288
+ if (visualResult.primaryActions?.length) lines.push(...visualResult.primaryActions);
289
+ result.text = `${lines.join('\n')}\n\n${result.text}`;
290
+ }
291
+
292
+ // Focused section: visual fallback
293
+ if (!hasFocusedSection(result.text) && visualResult.focusedSection) {
294
+ markSectionAsFocused(result, visualResult.focusedSection);
295
+ }
296
+ }
297
+
298
+ // Stage 5: Backfill broken elements
299
+ if (!interrupted()) {
300
+ await this.backfillCoordinates(result);
301
+ await this.backfillBrokenLocators(result);
302
+ }
303
+
304
+ // Focused section: final fallback
305
+ if (!hasFocusedSection(result.text)) {
306
+ const sections = parseResearchSections(result.text);
307
+ const fallback = pickDefaultFocusedSection(sections);
308
+ if (fallback) markSectionAsFocused(result, fallback);
309
+ }
310
+
311
+ if (!interrupted() && deep) {
312
+ await this.performDeepAnalysis(state, result);
313
+ }
314
+
315
+ if (!interrupted() && data) {
316
+ const extractedData = await this.extractData(state);
317
+ result.text += `\n\n## Data\n\n${extractedData}`;
318
+ }
319
+
320
+ if (interrupted()) {
321
+ tag('info').log('Research interrupted, returning partial result');
322
+ }
323
+
324
+ result.cleanup();
325
+
326
+ let researchFile: string | null = null;
327
+ if (stateHash) {
328
+ researchFile = saveResearch(stateHash, result.text, combinedHtml);
329
+ }
330
+
331
+ const summaryMatch = result.text.match(/## Summary\s*\n+([\s\S]*?)(?=\n##|$)/i);
332
+ if (summaryMatch) {
333
+ const summaryLine = summaryMatch[1].trim().split('\n')[0].trim().slice(0, 200);
334
+ if (summaryLine) this.experienceTracker.updateSummary(this.actionResult!, summaryLine);
335
+ }
336
+
337
+ tag('multiline').log(formatResearchSummary(result.text, { visionUsed: this.hasScreenshotToAnalyze }));
338
+ tag('success').log(`Research complete! ${result.text.length} characters`);
339
+ if (researchFile) tag('substep').log(`Research file saved to: ${researchFile}`);
340
+ if (this.actionResult?.screenshotFile) {
341
+ const screenshotPath = outputPath('states', this.actionResult.screenshotFile);
342
+ tag('substep').log(`UI screenshot: file://${screenshotPath}`);
343
+ }
344
+
345
+ await this.hooksRunner.runAfterHook('researcher', state.url);
346
+ return result.text;
347
+ });
348
+ }
349
+
350
+ private async ensureNavigated(url: string, screenshot?: boolean): Promise<void> {
351
+ if (!this.actionResult) {
352
+ debugLog('No action result, navigating to URL');
353
+ await this.explorer.visit(url);
354
+ this.actionResult = await this.explorer.createAction().capturePageState({ includeScreenshot: screenshot });
355
+ return;
356
+ }
357
+
358
+ const isOnCurrentState = this.actionResult.getStateHash() === this.stateManager.getCurrentState()?.hash;
359
+ const stateHtml = await this.actionResult.combinedHtml();
360
+ const isEmpty = isBodyEmpty(stateHtml);
361
+
362
+ if (!isEmpty && isOnCurrentState) {
363
+ if ((!this.actionResult.screenshot && screenshot) || !this.actionResult.ariaSnapshot) {
364
+ this.actionResult = await this.explorer.createAction().capturePageState({ includeScreenshot: screenshot });
365
+ }
366
+ return;
367
+ }
368
+
369
+ if (isEmpty) {
370
+ debugLog('HTML body is empty, refreshing page');
371
+ tag('step').log('Page body is empty, refreshing...');
372
+ } else {
373
+ debugLog('Not on current state, navigating to URL');
374
+ tag('step').log('Navigating to URL...');
375
+ }
376
+
377
+ await this.explorer.visit(url);
378
+ this.actionResult = await this.explorer.createAction().capturePageState({ includeScreenshot: screenshot ?? false });
379
+ }
380
+
381
+ private async waitForPageLoad(screenshot: boolean): Promise<boolean> {
382
+ const errorPageTimeout = (this.explorer.getConfig().ai?.agents?.researcher as any)?.errorPageTimeout ?? 10;
383
+ if (errorPageTimeout <= 0) return false;
384
+
385
+ try {
386
+ await withRetry(
387
+ async () => {
388
+ await this.explorer.annotateElements();
389
+ this.actionResult = await this.explorer.createAction().capturePageState({
390
+ includeScreenshot: screenshot && this.provider.hasVision(),
391
+ });
392
+ if (isErrorPage(this.actionResult!)) throw new Error('Error page detected');
393
+ },
394
+ {
395
+ maxAttempts: Math.ceil(errorPageTimeout / 3) + 1,
396
+ baseDelay: 1000,
397
+ maxDelay: 5000,
398
+ backoffMultiplier: 2,
399
+ retryCondition: (e) => e.message === 'Error page detected',
400
+ }
401
+ );
402
+ return true;
403
+ } catch {
404
+ return false;
405
+ }
406
+ }
407
+
408
+ private getConfiguredSections(): Record<string, string> {
409
+ const configSections = (this.explorer.getConfig().ai?.agents?.researcher as any)?.sections as string[] | undefined;
410
+ if (!configSections?.length) return POSSIBLE_SECTIONS;
411
+ const filtered: Record<string, string> = {};
412
+ for (const key of configSections) {
413
+ if (key in POSSIBLE_SECTIONS) filtered[key] = POSSIBLE_SECTIONS[key as keyof typeof POSSIBLE_SECTIONS];
414
+ }
415
+ return Object.keys(filtered).length > 0 ? filtered : POSSIBLE_SECTIONS;
416
+ }
417
+
418
+ private researchRules(): string {
419
+ const sections = this.getConfiguredSections();
420
+ return dedent`
421
+ <task>
422
+ Examine the provided page and explain its main purpose from the user perspective.
423
+ Identify the main user actions of this page.
424
+ Break down the page by sections and identify structural patterns.
425
+ Provide a comprehensive UI map report in markdown format.
426
+ </task>
427
+
428
+ <rules>
429
+ - Explain what the user can achieve on this page.
430
+ - Focus on primary user actions and interactive elements only.
431
+ - Research all menus and navigational areas.
432
+ - Ignore purely decorative sidebars, footer-only links, and external links.
433
+ - Detect layout patterns: list/detail split, 2-pane, or 3-pane layouts.
434
+ - If multiple elements match, pick the element inside the most relevant section and closest to recent UI context.
435
+ - UI map table must include ARIA and CSS for every element.
436
+ - Every element MUST have a CSS selector. NEVER leave CSS as "-".
437
+ - For icon-only buttons with empty aria-label, set ARIA to "-" but ALWAYS provide CSS.
438
+ - NEVER skip elements that have an eidx attribute. Every element with eidx MUST appear in the UI map table, even if it has no text or accessible name. Describe icon-only elements using their SVG class (e.g., md-icon-dots-horizontal → "More actions (ellipsis)") or their visual appearance.
439
+ - ARIA locator must be JSON with role and text keys (NOT "name").
440
+ - Note elements likely to have hover interactions (elements with title attribute, aria-describedby, navigation menu items with submenus) and mark them with "(hover)" in the UI map.
441
+ </rules>
442
+
443
+ ${generalLocatorRuleText}
444
+
445
+ ${RulesLoader.loadRules('researcher', ['ui-map-table', 'list-element'], this.stateManager.getCurrentState()?.url || '')}
446
+
447
+ <section_identification>
448
+ Identify page sections in this priority order:
449
+ ${Object.entries(sections)
450
+ .map(([name, description]) => `* ${name}: ${description}`)
451
+ .join('\n')}
452
+
453
+ - Sections can overlap, prefer more detailed sections over broader ones.
454
+ - Never name a section "Focus" or "Focused" — describe what the section actually contains (e.g., "Detail", "Modal", "Form", "Content", "List").
455
+ - If a proposed section is not relevant or not detected, do not include it.
456
+ - Each section must have a container CSS locator.
457
+ - UI map CSS locators must be relative to the section container.
458
+ </section_identification>
459
+
460
+ <container_rules>
461
+ CRITICAL: Container CSS must be a SINGLE selector — one class, one ID, or one attribute.
462
+ No spaces, no >, no combinators, no nesting.
463
+ - INVALID: '.filterbar-filter-btn-div button', 'div.static nav', 'div > .content'
464
+ - INVALID: 'div', 'section', 'nav', 'div:first' (bare tags are not containers)
465
+ - INVALID: Tailwind/Bootstrap utility classes that describe layout or styling (e.g. flex-none, d-flex, col-md-6, items-center, mt-4, p-2, bg-white, text-sm, rounded-lg). These are visual, not semantic.
466
+ - VALID: Semantic class names that describe WHAT the section IS — e.g. '.product-list', '.sidebar-menu', '.user-profile', '[role="dialog"]', '.search-results'
467
+ Container must uniquely identify a semantic wrapper, not a path through the DOM.
468
+ </container_rules>
469
+
470
+ <section_format>
471
+ ## Section Name
472
+
473
+ Explanation of this section and its purpose.
474
+
475
+ > Container: '.container-css-selector'
476
+
477
+ | Element | ARIA | CSS | eidx |
478
+ </section_format>
479
+ <section_example>
480
+ ## List
481
+
482
+ Product catalog showing available items with sorting and filtering.
483
+
484
+ > Container: '.product-list'
485
+
486
+ | Element | ARIA | CSS | eidx |
487
+ | 'Sort by price' | { role: 'button', text: 'Sort by price' } | '.sort-btn' | 3 |
488
+ | 'Add to cart' | { role: 'button', text: 'Add to cart' } | '.add-btn' | 4 |
489
+ | 'Product name' | { role: 'link', text: 'Widget Pro' } | 'a.product-link' | 5 |
490
+ </section_example>
491
+
492
+ <focused_section>
493
+ At the very end of your output, add a single line declaring which section is the user's primary focus area:
494
+
495
+ > Focused: <exact section name>
496
+
497
+ Rules for determining the focused section:
498
+ - If the page has a dialog, modal, drawer, or overlay — that section is focused
499
+ - If no overlay exists, pick the section where the user performs the main business action of this page (e.g., a list section for a catalog page, a detail section for an item page, a content section for an article page)
500
+ - Navigation is NEVER focused — it exists on every page
501
+ - Menu/toolbar is NEVER focused — it contains actions, not the main content
502
+ - The focused section is the one the user came to this page to interact with
503
+ </focused_section>
504
+
505
+ <css_selector_rules>
506
+ CSS selectors MUST point to the actual interactive element (input, button, a, select), NOT to container divs.
507
+ - If a submit button is inside a wrapper div, target the input/button directly
508
+ - Bad: '#submit-wrapper' (div container)
509
+ - Good: '#submit-wrapper input[type="submit"]' or 'input[type="submit"][value="Submit"]'
510
+ - For buttons with similar text, include distinguishing attributes like type, value, or form context
511
+
512
+ </css_selector_rules>
513
+ `;
514
+ }
515
+
516
+ private async buildResearchPrompt(): Promise<string> {
517
+ if (!this.actionResult) throw new Error('actionResult is not set');
518
+
519
+ const html = await this.actionResult.combinedHtml();
520
+ const knowledgeFiles = this.stateManager.getRelevantKnowledge();
521
+
522
+ let knowledge = '';
523
+ if (knowledgeFiles.length > 0) {
524
+ const knowledgeContent = knowledgeFiles
525
+ .map((k) => k.content)
526
+ .filter((k) => !!k)
527
+ .join('\n\n');
528
+
529
+ tag('substep').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')} for: ${this.actionResult.url}`);
530
+ knowledge = `
531
+ <hint>
532
+ Here is relevant knowledge for this page:
533
+
534
+ ${knowledgeContent}
535
+ </hint>`;
536
+ }
537
+
538
+ const ariaSnapshot = this.actionResult.getCompactARIA();
539
+
540
+ return dedent`
541
+ Analyze this web page and provide a comprehensive research report in markdown format.
542
+
543
+ <error_detection>
544
+ IMPORTANT: First check if this looks like an error page (404, 500, access denied,
545
+ not found, server error, forbidden, or similar). If so, respond ONLY with:
546
+
547
+ ## Error Page Detected
548
+ Type: [error type]
549
+ Reason: [what indicates this is an error page]
550
+
551
+ Then stop - do not provide normal research output for error pages.
552
+ </error_detection>
553
+
554
+ ${this.researchRules()}
555
+
556
+ URL: ${this.actionResult.url || 'Unknown'}
557
+ Title: ${this.actionResult.title || 'Unknown'}
558
+
559
+ <eidx_mapping>
560
+ Elements have \`eidx\` attribute (e.g. \`eidx="5"\`) — include its value in the eidx column.
561
+ Never include \`eidx\` attribute in CSS or XPath selectors.
562
+ </eidx_mapping>
563
+
564
+ <context>
565
+ HTML Content:
566
+ ${html}
567
+
568
+ ${ariaSnapshot ? `ARIA Tree:\n${ariaSnapshot}` : ''}
569
+ </context>
570
+
571
+ ${knowledge}
572
+
573
+ <output>
574
+
575
+ <output_rules>
576
+ - Please provide a structured analysis in markdown format divided by sections
577
+ - Use tables for section UI maps only.
578
+ - List sections by provided priorities: ${Object.keys(this.getConfiguredSections()).join(', ')}
579
+ - If a section is not present, do not include it in the output.
580
+ - Include coordinates when available from screenshot analysis. Use "-" when not available.
581
+ - If some sections are not present, do not include them in the output.
582
+ - Proposed sections must be relevant to the page.
583
+ - List all interactive elements on page and put them into appropriate sections.
584
+ - Group similar interactive elements (like dynamic lists or content) into one item
585
+ - At minimum include Main section if no other sections are clear.
586
+ - For pane sections, explain the relationship between panes.
587
+ - Each section must include only its relevant UI elements.
588
+ - Follow <section_format> and provided <section_example> when describing sections.
589
+ - When a section contains a list of similar data items (records, entities, rows — content that varies by data, not by app UI), output it as a Data section with NO table.
590
+ - Data section heading MUST be a level-2 heading (##) that starts exactly with "Data:" — for example: "## Data: Suites List". Do NOT use ### or add section numbers.
591
+ - Data sections must NOT include a UI map table. Only include the container and a brief summary line.
592
+ - Example data section:
593
+
594
+ ## Data: Suites List
595
+
596
+ > Container: \`.suites-list-content\`
597
+
598
+ Suite items, 13 items. List of test suites with expand/collapse buttons.
599
+ </output_rules>
600
+
601
+
602
+ `;
603
+ }
604
+
605
+ private buildFocusedRetryPrompt(): string {
606
+ return dedent`
607
+ Your previous response was truncated and could not be parsed.
608
+
609
+ Please retry with a shorter output. Focus ONLY on the main interactive section of the page.
610
+ Skip navigation, sidebar, and footer sections. Output ONE section only with max 15 elements.
611
+ `;
612
+ }
613
+
614
+ async textContent(state: WebPageState): Promise<string> {
615
+ const actionResult = ActionResult.fromState(state);
616
+ const html = await actionResult.combinedHtml();
617
+
618
+ const prompt = dedent`
619
+ Transform into markdown.
620
+ Identify headers, footers, asides, special application parts and main contant.
621
+ Content should be in markdown format. If it is content: tables must be tables, lists must be lists.
622
+ Navigation elements should be represented as standalone blocks after the content.
623
+ Do not summarize content, just transform it into markdown.
624
+ It is important to list all the content text
625
+ If it is link it must be linked
626
+ You can summarize footers/navigation/aside elements.
627
+ But main conteint should be kept as text and formatted as markdown based on its current markup.
628
+ Links to external web sites should be avoided in output.
629
+
630
+ Break down into sections:
631
+
632
+ ## Content Area
633
+
634
+ ## Navigation Area
635
+
636
+ <page_html>
637
+ ${html}
638
+ </page_html>
639
+ `;
640
+
641
+ const model = this.provider.getModelForAgent('researcher');
642
+ const r = await this.provider.chat([{ role: 'user', content: prompt }], model, { agentName: 'researcher', telemetryFunctionId: 'researcher.textContent' });
643
+
644
+ return r.text;
645
+ }
646
+
647
+ private getScreenshotFromState(state: WebPageState): { actionResult: ActionResult; image: Buffer } | null {
648
+ const actionResult = ActionResult.fromState(state);
649
+ const image = actionResult.screenshot;
650
+ if (!image) {
651
+ debugLog('No screenshot found', actionResult);
652
+ return null;
653
+ }
654
+ return { actionResult, image };
655
+ }
656
+
657
+ async checkElementLocation(state: WebPageState, elementDescription: string): Promise<string | null> {
658
+ const screenshotData = this.getScreenshotFromState(state);
659
+ if (!screenshotData) return null;
660
+
661
+ const { actionResult, image } = screenshotData;
662
+ tag('step').log('Checking element location on screenshot');
663
+ const prompt = dedent`
664
+ <role>
665
+ You are a precise UI inspector focused on confirming a single interface element on a webpage screenshot.
666
+ </role>
667
+
668
+ <task>
669
+ Focus ONLY on the element described as: "${elementDescription}".
670
+ Determine whether it is present and usable. If you locate it, respond with a short sentence describing the element and include its coordinates in the format "<description> at <x>X, <y>Y" (numbers must be integers followed by X and Y respectively).
671
+ If it is not visible or cannot be reached, explain that it was not found and provide a brief suggestion.
672
+ Do not list other elements.
673
+ </task>
674
+
675
+ <rules>
676
+ - Keep the answer under two sentences.
677
+ - Mention if the element is obscured or disabled when applicable.
678
+ - Always include coordinates only when the element is found.
679
+ - Coordinates must follow the pattern "123X, 456Y" with X and Y suffixes.
680
+ </rules>
681
+
682
+ URL: ${actionResult.url || 'Unknown'}
683
+ Title: ${actionResult.title || 'Unknown'}
684
+
685
+ The screenshot is provided below.
686
+ `;
687
+
688
+ const r = await this.provider.processImage(prompt, image.toString('base64'));
689
+ return r.text;
690
+ }
691
+
692
+ async answerQuestionAboutScreenshot(state: WebPageState, question: string): Promise<string | null> {
693
+ const screenshotData = this.getScreenshotFromState(state);
694
+ if (!screenshotData) return null;
695
+
696
+ const { actionResult, image } = screenshotData;
697
+ tag('step').log('Answering question about screenshot');
698
+ const prompt = dedent`
699
+ <role>
700
+ You are a UI analyst examining a webpage screenshot to answer specific questions about its state or content.
701
+ </role>
702
+
703
+ <task>
704
+ Answer the following question about the webpage screenshot: "${question}"
705
+
706
+ Examine the screenshot carefully and provide a clear, concise answer based on what you observe.
707
+ Be specific and factual in your response.
708
+ If the question cannot be answered from the screenshot alone, explain what information is missing.
709
+ </task>
710
+
711
+ <rules>
712
+ - Provide a direct answer to the question.
713
+ - Be specific and reference visual elements when relevant.
714
+ - If the answer requires checking form fields, buttons, or other UI elements, describe their state clearly.
715
+ - Keep the response focused and under 5 sentences unless more detail is needed.
716
+ </rules>
717
+
718
+ URL: ${actionResult.url || 'Unknown'}
719
+ Title: ${actionResult.title || 'Unknown'}
720
+
721
+ The screenshot is provided below.
722
+ `;
723
+
724
+ const r = await this.provider.processImage(prompt, image.toString('base64'));
725
+ return r.text;
726
+ }
727
+
728
+ async extractData(state: WebPageState): Promise<string> {
729
+ const actionResult = ActionResult.fromState(state);
730
+ tag('step').log('Extracting data from page');
731
+
732
+ const html = await actionResult.combinedHtml();
733
+
734
+ const prompt = dedent`
735
+ <task>
736
+ Extract all domain-specific content data items from this HTML page.
737
+ Focus only on actual content entities that represent business data, not navigation or UI controls.
738
+ </task>
739
+
740
+ <rules>
741
+ Include:
742
+ - Articles, posts, products, items, entries, records, cards, listings
743
+ - User profiles, accounts, entities
744
+ - Documents, files, resources that can be accessed individually
745
+ - Any data items that have their own detail pages
746
+ - Content that represents actual domain entities (users, products, tasks, etc.)
747
+
748
+ Exclude:
749
+ - Navigation menus, breadcrumbs, pagination controls
750
+ - Buttons, form inputs, search boxes, filters
751
+ - Headers, footers, sidebars (unless they contain actual content items)
752
+ - UI controls, toolbars, action buttons
753
+ - Links that are purely navigational (home, about, contact, etc.)
754
+ - Decorative elements, logos, icons without content
755
+ - Empty placeholders or loading states
756
+
757
+ Type requirements:
758
+ - The type field must be explicit and specific to the domain
759
+ - Use precise type names: "article", "comment", "product", "review", "order", etc.
760
+ - Never use generic terms like "content", "item", "element", "entry", "data"
761
+ - Infer the specific type from context, HTML structure, URL patterns, or semantic meaning
762
+ - Each distinct domain entity type should have its own specific name
763
+ </rules>
764
+
765
+ <output_format>
766
+ Return a markdown table with columns: type | title | link | meta
767
+
768
+ - type: Explicit and specific category of the content item. Must be a precise type name, not generic terms.
769
+ Examples: "article", "comment", "product", "user", "task", "document", "post", "review", "order", "project".
770
+ DO NOT use generic types like "content", "item", "element", "entry" - always use the specific domain type.
771
+ If unsure, infer the type from the context, URL structure, or surrounding elements.
772
+ - title: Display name or heading of the item
773
+ - link: URL or relative path to the item's detail page if available, otherwise "-"
774
+ - meta: Additional metadata (author, date, status, etc.) as key-value pairs or "-"
775
+ </output_format>
776
+
777
+ <example>
778
+ For a blog listing page with comments, extract:
779
+ | type | title | link | meta |
780
+ | article | "Getting Started with Testing" | "/articles/getting-started" | "author: John, date: 2024-01-15" |
781
+ | article | "Advanced Patterns" | "/articles/advanced-patterns" | "author: Jane, date: 2024-01-20" |
782
+ | comment | "Great tutorial!" | "/articles/getting-started#comment-1" | "author: Alice, date: 2024-01-16" |
783
+
784
+ Correct types: "article", "comment", "product", "review", "user", "order"
785
+ Incorrect types: "content", "item", "element", "entry", "data"
786
+
787
+ Do NOT include navigation like:
788
+ - "Home" link
789
+ - "Next Page" button
790
+ - Search input field
791
+ - Category filter dropdown
792
+ </example>
793
+
794
+ URL: ${actionResult.url || 'Unknown'}
795
+ Title: ${actionResult.title || 'Unknown'}
796
+
797
+ HTML:
798
+ ${html}
799
+ `;
800
+
801
+ const model = this.provider.getModelForAgent('researcher');
802
+ const r = await this.provider.chat([{ role: 'user', content: prompt }], model, { agentName: 'researcher', telemetryFunctionId: 'researcher.extractData' });
803
+
804
+ return r.text;
805
+ }
806
+
807
+ async summary(state: WebPageState, opts: { allowNewResearch?: boolean } = {}): Promise<string> {
808
+ const { allowNewResearch = false } = opts;
809
+ let researchText = Researcher.getCachedResearch(state);
810
+ if (!researchText && allowNewResearch) {
811
+ researchText = await this.research(state);
812
+ }
813
+ if (!researchText) return '';
814
+ return this.extractBrief(researchText);
815
+ }
816
+
817
+ extractBrief(researchText: string): string {
818
+ return mdq(researchText)
819
+ .query('section2')
820
+ .each()
821
+ .map((s) => {
822
+ const heading = s.query('h2').text().trim();
823
+ const paragraph = s.query('paragraph[0]').text().trim();
824
+ const elements = s
825
+ .query('table')
826
+ .toJson()
827
+ .map((row) => Object.values(row)[0])
828
+ .filter(Boolean);
829
+
830
+ const parts = [heading];
831
+ if (paragraph) parts.push(paragraph);
832
+ if (elements.length) parts.push(`Elements: ${elements.join(', ')}`);
833
+ if (heading.toLowerCase() === 'primary actions') {
834
+ const listItems = s.query('list').text().trim();
835
+ if (listItems) parts.push(listItems);
836
+ }
837
+ return parts.join('\n');
838
+ })
839
+ .filter(Boolean)
840
+ .join('\n\n');
841
+ }
842
+
843
+ async navigateTo(url: string): Promise<void> {
844
+ const action = this.explorer.createAction();
845
+ await action.execute(`I.amOnPage("${url}")`);
846
+ }
847
+
848
+ async cancelInUi() {
849
+ const beforeAria = this.stateManager.getCurrentState()?.ariaSnapshot || null;
850
+ const action = this.explorer.createAction();
851
+
852
+ await action.execute('I.clickXY(0, 0)');
853
+ if (diffAriaSnapshots(beforeAria, this.stateManager.getCurrentState()?.ariaSnapshot || null)) return;
854
+
855
+ await action.execute(`I.pressKey('Escape')`);
856
+ }
857
+ }