explorbot 0.1.31 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +14 -14
  2. package/bin/explorbot-cli.ts +4 -3
  3. package/boat/doc-collector/src/ai/documentarian.ts +22 -27
  4. package/boat/doc-collector/src/ai/tools.ts +67 -10
  5. package/boat/doc-collector/src/docbot.ts +28 -7
  6. package/boat/doc-collector/src/docs-renderer.ts +19 -24
  7. package/boat/doc-collector/src/screenshots.ts +33 -1
  8. package/boat/doc-collector/src/state-diagram.ts +281 -0
  9. package/dist/bin/explorbot-cli.js +3 -3
  10. package/dist/boat/doc-collector/src/ai/documentarian.js +16 -6
  11. package/dist/boat/doc-collector/src/ai/tools.js +47 -10
  12. package/dist/boat/doc-collector/src/docbot.js +25 -6
  13. package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
  14. package/dist/boat/doc-collector/src/screenshots.js +29 -0
  15. package/dist/boat/doc-collector/src/state-diagram.js +200 -0
  16. package/dist/package.json +8 -6
  17. package/dist/src/action-result.d.ts +131 -0
  18. package/dist/src/action-result.js +82 -159
  19. package/dist/src/action.d.ts +43 -0
  20. package/dist/src/action.js +18 -106
  21. package/dist/src/activity.d.ts +26 -0
  22. package/dist/src/ai/agent.d.ts +3 -0
  23. package/dist/src/ai/captain/file-tools.d.ts +28 -0
  24. package/dist/src/ai/captain/idle-mode.d.ts +11 -0
  25. package/dist/src/ai/captain/mixin.d.ts +10 -0
  26. package/dist/src/ai/captain/test-mode.d.ts +13 -0
  27. package/dist/src/ai/captain/web-mode.d.ts +13 -0
  28. package/dist/src/ai/captain/web-mode.js +0 -7
  29. package/dist/src/ai/captain.d.ts +141 -0
  30. package/dist/src/ai/captain.js +4 -19
  31. package/dist/src/ai/conversation.d.ts +28 -0
  32. package/dist/src/ai/conversation.js +9 -28
  33. package/dist/src/ai/driller.d.ts +157 -0
  34. package/dist/src/ai/driller.js +3 -12
  35. package/dist/src/ai/experience-compactor.d.ts +53 -0
  36. package/dist/src/ai/experience-compactor.js +16 -33
  37. package/dist/src/ai/fisherman-tools.d.ts +90 -0
  38. package/dist/src/ai/fisherman-tools.js +1 -1
  39. package/dist/src/ai/fisherman.d.ts +29 -0
  40. package/dist/src/ai/fisherman.js +0 -3
  41. package/dist/src/ai/historian/codeceptjs.d.ts +17 -0
  42. package/dist/src/ai/historian/codeceptjs.js +3 -2
  43. package/dist/src/ai/historian/experience.d.ts +40 -0
  44. package/dist/src/ai/historian/mixin.d.ts +2 -0
  45. package/dist/src/ai/historian/playwright.d.ts +22 -0
  46. package/dist/src/ai/historian/playwright.js +3 -2
  47. package/dist/src/ai/historian/screencast.d.ts +32 -0
  48. package/dist/src/ai/historian/utils.d.ts +3 -0
  49. package/dist/src/ai/historian/utils.js +0 -1
  50. package/dist/src/ai/historian.d.ts +109 -0
  51. package/dist/src/ai/historian.js +1 -5
  52. package/dist/src/ai/navigator.d.ts +54 -0
  53. package/dist/src/ai/navigator.js +11 -46
  54. package/dist/src/ai/pilot.d.ts +81 -0
  55. package/dist/src/ai/pilot.js +6 -17
  56. package/dist/src/ai/planner/session-dedup.d.ts +12 -0
  57. package/dist/src/ai/planner/styles.d.ts +6 -0
  58. package/dist/src/ai/planner/subpages.d.ts +35 -0
  59. package/dist/src/ai/planner.d.ts +79 -0
  60. package/dist/src/ai/planner.js +49 -37
  61. package/dist/src/ai/provider.d.ts +53 -0
  62. package/dist/src/ai/provider.js +78 -130
  63. package/dist/src/ai/quartermaster.d.ts +56 -0
  64. package/dist/src/ai/quartermaster.js +4 -10
  65. package/dist/src/ai/rerunner.d.ts +48 -0
  66. package/dist/src/ai/rerunner.js +14 -22
  67. package/dist/src/ai/researcher/cache.d.ts +6 -0
  68. package/dist/src/ai/researcher/cache.js +11 -18
  69. package/dist/src/ai/researcher/coordinates.d.ts +56 -0
  70. package/dist/src/ai/researcher/deep-analysis.d.ts +74 -0
  71. package/dist/src/ai/researcher/deep-analysis.js +3 -6
  72. package/dist/src/ai/researcher/fingerprint-worker.d.ts +1 -0
  73. package/dist/src/ai/researcher/focus.d.ts +8 -0
  74. package/dist/src/ai/researcher/locators.d.ts +36 -0
  75. package/dist/src/ai/researcher/mixin.d.ts +2 -0
  76. package/dist/src/ai/researcher/parser.d.ts +35 -0
  77. package/dist/src/ai/researcher/parser.js +1 -1
  78. package/dist/src/ai/researcher/research-result.d.ts +15 -0
  79. package/dist/src/ai/researcher/research-result.js +0 -19
  80. package/dist/src/ai/researcher/sections.d.ts +19 -0
  81. package/dist/src/ai/researcher/sections.js +5 -1
  82. package/dist/src/ai/researcher.js +12 -53
  83. package/dist/src/ai/rules.d.ts +16 -0
  84. package/dist/src/ai/rules.js +22 -15
  85. package/dist/src/ai/session-analyst.d.ts +11 -0
  86. package/dist/src/ai/session-analyst.js +8 -5
  87. package/dist/src/ai/task-agent.d.ts +28 -0
  88. package/dist/src/ai/task-agent.js +2 -27
  89. package/dist/src/ai/tester.d.ts +101 -0
  90. package/dist/src/ai/tester.js +10 -61
  91. package/dist/src/ai/tools.d.ts +52 -0
  92. package/dist/src/ai/tools.js +119 -101
  93. package/dist/src/api/api-client.d.ts +17 -0
  94. package/dist/src/api/request-result.d.ts +44 -0
  95. package/dist/src/api/request-store.d.ts +29 -0
  96. package/dist/src/api/spec-reader.d.ts +5 -0
  97. package/dist/src/api/xhr-capture.d.ts +10 -0
  98. package/dist/src/browser-server.d.ts +10 -0
  99. package/dist/src/command-handler.d.ts +68 -0
  100. package/dist/src/commands/add-rule-command.d.ts +11 -0
  101. package/dist/src/commands/base-command.d.ts +26 -0
  102. package/dist/src/commands/clean-command.d.ts +12 -0
  103. package/dist/src/commands/compact-command.d.ts +20 -0
  104. package/dist/src/commands/compact-command.js +6 -6
  105. package/dist/src/commands/context-aria-command.d.ts +6 -0
  106. package/dist/src/commands/context-command.d.ts +11 -0
  107. package/dist/src/commands/context-command.js +1 -1
  108. package/dist/src/commands/context-data-command.d.ts +6 -0
  109. package/dist/src/commands/context-experience-command.d.ts +6 -0
  110. package/dist/src/commands/context-html-command.d.ts +6 -0
  111. package/dist/src/commands/context-knowledge-command.d.ts +6 -0
  112. package/dist/src/commands/context-knowledge-command.js +1 -1
  113. package/dist/src/commands/debug-command.d.ts +6 -0
  114. package/dist/src/commands/drill-command.d.ts +10 -0
  115. package/dist/src/commands/exit-command.d.ts +7 -0
  116. package/dist/src/commands/experience-command.d.ts +15 -0
  117. package/dist/src/commands/experience-command.js +1 -1
  118. package/dist/src/commands/explore-command.d.ts +51 -0
  119. package/dist/src/commands/freesail-command.d.ts +12 -0
  120. package/dist/src/commands/help-command.d.ts +6 -0
  121. package/dist/src/commands/index.d.ts +4 -0
  122. package/dist/src/commands/init-command.d.ts +7 -0
  123. package/dist/src/commands/knows-command.d.ts +10 -0
  124. package/dist/src/commands/knows-command.js +1 -1
  125. package/dist/src/commands/learn-command.d.ts +7 -0
  126. package/dist/src/commands/learn-command.js +3 -1
  127. package/dist/src/commands/navigate-command.d.ts +7 -0
  128. package/dist/src/commands/path-command.d.ts +10 -0
  129. package/dist/src/commands/plan-clear-command.d.ts +7 -0
  130. package/dist/src/commands/plan-command.d.ts +14 -0
  131. package/dist/src/commands/plan-edit-command.d.ts +7 -0
  132. package/dist/src/commands/plan-load-command.d.ts +7 -0
  133. package/dist/src/commands/plan-reload-command.d.ts +7 -0
  134. package/dist/src/commands/plan-save-command.d.ts +7 -0
  135. package/dist/src/commands/plans-command.d.ts +21 -0
  136. package/dist/src/commands/rerun-command.d.ts +7 -0
  137. package/dist/src/commands/research-command.d.ts +11 -0
  138. package/dist/src/commands/runs-command.d.ts +7 -0
  139. package/dist/src/commands/start-command.d.ts +7 -0
  140. package/dist/src/commands/status-command.d.ts +6 -0
  141. package/dist/src/commands/test-command.d.ts +11 -0
  142. package/dist/src/components/ActivityPane.d.ts +6 -0
  143. package/dist/src/components/AddKnowledge.d.ts +10 -0
  144. package/dist/src/components/AddKnowledge.js +3 -7
  145. package/dist/src/components/AddRule.d.ts +9 -0
  146. package/dist/src/components/App.d.ts +9 -0
  147. package/dist/src/components/App.js +2 -2
  148. package/dist/src/components/Autocomplete.d.ts +3 -0
  149. package/dist/src/components/InputPane.d.ts +13 -0
  150. package/dist/src/components/InputReadline.d.ts +17 -0
  151. package/dist/src/components/LogPane.d.ts +6 -0
  152. package/dist/src/components/PlanEditor.d.ts +9 -0
  153. package/dist/src/components/PlanPane.d.ts +13 -0
  154. package/dist/src/components/SessionTimer.d.ts +6 -0
  155. package/dist/src/components/StateTransitionPane.d.ts +8 -0
  156. package/dist/src/components/StatusPane.d.ts +4 -0
  157. package/dist/src/components/TaskPane.d.ts +9 -0
  158. package/dist/src/components/Welcome.d.ts +3 -0
  159. package/dist/src/components/WelcomeChecklist.d.ts +9 -0
  160. package/dist/src/components/WelcomeCommands.d.ts +6 -0
  161. package/dist/src/components/autocomplete-store.d.ts +10 -0
  162. package/dist/src/components/parse-keypress.d.ts +12 -0
  163. package/dist/src/config.d.ts +253 -0
  164. package/dist/src/config.js +12 -0
  165. package/dist/src/execution-controller.d.ts +25 -0
  166. package/dist/src/experience-tracker.d.ts +112 -0
  167. package/dist/src/experience-tracker.js +83 -155
  168. package/dist/src/explorbot.d.ts +106 -0
  169. package/dist/src/explorbot.js +17 -51
  170. package/dist/src/explorer.d.ts +129 -0
  171. package/dist/src/explorer.js +31 -77
  172. package/dist/src/index.d.ts +6 -0
  173. package/dist/src/index.js +2 -55
  174. package/dist/src/knowledge-tracker.d.ts +33 -0
  175. package/dist/src/knowledge-tracker.js +47 -47
  176. package/dist/src/observability.d.ts +13 -0
  177. package/dist/src/observability.js +2 -1
  178. package/dist/src/playwright-recorder.d.ts +31 -0
  179. package/dist/src/reporter.d.ts +34 -0
  180. package/dist/src/state-manager.d.ts +160 -0
  181. package/dist/src/state-manager.js +10 -136
  182. package/dist/src/stats.d.ts +24 -0
  183. package/dist/src/suite.d.ts +24 -0
  184. package/dist/src/test-plan.d.ts +166 -0
  185. package/dist/src/utils/aria.d.ts +25 -0
  186. package/dist/src/utils/aria.js +4 -1
  187. package/dist/src/utils/browser-errors.d.ts +8 -0
  188. package/dist/src/utils/cache.d.ts +15 -0
  189. package/dist/src/utils/cache.js +34 -0
  190. package/dist/src/utils/cli-name.d.ts +1 -0
  191. package/dist/src/utils/code-extractor.d.ts +1 -0
  192. package/dist/src/utils/context-formatter.d.ts +31 -0
  193. package/dist/src/utils/context-formatter.js +15 -27
  194. package/dist/src/utils/error-page.d.ts +12 -0
  195. package/dist/src/utils/expandable.d.ts +4 -0
  196. package/dist/src/utils/hooks-runner.d.ts +15 -0
  197. package/dist/src/utils/html-diff.d.ts +19 -0
  198. package/dist/src/utils/html.d.ts +129 -0
  199. package/dist/src/utils/html.js +71 -0
  200. package/dist/src/utils/log-filters.d.ts +6 -0
  201. package/dist/src/utils/logger.d.ts +35 -0
  202. package/dist/src/utils/loop.d.ts +35 -0
  203. package/dist/src/utils/markdown-files.d.ts +10 -0
  204. package/dist/src/utils/markdown-files.js +21 -0
  205. package/dist/src/utils/markdown-parser.d.ts +17 -0
  206. package/dist/src/utils/markdown-query.d.ts +46 -0
  207. package/dist/src/utils/markdown-query.js +14 -1
  208. package/dist/src/utils/markdown-terminal.d.ts +1 -0
  209. package/dist/src/utils/next-steps.d.ts +11 -0
  210. package/dist/src/utils/page-readiness.d.ts +7 -0
  211. package/dist/src/utils/page-readiness.js +1 -1
  212. package/dist/src/utils/research-parser.d.ts +1 -0
  213. package/dist/src/utils/retry.d.ts +8 -0
  214. package/dist/src/utils/rules-loader.d.ts +12 -0
  215. package/dist/src/utils/secrets.d.ts +4 -0
  216. package/dist/src/utils/secrets.js +28 -0
  217. package/dist/src/utils/step-analyzer.d.ts +11 -0
  218. package/dist/src/utils/strings.d.ts +5 -0
  219. package/dist/src/utils/strings.js +10 -0
  220. package/dist/src/utils/test-files.d.ts +3 -0
  221. package/dist/src/utils/test-plan-markdown.d.ts +9 -0
  222. package/dist/src/utils/throttle.d.ts +2 -0
  223. package/dist/src/utils/unique-names.d.ts +2 -0
  224. package/dist/src/utils/url-matcher.d.ts +6 -0
  225. package/dist/src/utils/web-element.d.ts +45 -0
  226. package/dist/src/utils/web-element.js +3 -0
  227. package/dist/src/utils/web-sandbox.d.ts +4 -0
  228. package/dist/src/utils/web-sandbox.js +37 -0
  229. package/dist/src/utils/xpath.d.ts +17 -0
  230. package/package.json +8 -6
  231. package/src/action-result.ts +80 -168
  232. package/src/action.ts +16 -123
  233. package/src/ai/captain/web-mode.ts +0 -7
  234. package/src/ai/captain.ts +5 -17
  235. package/src/ai/conversation.ts +9 -32
  236. package/src/ai/driller.ts +3 -16
  237. package/src/ai/experience-compactor.ts +16 -35
  238. package/src/ai/fisherman-tools.ts +1 -1
  239. package/src/ai/fisherman.ts +0 -4
  240. package/src/ai/historian/codeceptjs.ts +4 -2
  241. package/src/ai/historian/playwright.ts +4 -2
  242. package/src/ai/historian/utils.ts +0 -1
  243. package/src/ai/historian.ts +4 -7
  244. package/src/ai/navigator.ts +13 -49
  245. package/src/ai/pilot.ts +6 -16
  246. package/src/ai/planner.ts +56 -44
  247. package/src/ai/provider.ts +86 -139
  248. package/src/ai/quartermaster.ts +34 -41
  249. package/src/ai/rerunner.ts +17 -21
  250. package/src/ai/researcher/cache.ts +10 -16
  251. package/src/ai/researcher/deep-analysis.ts +3 -6
  252. package/src/ai/researcher/parser.ts +1 -1
  253. package/src/ai/researcher/research-result.ts +0 -18
  254. package/src/ai/researcher/sections.ts +5 -1
  255. package/src/ai/researcher.ts +11 -59
  256. package/src/ai/rules.ts +24 -13
  257. package/src/ai/session-analyst.ts +8 -5
  258. package/src/ai/task-agent.ts +3 -30
  259. package/src/ai/tester.ts +12 -73
  260. package/src/ai/tools.ts +119 -106
  261. package/src/commands/compact-command.ts +7 -8
  262. package/src/commands/context-command.ts +1 -1
  263. package/src/commands/context-knowledge-command.ts +1 -1
  264. package/src/commands/experience-command.ts +1 -1
  265. package/src/commands/knows-command.ts +1 -1
  266. package/src/commands/learn-command.ts +3 -1
  267. package/src/components/AddKnowledge.tsx +5 -7
  268. package/src/components/App.tsx +2 -2
  269. package/src/config.ts +12 -0
  270. package/src/experience-tracker.ts +90 -152
  271. package/src/explorbot.ts +17 -51
  272. package/src/explorer.ts +34 -81
  273. package/src/index.ts +9 -0
  274. package/src/knowledge-tracker.ts +49 -50
  275. package/src/observability.ts +2 -1
  276. package/src/state-manager.ts +15 -164
  277. package/src/utils/aria.ts +11 -2
  278. package/src/utils/cache.ts +40 -0
  279. package/src/utils/context-formatter.ts +12 -15
  280. package/src/utils/html.ts +79 -0
  281. package/src/utils/markdown-files.ts +30 -0
  282. package/src/utils/markdown-query.ts +15 -1
  283. package/src/utils/page-readiness.ts +1 -1
  284. package/src/utils/secrets.ts +28 -0
  285. package/src/utils/strings.ts +12 -0
  286. package/src/utils/web-element.ts +4 -0
  287. package/src/utils/web-sandbox.ts +43 -0
  288. package/src/index.tsx +0 -62
