explorbot 0.2.5 → 0.3.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 (82) hide show
  1. package/boat/prima/README.md +96 -0
  2. package/boat/prima/package.json +14 -10
  3. package/boat/prima/src/cli.ts +5 -0
  4. package/boat/prima/src/prima.ts +17 -4
  5. package/dist/boat/prima/src/cli.js +7 -0
  6. package/dist/boat/prima/src/prima.js +18 -4
  7. package/dist/models.json +4 -4
  8. package/dist/package.json +6 -2
  9. package/dist/src/action-result.d.ts +13 -0
  10. package/dist/src/action-result.js +46 -15
  11. package/dist/src/action.d.ts +5 -2
  12. package/dist/src/action.js +48 -17
  13. package/dist/src/ai/captain/web-mode.js +1 -2
  14. package/dist/src/ai/captain.d.ts +20 -0
  15. package/dist/src/ai/captain.js +10 -1
  16. package/dist/src/ai/driller.js +6 -2
  17. package/dist/src/ai/fisherman-tools.d.ts +40 -1
  18. package/dist/src/ai/fisherman-tools.js +39 -0
  19. package/dist/src/ai/fisherman.js +2 -1
  20. package/dist/src/ai/navigator.d.ts +2 -1
  21. package/dist/src/ai/navigator.js +5 -9
  22. package/dist/src/ai/pilot.js +39 -22
  23. package/dist/src/ai/planner/subpages.js +2 -16
  24. package/dist/src/ai/planner.js +1 -1
  25. package/dist/src/ai/provider.js +16 -1
  26. package/dist/src/ai/researcher/cache.d.ts +8 -3
  27. package/dist/src/ai/researcher/cache.js +13 -8
  28. package/dist/src/ai/researcher/deep-analysis.js +1 -1
  29. package/dist/src/ai/researcher/fingerprint-worker.js +21 -4
  30. package/dist/src/ai/researcher.js +4 -3
  31. package/dist/src/ai/rules.js +1 -5
  32. package/dist/src/ai/tester.d.ts +1 -1
  33. package/dist/src/ai/tester.js +13 -22
  34. package/dist/src/ai/tools.d.ts +8 -5
  35. package/dist/src/ai/tools.js +79 -56
  36. package/dist/src/commands/init-command.js +13 -20
  37. package/dist/src/config.js +3 -1
  38. package/dist/src/experience-tracker.d.ts +2 -0
  39. package/dist/src/experience-tracker.js +12 -0
  40. package/dist/src/explorbot.js +1 -1
  41. package/dist/src/playwright-recorder.js +6 -12
  42. package/dist/src/test-plan.d.ts +8 -0
  43. package/dist/src/test-plan.js +11 -0
  44. package/dist/src/utils/html-diff.d.ts +5 -0
  45. package/dist/src/utils/html-diff.js +65 -6
  46. package/dist/src/utils/strings.d.ts +2 -0
  47. package/dist/src/utils/strings.js +32 -0
  48. package/dist/src/utils/url-matcher.d.ts +1 -0
  49. package/dist/src/utils/url-matcher.js +31 -2
  50. package/docs/basics/getting-started.md +33 -10
  51. package/docs/basics/providers.md +6 -4
  52. package/docs/contributing/npm-package.md +73 -4
  53. package/models.json +4 -4
  54. package/package.json +6 -2
  55. package/src/action-result.ts +61 -16
  56. package/src/action.ts +51 -17
  57. package/src/ai/captain/web-mode.ts +1 -2
  58. package/src/ai/captain.ts +9 -1
  59. package/src/ai/driller.ts +6 -2
  60. package/src/ai/fisherman-tools.ts +35 -0
  61. package/src/ai/fisherman.ts +2 -1
  62. package/src/ai/navigator.ts +6 -10
  63. package/src/ai/pilot.ts +41 -24
  64. package/src/ai/planner/subpages.ts +2 -13
  65. package/src/ai/planner.ts +1 -1
  66. package/src/ai/provider.ts +17 -1
  67. package/src/ai/researcher/cache.ts +17 -9
  68. package/src/ai/researcher/deep-analysis.ts +1 -1
  69. package/src/ai/researcher/fingerprint-worker.ts +23 -5
  70. package/src/ai/researcher.ts +4 -3
  71. package/src/ai/rules.ts +1 -5
  72. package/src/ai/tester.ts +13 -22
  73. package/src/ai/tools.ts +84 -60
  74. package/src/commands/init-command.ts +14 -20
  75. package/src/config.ts +2 -1
  76. package/src/experience-tracker.ts +13 -0
  77. package/src/explorbot.ts +1 -1
  78. package/src/playwright-recorder.ts +6 -11
  79. package/src/test-plan.ts +18 -0
  80. package/src/utils/html-diff.ts +72 -7
  81. package/src/utils/strings.ts +36 -0
  82. package/src/utils/url-matcher.ts +27 -2
