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.
- package/boat/prima/README.md +96 -0
- package/boat/prima/package.json +14 -10
- package/boat/prima/src/cli.ts +5 -0
- package/boat/prima/src/prima.ts +17 -4
- package/dist/boat/prima/src/cli.js +7 -0
- package/dist/boat/prima/src/prima.js +18 -4
- package/dist/models.json +4 -4
- package/dist/package.json +6 -2
- package/dist/src/action-result.d.ts +13 -0
- package/dist/src/action-result.js +46 -15
- package/dist/src/action.d.ts +5 -2
- package/dist/src/action.js +48 -17
- package/dist/src/ai/captain/web-mode.js +1 -2
- package/dist/src/ai/captain.d.ts +20 -0
- package/dist/src/ai/captain.js +10 -1
- package/dist/src/ai/driller.js +6 -2
- package/dist/src/ai/fisherman-tools.d.ts +40 -1
- package/dist/src/ai/fisherman-tools.js +39 -0
- package/dist/src/ai/fisherman.js +2 -1
- package/dist/src/ai/navigator.d.ts +2 -1
- package/dist/src/ai/navigator.js +5 -9
- package/dist/src/ai/pilot.js +39 -22
- package/dist/src/ai/planner/subpages.js +2 -16
- package/dist/src/ai/planner.js +1 -1
- package/dist/src/ai/provider.js +16 -1
- package/dist/src/ai/researcher/cache.d.ts +8 -3
- package/dist/src/ai/researcher/cache.js +13 -8
- package/dist/src/ai/researcher/deep-analysis.js +1 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +21 -4
- package/dist/src/ai/researcher.js +4 -3
- package/dist/src/ai/rules.js +1 -5
- package/dist/src/ai/tester.d.ts +1 -1
- package/dist/src/ai/tester.js +13 -22
- package/dist/src/ai/tools.d.ts +8 -5
- package/dist/src/ai/tools.js +79 -56
- package/dist/src/commands/init-command.js +13 -20
- package/dist/src/config.js +3 -1
- package/dist/src/experience-tracker.d.ts +2 -0
- package/dist/src/experience-tracker.js +12 -0
- package/dist/src/explorbot.js +1 -1
- package/dist/src/playwright-recorder.js +6 -12
- package/dist/src/test-plan.d.ts +8 -0
- package/dist/src/test-plan.js +11 -0
- package/dist/src/utils/html-diff.d.ts +5 -0
- package/dist/src/utils/html-diff.js +65 -6
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +32 -0
- package/dist/src/utils/url-matcher.d.ts +1 -0
- package/dist/src/utils/url-matcher.js +31 -2
- package/docs/basics/getting-started.md +33 -10
- package/docs/basics/providers.md +6 -4
- package/docs/contributing/npm-package.md +73 -4
- package/models.json +4 -4
- package/package.json +6 -2
- package/src/action-result.ts +61 -16
- package/src/action.ts +51 -17
- package/src/ai/captain/web-mode.ts +1 -2
- package/src/ai/captain.ts +9 -1
- package/src/ai/driller.ts +6 -2
- package/src/ai/fisherman-tools.ts +35 -0
- package/src/ai/fisherman.ts +2 -1
- package/src/ai/navigator.ts +6 -10
- package/src/ai/pilot.ts +41 -24
- package/src/ai/planner/subpages.ts +2 -13
- package/src/ai/planner.ts +1 -1
- package/src/ai/provider.ts +17 -1
- package/src/ai/researcher/cache.ts +17 -9
- package/src/ai/researcher/deep-analysis.ts +1 -1
- package/src/ai/researcher/fingerprint-worker.ts +23 -5
- package/src/ai/researcher.ts +4 -3
- package/src/ai/rules.ts +1 -5
- package/src/ai/tester.ts +13 -22
- package/src/ai/tools.ts +84 -60
- package/src/commands/init-command.ts +14 -20
- package/src/config.ts +2 -1
- package/src/experience-tracker.ts +13 -0
- package/src/explorbot.ts +1 -1
- package/src/playwright-recorder.ts +6 -11
- package/src/test-plan.ts +18 -0
- package/src/utils/html-diff.ts +72 -7
- package/src/utils/strings.ts +36 -0
- package/src/utils/url-matcher.ts +27 -2
package/dist/src/ai/provider.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
37
|
-
|
|
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(
|
|
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(
|
|
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);
|
package/dist/src/ai/rules.js
CHANGED
|
@@ -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
|
|
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):
|
package/dist/src/ai/tester.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/ai/tester.js
CHANGED
|
@@ -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 {
|
|
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
|
-
${
|
|
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>
|
|
541
|
-
Do not interact with elements that are not listed in <page_aria>
|
|
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
|
-
${
|
|
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
|
|
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
|
-
-
|
|
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
|
|
854
|
+
result.error = compactErrorMessage(resetAction.lastError);
|
|
864
855
|
}
|
|
865
856
|
return result;
|
|
866
857
|
},
|
package/dist/src/ai/tools.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ActionResult, type PageDiff } from '../action-result.js';
|
|
2
|
-
import type
|
|
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<{
|