@@ -2,10 +2,12 @@ import fs from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { ConfigParser, type HtmlConfig, outputPath } from './config.ts';
4
4
  import type { Link, WebPageState } from './state-manager.ts';
5
- import { compactAriaSnapshot, diffAriaSnapshots } from './utils/aria.ts';
5
+ import { LARGE_ARIA_CHANGE_THRESHOLD, compactAriaSnapshot, diffAriaSnapshots } from './utils/aria.ts';
6
+ import { TTLCache } from './utils/cache.ts';
6
7
  import { type HtmlDiffPart, type HtmlDiffResult, htmlDiff } from './utils/html-diff.ts';
7
8
  import { extractHeadings, extractLinks, extractTargetedHtml, htmlCombinedSnapshot, htmlMinimalUISnapshot, htmlTextSnapshot, minifyHtml } from './utils/html.ts';
8
9
  import { createDebug } from './utils/logger.ts';
10
+ import { slugify } from './utils/strings.ts';
9
11
  import { extractStatePath, matchesUrl } from './utils/url-matcher.ts';
10
12
 
11
13
  const debugLog = createDebug('explorbot:state');
@@ -37,6 +39,7 @@ export interface PageDiff {
37
39
  previousUrl?: string;
38
40
  currentUrl: string;
39
41
  ariaChanges?: string | null;
42
+ ariaChangeCount?: number;
40
43
  htmlParts?: HtmlDiffPart[];
41
44
  iframes?: string;
42
45
  }