@@ -327,7 +327,7 @@ export class Provider {
327
327
  const stopConditions = [isStepCount(maxRoundtrips)];
328
328
  if (extraStop)
329
329
  stopConditions.push(extraStop);
330
- const config = this.buildGenerateConfig({ tools, maxOutputTokens: 16384, toolChoice: 'auto' }, { stopWhen: stopConditions, model }, options);
330
+ const config = this.buildGenerateConfig({ tools, maxOutputTokens: 16384, toolChoice: 'auto', experimental_repairToolCall: repairToolCall }, { stopWhen: stopConditions, model }, options);
331
331
  try {
332
332
  const response = await withRetry(async () => {
333
333
  const result = (await this.raceWithIdleTimeout((signal) => generateText({ messages, ...config, abortSignal: signal }), config.timeout || 30000));
@@ -562,4 +562,19 @@ export class Provider {
562
562
  return this.config.visionModel !== undefined;
563
563
  }
564
564
  }
565
+ function repairToolCall(options) {
566
+ if (options.toolCall.toolName.includes('<|channel|>'))
567
+ return repairChannelMarker(options);
568
+ return null;
569
+ }
570
+ function repairChannelMarker({ toolCall, tools }) {
571
+ const markerIndex = toolCall.toolName.indexOf('<|channel|>');
572
+ if (markerIndex <= 0)
573
+ return null;
574
+ const toolName = toolCall.toolName.slice(0, markerIndex);
575
+ if (!tools[toolName])
576
+ return null;
577
+ tag('warning').log(`Repaired tool name '${toolCall.toolName}' → '${toolName}'`);
578
+ return { ...toolCall, toolName };
579
+ }
565
580
  export { AiError, Provider as AIProvider };
@@ -3,6 +3,11 @@ export declare function reportResearch(hash: string, text: string): void;
3
3
  export declare function clearResearchCache(): void;
4
4
  export declare function getCachedResearch(hash: string): string;
5
5
  export declare function getPreviousResearch(hash: string): string;
6
- export declare function saveResearch(hash: string, text: string, combinedHtml?: string): string;
7
- export declare function findSimilarResearch(combinedHtml: string): Promise<string | null>;
8
- export declare function findSimilarStateHash(combinedHtml: string): Promise<string | null>;
6
+ export declare function saveResearch(state: ResearchState, text: string, combinedHtml?: string): string;
7
+ export declare function findSimilarResearch(combinedHtml: string, url?: string): Promise<string | null>;
8
+ export declare function findSimilarStateHash(combinedHtml: string, url?: string): Promise<string | null>;
9
+ type ResearchState = {
10
+ hash: string;
11
+ url?: string;
12
+ };
13
+ export {};
@@ -55,7 +55,8 @@ export function getPreviousResearch(hash) {
55
55
  return '';
56
56
  return readFileSync(researchFile, 'utf8');
57
57
  }
58
- export function saveResearch(hash, text, combinedHtml) {
58
+ export function saveResearch(state, text, combinedHtml) {
59
+ const { hash, url } = state;
59
60
  const researchDir = outputPath('research');
60
61
  const researchFile = join(researchDir, `${hash}.md`);
61
62
  if (!existsSync(researchDir))
@@ -70,12 +71,15 @@ export function saveResearch(hash, text, combinedHtml) {
70
71
  mkdirSync(statesDir, { recursive: true });
71
72
  const fingerprint = computeHtmlFingerprint(combinedHtml);
72
73
  const fingerprintFile = join(statesDir, `${hash}.fingerprint`);
73
- writeFileSync(fingerprintFile, fingerprint.join('\n'));
74
+ const record = { entries: fingerprint };
75
+ if (url)
76
+ record.url = url;
77
+ writeFileSync(fingerprintFile, JSON.stringify(record));
74
78
  debugLog(`Fingerprint saved to ${fingerprintFile}`);
75
79
  }
76
80
  return researchFile;
77
81
  }
78
- function findSimilarMatch(combinedHtml) {
82
+ function findSimilarMatch(combinedHtml, url) {
79
83
  const statesDir = getStatesDir();
80
84
  if (!existsSync(statesDir))
81
85
  return Promise.resolve(null);
@@ -93,23 +97,24 @@ function findSimilarMatch(combinedHtml) {
93
97
  return;
94
98
  }
95
99
  debugLog(`Similar fingerprint found: ${matchHash} (${similarity}% similar)`);
96
- resolve({ hash: matchHash, similarity });
100
+ resolve({ hash: matchHash, similarity, url: data.url });
97
101
  });
98
102
  worker.postMessage({
99
103
  html: combinedHtml,
100
104
  statesDir,
101
105
  maxAgeMs: FINGERPRINT_MAX_AGE_MS,
102
106
  threshold: SIMILARITY_THRESHOLD,
107
+ url,
103
108
  });
104
109
  });
105
110
  }
106
- export async function findSimilarResearch(combinedHtml) {
107
- const match = await findSimilarMatch(combinedHtml);
111
+ export async function findSimilarResearch(combinedHtml, url) {
112
+ const match = await findSimilarMatch(combinedHtml, url);
108
113
  if (!match)
109
114
  return null;
110
115
  return getCachedResearch(match.hash) || null;
111
116
  }
112
- export async function findSimilarStateHash(combinedHtml) {
113
- const match = await findSimilarMatch(combinedHtml);
117
+ export async function findSimilarStateHash(combinedHtml, url) {
118
+ const match = await findSimilarMatch(combinedHtml, url);
114
119
  return match?.hash || null;
115
120
  }
@@ -96,7 +96,7 @@ export function WithDeepAnalysis(Base) {
96
96
  else {
97
97
  updated = `${cached.trimEnd()}\n\n# Extended Research\n\n${sectionMarkdown}\n`;
98
98
  }
99
- saveResearch(pageStateHash, updated);
99
+ saveResearch({ hash: pageStateHash }, updated);
100
100
  tag('substep').log(`Overlay research appended: ${focusArea.name}`);
101
101
  return sectionMarkdown;
102
102
  }
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { parentPort } from 'node:worker_threads';
4
4
  import { computeHtmlFingerprint } from "../../utils/html-diff.js";
5
+ import { isSamePageFamily } from "../../utils/url-matcher.js";
5
6
  function diceSimilarity(a, b) {
6
7
  let intersection = 0;
7
8
  for (const item of a) {
@@ -14,7 +15,7 @@ function diceSimilarity(a, b) {
14
15
  return Math.round(((2 * intersection) / total) * 100);
15
16
  }
16
17
  parentPort.on('message', (data) => {
17
- const { html, statesDir, maxAgeMs, threshold } = data;
18
+ const { html, statesDir, maxAgeMs, threshold, url } = data;
18
19
  if (!existsSync(statesDir)) {
19
20
  parentPort.postMessage({ matchHash: null, similarity: 0 });
20
21
  return;
@@ -28,19 +29,35 @@ parentPort.on('message', (data) => {
28
29
  const files = readdirSync(statesDir).filter((f) => f.endsWith('.fingerprint'));
29
30
  let bestHash = null;
30
31
  let bestSimilarity = 0;
32
+ let bestUrl;
31
33
  for (const file of files) {
32
34
  const filePath = join(statesDir, file);
33
35
  const mtime = statSync(filePath).mtimeMs;
34
36
  if (now - mtime > maxAgeMs)
35
37
  continue;
36
- const lines = readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
37
- const storedFingerprint = new Set(lines);
38
+ const record = readFingerprint(filePath);
39
+ if (url && record.url && !isSamePageFamily(url, record.url))
40
+ continue;
41
+ const storedFingerprint = new Set(record.entries);
38
42
  const similarity = diceSimilarity(currentFingerprint, storedFingerprint);
39
43
  if (similarity > bestSimilarity) {
40
44
  bestSimilarity = similarity;
41
45
  bestHash = file.replace('.fingerprint', '');
46
+ bestUrl = record.url;
42
47
  }
43
48
  }
44
49
  const matched = bestSimilarity >= threshold;
45
- parentPort.postMessage({ matchHash: matched ? bestHash : null, similarity: bestSimilarity });
50
+ parentPort.postMessage({ matchHash: matched ? bestHash : null, similarity: bestSimilarity, url: matched ? bestUrl : undefined });
46
51
  });
52
+ function readFingerprint(filePath) {
53
+ const content = readFileSync(filePath, 'utf8');
54
+ try {
55
+ const record = JSON.parse(content);
56
+ if (Array.isArray(record.entries))
57
+ return record;
58
+ }
59
+ catch {
60
+ return { entries: content.split('\n').filter(Boolean) };
61
+ }
62
+ return { entries: content.split('\n').filter(Boolean) };
63
+ }
@@ -72,6 +72,7 @@ export class Researcher extends ResearcherBase {
72
72
  let retriesLeft = opts._retriesLeft ?? maxRetries;
73
73
  this.actionResult = ActionResult.fromState(state);
74
74
  const stateHash = state.hash || this.actionResult.getStateHash();
75
+ const researchState = { ...state, hash: stateHash };
75
76
  if (!force && stateHash) {
76
77
  const cached = getCachedResearch(stateHash);
77
78
  if (cached) {
@@ -105,11 +106,11 @@ export class Researcher extends ResearcherBase {
105
106
  debugLog('Researching web page:', this.actionResult.url);
106
107
  const combinedHtml = await this.actionResult.combinedHtml();
107
108
  if (!deep && !force) {
108
- const similar = await findSimilarResearch(combinedHtml);
109
+ const similar = await findSimilarResearch(combinedHtml, state.url);
109
110
  if (similar) {
110
111
  tag('operation').log('Similar research found, reusing cached result');
111
112
  if (stateHash)
112
- saveResearch(stateHash, similar, combinedHtml);
113
+ saveResearch(researchState, similar, combinedHtml);
113
114
  tag('multiline').log(formatResearchSummary(similar));
114
115
  tag('success').log('Research complete (reused)');
115
116
  await this.hooksRunner.runAfterHook('researcher', state.url);
@@ -238,7 +239,7 @@ export class Researcher extends ResearcherBase {
238
239
  result.cleanup();
239
240
  let researchFile = null;
240
241
  if (stateHash) {
241
- researchFile = saveResearch(stateHash, result.text, combinedHtml);
242
+ researchFile = saveResearch(researchState, result.text, combinedHtml);
242
243
  }
243
244
  const summaryText = mdq(result.text).query('section2(/^summary/)').query('paragraph[0]').text().trim();
244
245
  const summaryLine = summaryText.split('\n')[0]?.trim().slice(0, 200);
@@ -2,10 +2,6 @@ import dedent from 'dedent';
2
2
  export const recommendedCodeceptCommands = ['I.click', 'I.type', 'I.fillField', 'I.see', 'I.seeElement'];
3
3
  const locatorPriorityRule = dedent `
4
4
  <locator_priority>
5
- When the page context shows the element a ref, such as [ref=e14], there is no locator to select: click it with clickRef
6
- and that ref. A ref names one exact element, so it never matches the wrong one and never has to be narrowed. Everything
7
- below is for elements the context gives no ref for.
8
-
9
5
  Use the following priority when selecting locators:
10
6
 
11
7
  1. ARIA locators (first choice) - target browser's accessibility tree, most reliable
@@ -76,7 +72,7 @@ const locatorStrategyRule = dedent `
76
72
 
77
73
  NEVER include \`eidx\` attribute in any locator (ARIA, CSS, XPath). It is an internal annotation.
78
74
 
79
- If <aria> section is not present or element is not found there, fall back to CSS/XPath locators from <html> section.
75
+ If the element is not found in the ARIA snapshot, fall back to CSS/XPath locators from page HTML.
80
76
 
81
77
  Stick to semantic attributes like role, aria-*, id, class, name, data-id, etc.
82
78
  Avoid IDs that follow framework auto-generation patterns (these change on every page load):
@@ -11,6 +11,7 @@ import { Researcher } from './researcher.js';
11
11
  import { TaskAgent } from './task-agent.js';
12
12
  export declare class Tester extends TaskAgent implements Agent {
13
13
  readonly ACTION_TOOLS: string[];
14
+ readonly DELEGATED_ACTION_TOOLS: string[];
14
15
  readonly SPECIAL_CONTEXT_ACTION_TOOLS: string[];
15
16
  emoji: string;
16
17
  requestStore: RequestStore;
@@ -49,7 +50,6 @@ export declare class Tester extends TaskAgent implements Agent {
49
50
  shouldStopForStalledExecution(task: Test, previousState: ActionResult, toolExecutions: any[]): boolean;
50
51
  prepareInstructionsForNextStep(task: Test): Promise<string>;
51
52
  reinjectContextIfNeeded(iteration: number, currentState: ActionResult): Promise<string>;
52
- interactiveAriaWithRefs(state: ActionResult): Promise<string>;
53
53
  finishTest(task: Test): void;
54
54
  abortStartedTestOnErrorPage(task: Test, actionResult: ActionResult): Promise<{
55
55
  success: boolean;
@@ -8,10 +8,11 @@ import { clearActivity, setActivity } from "../activity.js";
8
8
  import { Observability } from "../observability.js";
9
9
  import { Stats } from "../stats.js";
10
10
  import { TestResult } from "../test-plan.js";
11
- import { compactAriaSnapshot, detectFocusArea } from "../utils/aria.js";
11
+ import { detectFocusArea } from "../utils/aria.js";
12
12
  import { ErrorPageError, isErrorPage } from "../utils/error-page.js";
13
13
  import { createDebug, tag } from "../utils/logger.js";
14
14
  import { loop } from "../utils/loop.js";
15
+ import { compactErrorMessage } from "../utils/strings.js";
15
16
  import { actionRule, capabilityGroundingRule, dataProtectionRules, focusedElementRule, formRequirementsRule, locatorRule, multipleTabsRule, sectionContextRule } from "./rules.js";
16
17
  import { TaskAgent } from "./task-agent.js";
17
18
  import { createCodeceptJSTools, createIframeTools, withdrawVisionTools } from "./tools.js";
@@ -28,6 +29,7 @@ const SAMPLE_FILES = {
28
29
  };
29
30
  export class Tester extends TaskAgent {
30
31
  ACTION_TOOLS = ['click', 'hover', 'pressKey', 'form'];
32
+ DELEGATED_ACTION_TOOLS = ['interact'];
31
33
  SPECIAL_CONTEXT_ACTION_TOOLS = ['exitIframe'];
32
34
  emoji = '🧪';
33
35
  requestStore;
@@ -232,8 +234,6 @@ export class Tester extends TaskAgent {
232
234
  `);
233
235
  }
234
236
  conversation.cleanupTag('page_aria', '...cleaned aria snapshot...', 1);
235
- conversation.cleanupTag('page_html', '...cleaned HTML snapshot...', 1);
236
- conversation.cleanupTag('experience', '...cleaned experience...', 1);
237
237
  conversation.cleanupTag('applied_experience', '...cleaned past experience...', 1);
238
238
  conversation.cleanupTag('page_ui_map', '...cleaned UI map...', 1);
239
239
  conversation.cleanupTag('page_ui_map_overlay', '...cleaned UI overlay...', 1);
@@ -359,8 +359,6 @@ export class Tester extends TaskAgent {
359
359
  this.stalledIterations = 0;
360
360
  tag('info').log(`Pilot extending test (${extensions}/${this.MAX_EXTENSIONS})`);
361
361
  conversation.cleanupTag('page_aria', '...trimmed...', 1);
362
- conversation.cleanupTag('page_html', '...trimmed...', 0);
363
- conversation.cleanupTag('experience', '...trimmed...', 0);
364
362
  conversation.cleanupTag('page_ui_map', '...trimmed...', 0);
365
363
  conversation.cleanupTag('page_ui_map_overlay', '...trimmed...', 0);
366
364
  conversation.compactToolResults(1);
@@ -398,7 +396,7 @@ export class Tester extends TaskAgent {
398
396
  return false;
399
397
  const currentState = this.getCurrentState();
400
398
  const stateChanged = previousState.url !== currentState.url || previousState.hash !== currentState.hash;
401
- const actionTools = [...this.ACTION_TOOLS, ...this.SPECIAL_CONTEXT_ACTION_TOOLS];
399
+ const actionTools = [...this.ACTION_TOOLS, ...this.DELEGATED_ACTION_TOOLS, ...this.SPECIAL_CONTEXT_ACTION_TOOLS];
402
400
  const hasSuccessfulAction = toolExecutions.some((execution) => execution.wasSuccessful && actionTools.includes(execution.toolName));
403
401
  const hasSuccessfulAssertion = toolExecutions.some((execution) => execution.wasSuccessful && this.ASSERTION_TOOLS.includes(execution.toolName));
404
402
  if (stateChanged || hasSuccessfulAction || hasSuccessfulAssertion) {
@@ -422,6 +420,7 @@ export class Tester extends TaskAgent {
422
420
 
423
421
  <rules>
424
422
  Use tools ${this.ACTION_TOOLS.join(', ')} to interact with the page.
423
+ Fall back to interact() when those fail, when the step needs a sequence of actions, or when your context is not enough to locate the element.
425
424
  Use tool names exactly as listed in this prompt. Do not invent combined tool names, aliases, or names with channel markers such as "commentary".
426
425
  Match each tool input schema exactly. Do not invent parameter names or pass extra fields.
427
426
  Do not do unsuccesful clicks again.
@@ -532,13 +531,13 @@ export class Tester extends TaskAgent {
532
531
  </page>
533
532
 
534
533
  <page_aria>
535
- ${await this.interactiveAriaWithRefs(currentState)}
534
+ ${currentState.getInteractiveARIA()}
536
535
  </page_aria>
537
536
  ${uiMapSection}
538
537
 
539
538
  Use <page_ui_map> to understand the page structure and its main elements.
540
- However, <page_ui_map> is not always up to date, use <page_aria> and <page_html> to understand the ACTUAL state of the page
541
- Do not interact with elements that are not listed in <page_aria> and <page_html>
539
+ However, <page_ui_map> is not always up to date, use <page_aria> to understand the ACTUAL state of the page
540
+ Do not interact with elements that are not listed in <page_aria> or in HTML returned by tools
542
541
  Refer to information on page sections in <page_ui_map> and use container CSS locators to interact with elements inside sections
543
542
  `;
544
543
  return context;
@@ -567,16 +566,10 @@ export class Tester extends TaskAgent {
567
566
  </page>
568
567
 
569
568
  <page_aria>
570
- ${await this.interactiveAriaWithRefs(currentState)}
569
+ ${currentState.getInteractiveARIA()}
571
570
  </page_aria>
572
571
  `;
573
572
  }
574
- async interactiveAriaWithRefs(state) {
575
- const withRefs = await Promise.resolve(this.explorer?.withPage?.((page) => page.locator('body').ariaSnapshot({ mode: 'ai' }))).catch(() => null);
576
- if (!withRefs)
577
- return state.getInteractiveARIA();
578
- return compactAriaSnapshot(withRefs, false);
579
- }
580
573
  finishTest(task) {
581
574
  if (!task.result) {
582
575
  if (task.hasAchievedAll())
@@ -646,7 +639,7 @@ export class Tester extends TaskAgent {
646
639
 
647
640
  <rules>
648
641
  - Refer to UI Map from <page_ui_map> to understand the page structure and its main elements
649
- - Use only elements that exist in the provided ARIA tree or HTML, <page_aria> and <page_html>
642
+ - Use only elements that exist in <page_aria> or in HTML returned by tools
650
643
  - Use tool input schemas exactly as documented. Do not invent parameter names or add fields not listed by the tool schema.
651
644
  - Use click() for buttons, links, and clickable elements ONLY - do NOT include I.fillField() or I.type() commands in click() tool
652
645
  - click() commands array is for FALLBACK LOCATORS of the SAME element, NOT for clicking different elements in sequence. If you need to click two different elements, make two separate click() calls.
@@ -669,7 +662,8 @@ export class Tester extends TaskAgent {
669
662
  - Check for error messages to understand if there are issues
670
663
  - Verify if data was correctly saved and changes are reflected on the page
671
664
  - By default, you receive accessibility tree data which shows interactive elements and page structure
672
- - Understand current context by following <page_html>, <page_aria>, and <page_ui_map>
665
+ - Full page HTML is never injected automatically. When ARIA is not enough, delegate to the tools that read it: verify() to assert, interact() to act
666
+ - Understand current context by following <page_aria> and <page_ui_map>
673
667
  - Before submitting form, check all inputs were filled in correctly using see() tool
674
668
  - When you interact with form with inputs, ensure that you click corresponding button to save its data
675
669
  - Follow <locator_priority> rules when selecting locators for all tools
@@ -720,7 +714,6 @@ export class Tester extends TaskAgent {
720
714
  }
721
715
  buildScenarioBlock(task, actionResult) {
722
716
  const knowledge = this.getKnowledge(actionResult);
723
- const experience = this.getExperience(actionResult);
724
717
  return dedent `
725
718
  <task>
726
719
  SCENARIO GOAL: ${task.scenario}
@@ -749,8 +742,6 @@ export class Tester extends TaskAgent {
749
742
  ${this.buildAvailableFiles()}
750
743
 
751
744
  ${knowledge}
752
-
753
- ${experience}
754
745
  `;
755
746
  }
756
747
  getDeletableSessionNames(task) {
@@ -860,7 +851,7 @@ export class Tester extends TaskAgent {
860
851
  explanation,
861
852
  };
862
853
  if (resetAction.lastError) {
863
- result.error = resetAction.lastError.toString();
854
+ result.error = compactErrorMessage(resetAction.lastError);
864
855
  }
865
856
  return result;
866
857
  },
@@ -1,5 +1,5 @@
1
1
  import { ActionResult, type PageDiff } from '../action-result.js';
2
- import type { ExperienceTracker } from '../experience-tracker.js';
2
+ import { type ExperienceTracker } from '../experience-tracker.js';
3
3
  import { type Task } from '../test-plan.js';
4
4
  import type { ToolDeps } from './agent.js';
5
5
  import { Navigator } from './navigator.js';
@@ -16,10 +16,6 @@ export declare function createCodeceptJSTools({ explorer, stateManager, ai }: To
16
16
  commands: any;
17
17
  explanation: any;
18
18
  }, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
19
- clickRef: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
20
- ref: any;
21
- element: any;
22
- }, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
23
19
  hover: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
24
20
  commands: any;
25
21
  explanation: any;
@@ -34,6 +30,12 @@ export declare function createCodeceptJSTools({ explorer, stateManager, ai }: To
34
30
  explanation: any;
35
31
  }, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
36
32
  };
33
+ export declare function createRefTools({ explorer, stateManager }: ToolDeps, task: Task): {
34
+ clickRef: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
35
+ ref: any;
36
+ element: any;
37
+ }, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
38
+ };
37
39
  export declare function createIframeTools({ explorer, stateManager }: ToolDeps): {
38
40
  exitIframe: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
39
41
  reason?: string;
@@ -59,6 +61,7 @@ export declare function successToolResult(action: string, data?: Record<string,
59
61
  assertionSteps?: any[];
60
62
  }): Record<string, any>;
61
63
  export declare function isMajorPageChange(pageDiff: PageDiff): boolean;
64
+ export declare function hasFailedRequest(pageDiff: PageDiff): boolean;
62
65
  export declare function failedToolResult(action: string, message: string, data?: Record<string, any>, error?: Error | null): Promise<Record<string, any>>;
63
66
  export declare function withdrawVisionTools(tools: Record<string, any>): void;
64
67
  export declare function clickFailureSuggestion(attempts: Array<{