@@ -68,12 +71,12 @@ export class ActionResult implements ActionResultData {
68
71
  private _screenshot: Buffer | undefined = undefined;
69
72
  readonly htmlFile: string | undefined = undefined;
70
73
  private _html: string | undefined = undefined;
74
+ private snapshotCache = new TTLCache<string>();
71
75
  readonly logFile: string | undefined = undefined;
72
- private _browserLogs: any[] | undefined = undefined;
73
76
  readonly ariaSnapshotFile: string | undefined = undefined;
74
77
  private _ariaSnapshot: string | null | undefined = undefined;
75
78
  private _lastExtractedHtml: string | undefined = undefined;
76
- notes: any;
79
+ notes: string[] = [];
77
80
  public links: Link[] = [];
78
81
  public verifications?: Record<string, boolean>;
79
82
 
@@ -105,14 +108,11 @@ export class ActionResult implements ActionResultData {
105
108
  this.ariaSnapshotFile = data.ariaSnapshotFile;
106
109
  }
107
110
 
108
- // Store HTML and browser logs in private properties if provided
111
+ // Store HTML in a private property if provided
109
112
  if (data.html !== undefined) {
110
113
  this._html = data.html;
111
114
  }
112
115
 
113
- if (data.browserLogs !== undefined) {
114
- this._browserLogs = data.browserLogs;
115
- }
116
116
  if (data.screenshot !== undefined) {
117
117
  this._screenshot = data.screenshot;
118
118
  }
@@ -120,7 +120,7 @@ export class ActionResult implements ActionResultData {
120
120
  this._ariaSnapshot = data.ariaSnapshot;
121
121
  }
122
122
 
123
- if (!this.fullUrl && this.url && this.url !== '') {
123
+ if (!this.fullUrl && this.url) {
124
124
  this.fullUrl = this.url;
125
125
  }
126
126
 
@@ -132,7 +132,7 @@ export class ActionResult implements ActionResultData {
132
132
  this.links = extractLinks(this._html);
133
133
  }
134
134
 
135
- if (this.url && this.url !== '') {
135
+ if (this.url) {
136
136
  this.url = extractStatePath(this.url);
137
137
  }
138
138
  }
@@ -151,6 +151,7 @@ export class ActionResult implements ActionResultData {
151
151
 
152
152
  set html(value: string) {
153
153
  this._html = value;
154
+ this.snapshotCache.clear();
154
155
  }
155
156
 
156
157
  get screenshot(): Buffer | undefined {
@@ -163,16 +164,6 @@ export class ActionResult implements ActionResultData {
163
164
  return undefined;
164
165
  }
165
166
 
166
- get browserLogsContent(): any[] {
167
- if (this._browserLogs !== undefined) {
168
- return this._browserLogs;
169
- }
170
- if (this.logFile) {
171
- return ActionResult.loadBrowserLogsFromFile(this.logFile);
172
- }
173
- return [];
174
- }
175
-
176
167
  get ariaSnapshot(): string | null {
177
168
  if (this._ariaSnapshot !== undefined) {
178
169
  return this._ariaSnapshot;
@@ -221,14 +212,14 @@ export class ActionResult implements ActionResultData {
221
212
  }
222
213
 
223
214
  isSameUrl(state: WebPageState): boolean {
224
- if (!this.url || this.url === '') {
215
+ if (!this.url) {
225
216
  return false;
226
217
  }
227
218
  return extractStatePath(state.url) === extractStatePath(this.url);
228
219
  }
229
220
 
230
221
  isMatchedBy(state: WebPageState): boolean {
231
- if (!this.url || this.url === '') {
222
+ if (!this.url) {
232
223
  return false;
233
224
  }
234
225
 
@@ -263,21 +254,26 @@ export class ActionResult implements ActionResultData {
263
254
 
264
255
  async simplifiedHtml(htmlConfig?: HtmlConfig): Promise<string> {
265
256
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
266
- return await minifyHtml(htmlMinimalUISnapshot(this.html ?? '', normalizedConfig?.minimal));
257
+ const cacheKey = `simplified:${JSON.stringify(normalizedConfig?.minimal ?? null)}`;
258
+ return this.snapshotCache.fetch(cacheKey, () => minifyHtml(htmlMinimalUISnapshot(this.html ?? '', normalizedConfig?.minimal)));
267
259
  }
268
260
 
269
261
  async combinedHtml(htmlConfig?: HtmlConfig & { keepPositions?: boolean }): Promise<string> {
270
262
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
271
- const combinedHtml = await minifyHtml(htmlCombinedSnapshot(this.html ?? '', normalizedConfig?.combined, { keepPositions: htmlConfig?.keepPositions }));
272
- debugLog(`----${this.url}----`);
273
- debugLog(`Combined HTML: \n${combinedHtml}`);
274
- debugLog('----');
275
- return combinedHtml;
263
+ const cacheKey = `combined:${!!htmlConfig?.keepPositions}:${JSON.stringify(normalizedConfig?.combined ?? null)}`;
264
+ return this.snapshotCache.fetch(cacheKey, async () => {
265
+ const combinedHtml = await minifyHtml(htmlCombinedSnapshot(this.html ?? '', normalizedConfig?.combined, { keepPositions: htmlConfig?.keepPositions }));
266
+ debugLog(`----${this.url}----`);
267
+ debugLog(`Combined HTML: \n${combinedHtml}`);
268
+ debugLog('----');
269
+ return combinedHtml;
270
+ });
276
271
  }
277
272
 
278
273
  async textHtml(htmlConfig?: HtmlConfig): Promise<string> {
279
274
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
280
- return await minifyHtml(htmlTextSnapshot(this.html ?? '', normalizedConfig?.text));
275
+ const cacheKey = `text:${JSON.stringify(normalizedConfig?.text ?? null)}`;
276
+ return this.snapshotCache.fetch(cacheKey, () => minifyHtml(htmlTextSnapshot(this.html ?? '', normalizedConfig?.text)));
281
277
  }
282
278
 
283
279
  getInteractiveARIA(): string {
@@ -341,26 +337,26 @@ export class ActionResult implements ActionResultData {
341
337
  return new ActionResult(actionResultData);
342
338
  }
343
339
 
344
- private static loadHtmlFromFile(htmlFile: string): string | null {
340
+ private static loadStateFile(file: string): string | null {
345
341
  try {
346
- const filePath = outputPath('states', htmlFile);
347
- if (fs.existsSync(filePath)) {
348
- return fs.readFileSync(filePath, 'utf8');
349
- }
350
- return null;
342
+ const filePath = outputPath('states', file);
343
+ if (!fs.existsSync(filePath)) return null;
344
+ return fs.readFileSync(filePath, 'utf8');
351
345
  } catch (error) {
352
- console.error('Failed to load HTML from file:', error);
346
+ console.error('Failed to load state file:', error);
353
347
  return null;
354
348
  }
355
349
  }
356
350
 
351
+ private static loadHtmlFromFile(htmlFile: string): string | null {
352
+ return ActionResult.loadStateFile(htmlFile);
353
+ }
354
+
357
355
  private static loadScreenshotFromFile(screenshotFile: string): Buffer | undefined {
358
356
  try {
359
357
  const filePath = outputPath('states', screenshotFile);
360
- if (fs.existsSync(filePath)) {
361
- return fs.readFileSync(filePath);
362
- }
363
- return undefined;
358
+ if (!fs.existsSync(filePath)) return undefined;
359
+ return fs.readFileSync(filePath);
364
360
  } catch (error) {
365
361
  console.error('Failed to load screenshot from file:', error);
366
362
  return undefined;
@@ -368,58 +364,38 @@ export class ActionResult implements ActionResultData {
368
364
  }
369
365
 
370
366
  private static loadBrowserLogsFromFile(logFile: string): any[] {
371
- try {
372
- const filePath = outputPath('states', logFile);
373
- if (fs.existsSync(filePath)) {
374
- const logContent = fs.readFileSync(filePath, 'utf8');
375
- return logContent
376
- .split('\n')
377
- .filter((line) => line.trim())
378
- .map((line) => {
379
- const match = line.match(/\[([^\]]+)\] (\w+): (.+)/);
380
- if (match) {
381
- return {
382
- timestamp: match[1],
383
- type: match[2].toLowerCase(),
384
- text: match[3],
385
- level: match[2].toLowerCase(),
386
- message: match[3],
387
- };
388
- }
389
- return { text: line, type: 'log', level: 'log', message: line };
390
- });
391
- }
392
- return [];
393
- } catch (error) {
394
- console.error('Failed to load browser logs from file:', error);
395
- return [];
396
- }
367
+ const logContent = ActionResult.loadStateFile(logFile);
368
+ if (!logContent) return [];
369
+ return logContent
370
+ .split('\n')
371
+ .filter((line) => line.trim())
372
+ .map((line) => {
373
+ const match = line.match(/\[([^\]]+)\] (\w+): (.+)/);
374
+ if (match) {
375
+ return {
376
+ timestamp: match[1],
377
+ type: match[2].toLowerCase(),
378
+ text: match[3],
379
+ level: match[2].toLowerCase(),
380
+ message: match[3],
381
+ };
382
+ }
383
+ return { text: line, type: 'log', level: 'log', message: line };
384
+ });
397
385
  }
398
386
 
399
387
  private static loadAriaSnapshotFromFile(ariaSnapshotFile: string): string | null {
400
- try {
401
- const filePath = outputPath('states', ariaSnapshotFile);
402
- if (!fs.existsSync(filePath)) {
403
- return null;
404
- }
405
-
406
- const content = fs.readFileSync(filePath, 'utf8');
407
- const trimmed = content.trim();
408
- if (!trimmed) {
409
- return null;
410
- }
411
-
412
- return trimmed.endsWith('\n') ? trimmed : `${trimmed}\n`;
413
- } catch (error) {
414
- console.error('Failed to load aria snapshot from file:', error);
415
- return null;
416
- }
388
+ const content = ActionResult.loadStateFile(ariaSnapshotFile);
389
+ if (!content) return null;
390
+ const trimmed = content.trim();
391
+ if (!trimmed) return null;
392
+ return trimmed.endsWith('\n') ? trimmed : `${trimmed}\n`;
417
393
  }
418
394
 
419
395
  toAiContext(): string {
420
396
  const parts: string[] = [];
421
397
 
422
- if (this.url && this.url !== '') {
398
+ if (this.url) {
423
399
  parts.push(`<url>${this.url}</url>`);
424
400
  }
425
401
 
@@ -465,7 +441,7 @@ export class ActionResult implements ActionResultData {
465
441
  }
466
442
 
467
443
  get relativeUrl(): string | null {
468
- if (!this.url || this.url === '') return null;
444
+ if (!this.url) return null;
469
445
 
470
446
  try {
471
447
  const urlObj = new URL(this.url);
@@ -490,22 +466,10 @@ export class ActionResult implements ActionResultData {
490
466
 
491
467
  this.extractHeadings(this.html);
492
468
 
493
- const headings = ['h1', 'h2'];
469
+ if (this.h1) parts.push(`h1_${this.h1}`);
470
+ if (this.h2) parts.push(`h2_${this.h2}`);
494
471
 
495
- for (const heading of headings) {
496
- const value = this[heading as keyof this] as string;
497
- if (value) {
498
- parts.push(`${heading}_${value}`);
499
- }
500
- }
501
-
502
- let stateString = parts
503
- .map((part) => part.substring(0, 100))
504
- .join('_')
505
- .replace(/[^a-zA-Z0-9_]/g, '_')
506
- .replace(/_+/g, '_')
507
- .replace(/^_|_$/g, '')
508
- .toLowerCase();
472
+ let stateString = slugify(parts.map((part) => part.substring(0, 100)).join('_'));
509
473
 
510
474
  if (stateString.length > 200) {
511
475
  stateString = stateString.substring(0, 200);
@@ -517,60 +481,8 @@ export class ActionResult implements ActionResultData {
517
481
  return stateString;
518
482
  }
519
483
 
520
- private saveBrowserLogs(): void {
521
- const logs = this.browserLogsContent;
522
- if (!logs || logs.length === 0) return;
523
-
524
- try {
525
- const outputDir = 'output';
526
- const stateHash = this.getStateHash();
527
- const filename = `${stateHash}.log`;
528
- const filePath = join(outputDir, filename);
529
-
530
- // Ensure output directory exists
531
- if (!fs.existsSync(outputDir)) {
532
- fs.mkdirSync(outputDir, { recursive: true });
533
- }
534
-
535
- // Format logs for saving
536
- const formattedLogs = logs.map((log: any) => {
537
- const timestamp = new Date().toISOString();
538
- const level = (log.type || log.level || 'LOG').toUpperCase();
539
- const message = log.text || log.message || String(log);
540
- return `[${timestamp}] ${level}: ${message}`;
541
- });
542
-
543
- // Save log content to file
544
- const logContent = `${formattedLogs.join('\n')}\n`;
545
- fs.writeFileSync(filePath, logContent, 'utf8');
546
- } catch (error) {
547
- // Silently fail to avoid breaking the main flow
548
- console.error('Failed to save browser logs:', error);
549
- }
550
- }
551
-
552
- private saveHtmlOutput(): void {
553
- try {
554
- const outputDir = 'output';
555
- const stateHash = this.getStateHash();
556
- const filename = `${stateHash}.html`;
557
- const filePath = join(outputDir, filename);
558
-
559
- // Ensure output directory exists
560
- if (!fs.existsSync(outputDir)) {
561
- fs.mkdirSync(outputDir, { recursive: true });
562
- }
563
-
564
- // Save HTML content to file
565
- fs.writeFileSync(filePath, this.html, 'utf8');
566
- } catch (error) {
567
- // Silently fail to avoid breaking the main flow
568
- console.error('Failed to save HTML output:', error);
569
- }
570
- }
571
-
572
484
  async diff(previousState: ActionResult | null): Promise<Diff> {
573
- return new Diff(this, previousState);
485
+ return Diff.create(this, previousState);
574
486
  }
575
487
 
576
488
  async toToolResult(previousState: ActionResult | null, locator: string): Promise<ToolResultMetadata> {
@@ -602,7 +514,6 @@ export class ActionResult implements ActionResultData {
602
514
  }
603
515
 
604
516
  const diff = await this.diff(previousState);
605
- await diff.calculate();
606
517
 
607
518
  const pageDiff: PageDiff = {
608
519
  urlChanged,
@@ -612,6 +523,7 @@ export class ActionResult implements ActionResultData {
612
523
 
613
524
  if (diff.ariaChanged) {
614
525
  pageDiff.ariaChanges = diff.ariaChanged;
526
+ pageDiff.ariaChangeCount = diff.ariaChangeCount;
615
527
  }
616
528
 
617
529
  if (diff.htmlParts.length > 0) {
@@ -630,10 +542,7 @@ export class ActionResult implements ActionResultData {
630
542
  }
631
543
 
632
544
  if (pageDiff.ariaChanges && this.iframeSnapshots.length > 0) {
633
- const addedCount = (pageDiff.ariaChanges.match(/\n {4}- /g) || []).length;
634
- const removedMatch = pageDiff.ariaChanges.match(/removed: (\d+) interactive/);
635
- const removedCount = removedMatch ? Number.parseInt(removedMatch[1]) : 0;
636
- if (addedCount + removedCount >= 50) {
545
+ if (diff.ariaChangeCount >= LARGE_ARIA_CHANGE_THRESHOLD) {
637
546
  pageDiff.iframes = this.iframeSnapshots.map((snap) => `iframe src="${snap.src}":\n${snap.html}`).join('\n\n');
638
547
  }
639
548
  }
@@ -671,20 +580,25 @@ function collapseHtmlParts(parts: HtmlDiffPart[]): HtmlDiffPart[] {
671
580
  export class Diff {
672
581
  private _htmlDiffResult: HtmlDiffResult | null = null;
673
582
  private _ariaDiffResult: string | null = null;
583
+ private _ariaChangeCount = 0;
674
584
  private _isSameUrl: boolean;
675
- private _urlChanged: boolean;
676
585
 
677
586
  constructor(
678
587
  private current: ActionResult,
679
588
  private previous: ActionResult | null
680
589
  ) {
681
590
  this._isSameUrl = previous ? current.isSameUrl({ url: previous.url }) : false;
682
- this._urlChanged = !this._isSameUrl;
591
+ }
592
+
593
+ static async create(current: ActionResult, previous: ActionResult | null): Promise<Diff> {
594
+ const diff = new Diff(current, previous);
595
+ await diff.calculate();
596
+ return diff;
683
597
  }
684
598
 
685
599
  hasChanges(): boolean {
686
600
  if (!this.previous) return false;
687
- if (this._urlChanged) return true;
601
+ if (!this._isSameUrl) return true;
688
602
 
689
603
  const hasHtmlChanges = this._htmlDiffResult && (this._htmlDiffResult.parts.length > 0 || this._htmlDiffResult.added.length > 0 || this._htmlDiffResult.removed.length > 0);
690
604
  const hasAriaChanges = this._ariaDiffResult !== null;
@@ -697,7 +611,7 @@ export class Diff {
697
611
  }
698
612
 
699
613
  urlHasChanged(): boolean {
700
- return this._urlChanged;
614
+ return !this._isSameUrl;
701
615
  }
702
616
 
703
617
  get htmlParts(): HtmlDiffPart[] {
@@ -709,18 +623,14 @@ export class Diff {
709
623
  return this._ariaDiffResult;
710
624
  }
711
625
 
712
- get ariaRemoved(): string | null {
713
- return this._ariaDiffResult;
626
+ get ariaChangeCount(): number {
627
+ return this._ariaChangeCount;
714
628
  }
715
629
 
716
630
  get htmlDiff(): HtmlDiffResult | null {
717
631
  return this._htmlDiffResult;
718
632
  }
719
633
 
720
- get ariaDiff(): string | null {
721
- return this._ariaDiffResult;
722
- }
723
-
724
634
  async calculate(): Promise<void> {
725
635
  if (!this.previous) return;
726
636
 
@@ -728,6 +638,8 @@ export class Diff {
728
638
  this._htmlDiffResult = await htmlDiff(this.previous.html, this.current.html, ConfigParser.getInstance().getConfig().html);
729
639
  }
730
640
 
731
- this._ariaDiffResult = diffAriaSnapshots(this.previous.ariaSnapshot, this.current.ariaSnapshot);
641
+ const ariaDiff = diffAriaSnapshots(this.previous.ariaSnapshot, this.current.ariaSnapshot);
642
+ this._ariaDiffResult = ariaDiff.text;
643
+ this._ariaChangeCount = ariaDiff.count;
732
644
  }
733
645
  }