explorbot 0.1.32-beta.1 → 0.2.1
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/README.md +35 -19
- package/bin/explorbot-cli.ts +33 -10
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +26 -28
- package/boat/doc-collector/src/ai/tools.ts +74 -16
- package/boat/doc-collector/src/cli.ts +1 -6
- package/boat/doc-collector/src/docbot.ts +30 -9
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +34 -2
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +29 -9
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +19 -7
- package/dist/boat/doc-collector/src/ai/tools.js +53 -16
- package/dist/boat/doc-collector/src/cli.js +1 -5
- package/dist/boat/doc-collector/src/docbot.js +27 -8
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +30 -1
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/models.json +30 -0
- package/dist/package.json +23 -3
- package/dist/src/action-result.d.ts +8 -10
- package/dist/src/action-result.js +72 -164
- package/dist/src/action.d.ts +13 -12
- package/dist/src/action.js +37 -113
- package/dist/src/ai/agent.d.ts +17 -0
- package/dist/src/ai/captain/idle-mode.js +1 -1
- package/dist/src/ai/captain/test-mode.js +1 -1
- package/dist/src/ai/captain/web-mode.js +18 -29
- package/dist/src/ai/captain.d.ts +1 -3
- package/dist/src/ai/captain.js +17 -45
- package/dist/src/ai/conversation.d.ts +2 -5
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +2 -17
- package/dist/src/ai/driller.js +26 -50
- package/dist/src/ai/experience-compactor.d.ts +2 -2
- package/dist/src/ai/experience-compactor.js +16 -33
- package/dist/src/ai/fisherman-tools.js +1 -1
- package/dist/src/ai/fisherman.d.ts +0 -1
- package/dist/src/ai/fisherman.js +0 -3
- package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
- package/dist/src/ai/historian/codeceptjs.js +4 -3
- package/dist/src/ai/historian/playwright.d.ts +4 -1
- package/dist/src/ai/historian/playwright.js +4 -3
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -2
- package/dist/src/ai/historian/utils.d.ts +0 -1
- package/dist/src/ai/historian/utils.js +0 -1
- package/dist/src/ai/historian.d.ts +10 -8
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +8 -6
- package/dist/src/ai/navigator.js +34 -65
- package/dist/src/ai/pilot.d.ts +13 -8
- package/dist/src/ai/pilot.js +53 -59
- package/dist/src/ai/planner.d.ts +5 -4
- package/dist/src/ai/planner.js +66 -52
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +77 -137
- package/dist/src/ai/quartermaster.d.ts +22 -22
- package/dist/src/ai/quartermaster.js +8 -14
- package/dist/src/ai/rerunner.d.ts +3 -13
- package/dist/src/ai/rerunner.js +23 -44
- package/dist/src/ai/researcher/cache.js +11 -18
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
- package/dist/src/ai/researcher/deep-analysis.js +8 -11
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- package/dist/src/ai/researcher/parser.js +1 -1
- package/dist/src/ai/researcher/research-result.d.ts +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -19
- package/dist/src/ai/researcher/sections.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +7 -3
- package/dist/src/ai/researcher.js +40 -106
- package/dist/src/ai/rules.d.ts +1 -0
- package/dist/src/ai/rules.js +22 -15
- package/dist/src/ai/session-analyst.js +8 -5
- package/dist/src/ai/task-agent.d.ts +19 -6
- package/dist/src/ai/task-agent.js +37 -33
- package/dist/src/ai/tester.d.ts +6 -14
- package/dist/src/ai/tester.js +46 -111
- package/dist/src/ai/tools.d.ts +26 -14
- package/dist/src/ai/tools.js +138 -132
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/compact-command.d.ts +1 -1
- package/dist/src/commands/compact-command.js +6 -6
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +8 -6
- package/dist/src/commands/context-data-command.js +2 -2
- package/dist/src/commands/context-experience-command.js +2 -2
- package/dist/src/commands/context-html-command.js +2 -2
- package/dist/src/commands/context-knowledge-command.js +2 -2
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/experience-command.js +1 -1
- package/dist/src/commands/explore-command.js +4 -3
- package/dist/src/commands/freesail-command.js +2 -2
- package/dist/src/commands/knows-command.js +1 -1
- package/dist/src/commands/learn-command.js +5 -3
- package/dist/src/commands/path-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/test-command.js +1 -1
- package/dist/src/components/AddKnowledge.d.ts +2 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/App.js +3 -3
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.js +156 -10
- package/dist/src/experience-tracker.d.ts +11 -6
- package/dist/src/experience-tracker.js +82 -155
- package/dist/src/explorbot.d.ts +21 -13
- package/dist/src/explorbot.js +84 -80
- package/dist/src/explorer.d.ts +66 -102
- package/dist/src/explorer.js +332 -623
- package/dist/src/knowledge-tracker.d.ts +2 -2
- package/dist/src/knowledge-tracker.js +38 -45
- package/dist/src/state-manager.d.ts +10 -30
- package/dist/src/state-manager.js +11 -129
- package/dist/src/utils/aria.d.ts +6 -1
- package/dist/src/utils/aria.js +4 -1
- package/dist/src/utils/cache.d.ts +15 -0
- package/dist/src/utils/cache.js +34 -0
- package/dist/src/utils/context-formatter.d.ts +6 -0
- package/dist/src/utils/context-formatter.js +15 -27
- package/dist/src/utils/hooks-runner.js +2 -4
- package/dist/src/utils/html.d.ts +5 -0
- package/dist/src/utils/html.js +71 -0
- package/dist/src/utils/markdown-files.d.ts +10 -0
- package/dist/src/utils/markdown-files.js +21 -0
- package/dist/src/utils/markdown-query.d.ts +6 -0
- package/dist/src/utils/markdown-query.js +14 -1
- package/dist/src/utils/page-readiness.d.ts +1 -0
- package/dist/src/utils/page-readiness.js +1 -1
- package/dist/src/utils/secrets.js +2 -2
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +10 -0
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/web-annotate.d.ts +5 -0
- package/dist/src/utils/web-annotate.js +58 -0
- package/dist/src/utils/web-eidx.d.ts +2 -0
- package/dist/src/utils/web-eidx.js +20 -0
- package/dist/src/utils/web-element.d.ts +3 -1
- package/dist/src/utils/web-element.js +32 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/models.json +30 -0
- package/package.json +23 -3
- package/src/action-result.ts +70 -173
- package/src/action.ts +38 -132
- package/src/ai/agent.ts +20 -0
- package/src/ai/captain/idle-mode.ts +1 -1
- package/src/ai/captain/test-mode.ts +1 -1
- package/src/ai/captain/web-mode.ts +18 -30
- package/src/ai/captain.ts +17 -42
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +53 -83
- package/src/ai/experience-compactor.ts +16 -35
- package/src/ai/fisherman-tools.ts +1 -1
- package/src/ai/fisherman.ts +0 -4
- package/src/ai/historian/codeceptjs.ts +5 -3
- package/src/ai/historian/playwright.ts +7 -4
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +6 -8
- package/src/ai/navigator.ts +39 -70
- package/src/ai/pilot.ts +59 -61
- package/src/ai/planner.ts +76 -62
- package/src/ai/provider.ts +85 -146
- package/src/ai/quartermaster.ts +39 -45
- package/src/ai/rerunner.ts +27 -50
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +10 -11
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +9 -3
- package/src/ai/researcher.ts +42 -119
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +45 -38
- package/src/ai/tester.ts +51 -129
- package/src/ai/tools.ts +145 -153
- package/src/command-handler.ts +1 -1
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +8 -6
- package/src/commands/context-data-command.ts +2 -2
- package/src/commands/context-experience-command.ts +2 -2
- package/src/commands/context-html-command.ts +2 -2
- package/src/commands/context-knowledge-command.ts +2 -2
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +5 -3
- package/src/commands/path-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/test-command.ts +1 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +3 -3
- package/src/config.ts +185 -11
- package/src/experience-tracker.ts +89 -152
- package/src/explorbot.ts +88 -83
- package/src/explorer.ts +402 -693
- package/src/knowledge-tracker.ts +42 -49
- package/src/state-manager.ts +20 -155
- package/src/utils/aria.ts +11 -2
- package/src/utils/cache.ts +40 -0
- package/src/utils/context-formatter.ts +12 -15
- package/src/utils/hooks-runner.ts +2 -4
- package/src/utils/html.ts +79 -0
- package/src/utils/markdown-files.ts +30 -0
- package/src/utils/markdown-query.ts +15 -1
- package/src/utils/page-readiness.ts +1 -1
- package/src/utils/secrets.ts +2 -3
- package/src/utils/strings.ts +12 -0
- package/src/utils/test-files.ts +1 -1
- package/src/utils/web-annotate.ts +64 -0
- package/src/utils/web-eidx.ts +21 -0
- package/src/utils/web-element.ts +34 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/dist/boat/api-tester/example/apibot.config.js +0 -30
package/src/ai/tools.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { tool } from 'ai';
|
|
2
2
|
import dedent from 'dedent';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { ActionResult, type ToolResultMetadata } from '../action-result.ts';
|
|
4
|
+
import { ActionResult, type PageDiff, type ToolResultMetadata } from '../action-result.ts';
|
|
5
5
|
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
6
|
-
import type Explorer from '../explorer.ts';
|
|
7
6
|
import { type Task, TestResult } from '../test-plan.js';
|
|
8
|
-
import { extractFocusedElement } from '../utils/aria.ts';
|
|
7
|
+
import { LARGE_ARIA_CHANGE_THRESHOLD, extractFocusedElement } from '../utils/aria.ts';
|
|
9
8
|
import { isFatalBrowserError } from '../utils/browser-errors.ts';
|
|
10
9
|
import { createDebug, tag } from '../utils/logger.js';
|
|
11
10
|
import { pause } from '../utils/loop.js';
|
|
12
11
|
import { WebElement } from '../utils/web-element.ts';
|
|
12
|
+
import type { ToolDeps } from './agent.ts';
|
|
13
13
|
import { Navigator } from './navigator.ts';
|
|
14
14
|
import type { AIProvider } from './provider.ts';
|
|
15
15
|
import { Researcher } from './researcher.ts';
|
|
@@ -18,11 +18,16 @@ import { isInteractive } from './task-agent.ts';
|
|
|
18
18
|
|
|
19
19
|
const debugLog = createDebug('explorbot:tools');
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
interface AgentToolDeps extends ToolDeps {
|
|
22
|
+
researcher: Researcher;
|
|
23
|
+
navigator: Navigator;
|
|
24
|
+
supervisor?: boolean;
|
|
25
|
+
withExperience?: boolean;
|
|
26
|
+
}
|
|
22
27
|
|
|
23
|
-
export
|
|
24
|
-
const stateManager = explorer.getStateManager();
|
|
28
|
+
export const ASSERTION_TOOLS = ['verify'] as const;
|
|
25
29
|
|
|
30
|
+
export function createCodeceptJSTools({ explorer, stateManager, ai }: ToolDeps, task: Task) {
|
|
26
31
|
return {
|
|
27
32
|
click: tool({
|
|
28
33
|
description: dedent`
|
|
@@ -40,6 +45,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
40
45
|
CRITICAL: All commands MUST target the SAME element using different locators.
|
|
41
46
|
This is a FALLBACK list, NOT a sequence of different clicks.
|
|
42
47
|
If you need to click multiple different elements, make SEPARATE click() calls.
|
|
48
|
+
Inline create/edit UIs may use icon-only confirm controls near the edited field instead of text buttons. Target the nearest actionable icon/control in the same row or form.
|
|
43
49
|
`,
|
|
44
50
|
inputSchema: z.object({
|
|
45
51
|
commands: z.array(z.string()).describe(dedent`
|
|
@@ -80,33 +86,27 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
80
86
|
});
|
|
81
87
|
|
|
82
88
|
const previousState = ActionResult.fromState(stateManager.getCurrentState()!);
|
|
83
|
-
const action = explorer.
|
|
89
|
+
const action = explorer.action();
|
|
84
90
|
const attempts: Array<{ command: string; success: boolean; error?: string }> = [];
|
|
85
91
|
|
|
86
92
|
for (let i = 0; i < commands.length; i++) {
|
|
87
93
|
const command = transformContainsCommand(commands[i]);
|
|
88
|
-
const
|
|
89
|
-
const success = await action.attempt(command, explanation, isLast);
|
|
94
|
+
const success = await action.attempt(command, explanation);
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
...(action.lastError && { error: action.lastError.toString() }),
|
|
95
|
-
});
|
|
96
|
+
const attempt: { command: string; success: boolean; error?: string } = { command, success };
|
|
97
|
+
if (action.lastError) attempt.error = action.lastError.toString();
|
|
98
|
+
attempts.push(attempt);
|
|
96
99
|
|
|
97
100
|
if (success) {
|
|
98
101
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()!).toToolResult(previousState, command);
|
|
99
|
-
|
|
100
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
101
|
-
}
|
|
102
|
-
activeNote.commit(TestResult.PASSED);
|
|
102
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
103
103
|
return successToolResult('click', { ...toolResult, attempts, code: command }, action);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
let disambiguated = null;
|
|
108
108
|
if (attempts.some((a) => a.error?.toLowerCase().includes(MULTIPLE_ELEMENTS_PATTERN))) {
|
|
109
|
-
disambiguated = await disambiguateElements(action.lastError, explanation,
|
|
109
|
+
disambiguated = await disambiguateElements(action.lastError, explanation, ai);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
if (disambiguated) {
|
|
@@ -119,24 +119,18 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
119
119
|
retryCommands.push(`I.click('${disambiguated.xpath.replace(/'/g, "\\'")}')`);
|
|
120
120
|
|
|
121
121
|
for (const retryCmd of retryCommands) {
|
|
122
|
-
if (!(await action.attempt(retryCmd, explanation
|
|
122
|
+
if (!(await action.attempt(retryCmd, explanation))) {
|
|
123
123
|
attempts.push({ command: retryCmd, success: false, error: action.lastError?.toString() });
|
|
124
124
|
continue;
|
|
125
125
|
}
|
|
126
126
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()!).toToolResult(previousState, retryCmd);
|
|
127
|
-
|
|
128
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
129
|
-
}
|
|
130
|
-
activeNote.commit(TestResult.PASSED);
|
|
127
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
131
128
|
return successToolResult('click', { ...toolResult, attempts, code: retryCmd, disambiguated: true }, action);
|
|
132
129
|
}
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()!).toToolResult(previousState, commands[0]);
|
|
136
|
-
|
|
137
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
138
|
-
}
|
|
139
|
-
activeNote.commit(TestResult.FAILED);
|
|
133
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
140
134
|
|
|
141
135
|
let suggestion = "Try xpathCheck() to find the element's actual position, see() for visual analysis, or visualClick() to click by visual appearance.";
|
|
142
136
|
const lastError = attempts[attempts.length - 1]?.error || '';
|
|
@@ -204,16 +198,14 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
204
198
|
});
|
|
205
199
|
|
|
206
200
|
const previousState = ActionResult.fromState(stateManager.getCurrentState()!);
|
|
207
|
-
const action = explorer.
|
|
201
|
+
const action = explorer.action();
|
|
208
202
|
const attempts: Array<{ command: string; success: boolean; error?: string }> = [];
|
|
209
203
|
|
|
210
204
|
for (const command of commands) {
|
|
211
|
-
const success = await action.attempt(command, explanation
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
...(action.lastError && { error: action.lastError.toString() }),
|
|
216
|
-
});
|
|
205
|
+
const success = await action.attempt(command, explanation);
|
|
206
|
+
const attempt: { command: string; success: boolean; error?: string } = { command, success };
|
|
207
|
+
if (action.lastError) attempt.error = action.lastError.toString();
|
|
208
|
+
attempts.push(attempt);
|
|
217
209
|
|
|
218
210
|
if (!success) continue;
|
|
219
211
|
|
|
@@ -276,16 +268,13 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
276
268
|
|
|
277
269
|
if (!isSingleChar && !isStandardKey) {
|
|
278
270
|
const previousState = ActionResult.fromState(stateManager.getCurrentState()!);
|
|
279
|
-
const action = explorer.
|
|
271
|
+
const action = explorer.action();
|
|
280
272
|
const typeCommand = `I.type(${JSON.stringify(key)})`;
|
|
281
273
|
await action.attempt(typeCommand, explanation);
|
|
282
274
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()!).toToolResult(previousState, key);
|
|
283
275
|
|
|
284
276
|
if (!action.lastError) {
|
|
285
|
-
|
|
286
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
287
|
-
}
|
|
288
|
-
activeNote.commit(TestResult.PASSED);
|
|
277
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
289
278
|
return successToolResult(
|
|
290
279
|
'pressKey',
|
|
291
280
|
{
|
|
@@ -299,10 +288,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
299
288
|
}
|
|
300
289
|
|
|
301
290
|
const errorMsg = `pressKey fallback to type() failed: ${action.lastError?.toString()}`;
|
|
302
|
-
|
|
303
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
304
|
-
}
|
|
305
|
-
activeNote.commit(TestResult.FAILED);
|
|
291
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
306
292
|
return failedToolResult('pressKey', errorMsg, {
|
|
307
293
|
...toolResult,
|
|
308
294
|
code: typeCommand,
|
|
@@ -325,7 +311,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
325
311
|
}
|
|
326
312
|
|
|
327
313
|
const previousState = ActionResult.fromState(stateManager.getCurrentState()!);
|
|
328
|
-
const action = explorer.
|
|
314
|
+
const action = explorer.action();
|
|
329
315
|
|
|
330
316
|
let pressKeyCommand: string;
|
|
331
317
|
if (modifier) {
|
|
@@ -339,10 +325,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
339
325
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()!).toToolResult(previousState, key);
|
|
340
326
|
|
|
341
327
|
if (!action.lastError) {
|
|
342
|
-
|
|
343
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
344
|
-
}
|
|
345
|
-
activeNote.commit(TestResult.PASSED);
|
|
328
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
346
329
|
return successToolResult(
|
|
347
330
|
'pressKey',
|
|
348
331
|
{
|
|
@@ -355,10 +338,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
355
338
|
}
|
|
356
339
|
|
|
357
340
|
const errorMsg = `pressKey() failed: ${action.lastError?.toString()}`;
|
|
358
|
-
|
|
359
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
360
|
-
}
|
|
361
|
-
activeNote.commit(TestResult.FAILED);
|
|
341
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
362
342
|
return failedToolResult('pressKey', errorMsg, {
|
|
363
343
|
...toolResult,
|
|
364
344
|
code: pressKeyCommand,
|
|
@@ -367,7 +347,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
367
347
|
} catch (error) {
|
|
368
348
|
throwIfFatalBrowserError(error);
|
|
369
349
|
activeNote.commit(TestResult.FAILED);
|
|
370
|
-
const errorMessage = error
|
|
350
|
+
const errorMessage = errorText(error);
|
|
371
351
|
return failedToolResult('pressKey', `PressKey tool failed: ${errorMessage}`);
|
|
372
352
|
}
|
|
373
353
|
},
|
|
@@ -431,26 +411,18 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
431
411
|
|
|
432
412
|
const previousState = ActionResult.fromState(stateManager.getCurrentState()!);
|
|
433
413
|
const formLocator = codeLines[0] || 'form';
|
|
434
|
-
const action = explorer.
|
|
435
|
-
const wasInIframe = await explorer.isInsideIframe();
|
|
414
|
+
const action = explorer.action();
|
|
436
415
|
await action.attempt(codeBlock, explanation);
|
|
437
416
|
|
|
438
|
-
if (action.lastError && !wasInIframe && (await explorer.isInsideIframe())) {
|
|
439
|
-
await explorer.switchToMainFrame();
|
|
440
|
-
}
|
|
441
|
-
|
|
442
417
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()!).toToolResult(previousState, formLocator);
|
|
443
418
|
|
|
444
419
|
if (action.lastError) {
|
|
445
420
|
const message = action.lastError ? String(action.lastError) : 'Unknown error';
|
|
446
|
-
|
|
447
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
448
|
-
}
|
|
449
|
-
activeNote.commit(TestResult.FAILED);
|
|
421
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
450
422
|
|
|
451
423
|
let formSuggestion = 'Look into error message and identify which commands passed and which failed. Continue execution using step-by-step approach using click() and form() tools.';
|
|
452
424
|
if (message.toLowerCase().includes(MULTIPLE_ELEMENTS_PATTERN)) {
|
|
453
|
-
const disambiguated = await disambiguateElements(action.lastError, explanation,
|
|
425
|
+
const disambiguated = await disambiguateElements(action.lastError, explanation, ai);
|
|
454
426
|
if (disambiguated) {
|
|
455
427
|
formSuggestion = `Multiple elements matched. Add step.opts({ elementIndex: ${disambiguated.position} }) to the failing command. Fallback locator: ${disambiguated.xpath}`;
|
|
456
428
|
}
|
|
@@ -468,10 +440,15 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
468
440
|
);
|
|
469
441
|
}
|
|
470
442
|
|
|
471
|
-
if (toolResult
|
|
472
|
-
activeNote.
|
|
443
|
+
if (!hasObservablePageChange(toolResult)) {
|
|
444
|
+
activeNote.commit(TestResult.FAILED);
|
|
445
|
+
return failedToolResult('form', 'Form command executed, but no observable page or form-state change was captured.', {
|
|
446
|
+
...toolResult,
|
|
447
|
+
code: codeBlock,
|
|
448
|
+
suggestion: 'Treat the field/form action as not completed. Re-locate the editable control, check whether another UI layer is active, then retry and verify the field value before submitting.',
|
|
449
|
+
});
|
|
473
450
|
}
|
|
474
|
-
activeNote
|
|
451
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
475
452
|
return successToolResult(
|
|
476
453
|
'form',
|
|
477
454
|
{
|
|
@@ -486,7 +463,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
486
463
|
} catch (error) {
|
|
487
464
|
throwIfFatalBrowserError(error);
|
|
488
465
|
activeNote.commit(TestResult.FAILED);
|
|
489
|
-
const errorMessage = error
|
|
466
|
+
const errorMessage = errorText(error);
|
|
490
467
|
return failedToolResult('form', `Form tool failed: ${errorMessage}`);
|
|
491
468
|
}
|
|
492
469
|
},
|
|
@@ -494,13 +471,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) {
|
|
|
494
471
|
};
|
|
495
472
|
}
|
|
496
473
|
|
|
497
|
-
export function
|
|
498
|
-
const stateManager = explorer.getStateManager();
|
|
499
|
-
|
|
500
|
-
if (context !== 'iframe') {
|
|
501
|
-
return {};
|
|
502
|
-
}
|
|
503
|
-
|
|
474
|
+
export function createIframeTools({ explorer, stateManager }: ToolDeps) {
|
|
504
475
|
return {
|
|
505
476
|
exitIframe: tool({
|
|
506
477
|
description: dedent`
|
|
@@ -522,9 +493,9 @@ export function createSpecialContextTools(explorer: Explorer, context: 'iframe')
|
|
|
522
493
|
});
|
|
523
494
|
}
|
|
524
495
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const nextState =
|
|
496
|
+
const action = explorer.action();
|
|
497
|
+
await action.execute('I.switchTo()');
|
|
498
|
+
const nextState = action.getActionResult()!;
|
|
528
499
|
const toolResult = await nextState.toToolResult(previousState, 'I.switchTo()');
|
|
529
500
|
|
|
530
501
|
return successToolResult('exitIframe', {
|
|
@@ -534,7 +505,7 @@ export function createSpecialContextTools(explorer: Explorer, context: 'iframe')
|
|
|
534
505
|
});
|
|
535
506
|
} catch (error) {
|
|
536
507
|
throwIfFatalBrowserError(error);
|
|
537
|
-
const errorMessage = error
|
|
508
|
+
const errorMessage = errorText(error);
|
|
538
509
|
return failedToolResult('exitIframe', `Failed to exit iframe: ${errorMessage}`);
|
|
539
510
|
}
|
|
540
511
|
},
|
|
@@ -542,21 +513,32 @@ export function createSpecialContextTools(explorer: Explorer, context: 'iframe')
|
|
|
542
513
|
};
|
|
543
514
|
}
|
|
544
515
|
|
|
545
|
-
export function
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
516
|
+
export function createLearnExperienceTool({ getExperienceTracker, getState }: { getExperienceTracker: () => ExperienceTracker; getState: () => ActionResult | null }) {
|
|
517
|
+
return tool({
|
|
518
|
+
description: dedent`
|
|
519
|
+
Read the full body of a specific experience section listed in <experience>.
|
|
520
|
+
The TOC shows entries like "A.1 ## FLOW: ..." or "A.2 ## ACTION: ...". Pass the fileTag and sectionIndex.
|
|
521
|
+
Only call when a TOC entry looks directly relevant to the current step.
|
|
522
|
+
`,
|
|
523
|
+
inputSchema: z.object({
|
|
524
|
+
fileTag: z.string().describe('File tag from the TOC, e.g. "A", "B", "AA"'),
|
|
525
|
+
sectionIndex: z.number().int().positive().describe('1-based section index within that file'),
|
|
526
|
+
}),
|
|
527
|
+
execute: async ({ fileTag, sectionIndex }) => {
|
|
528
|
+
const state = getState();
|
|
529
|
+
if (!state) {
|
|
530
|
+
return { error: 'No current page state available.' };
|
|
531
|
+
}
|
|
532
|
+
const section = getExperienceTracker().getExperienceSection(fileTag, sectionIndex, state);
|
|
533
|
+
if (!section) {
|
|
534
|
+
return { error: 'Section not found. Experience may have been updated; re-read the latest TOC.' };
|
|
535
|
+
}
|
|
536
|
+
return section;
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function createAgentTools({ explorer, stateManager, ai, researcher, navigator, supervisor, withExperience }: AgentToolDeps): any {
|
|
560
542
|
let visionDisabled = false;
|
|
561
543
|
|
|
562
544
|
const tools: Record<string, any> = {
|
|
@@ -581,7 +563,7 @@ export function createAgentTools({
|
|
|
581
563
|
}
|
|
582
564
|
|
|
583
565
|
try {
|
|
584
|
-
const actionResult = await explorer.
|
|
566
|
+
const actionResult = await explorer.capture({ screenshot: true });
|
|
585
567
|
|
|
586
568
|
if (!actionResult.screenshot) {
|
|
587
569
|
return failedToolResult('see', 'Failed to capture screenshot for analysis');
|
|
@@ -600,7 +582,7 @@ export function createAgentTools({
|
|
|
600
582
|
});
|
|
601
583
|
} catch (error) {
|
|
602
584
|
throwIfFatalBrowserError(error);
|
|
603
|
-
const errorMessage = error
|
|
585
|
+
const errorMessage = errorText(error);
|
|
604
586
|
visionDisabled = true;
|
|
605
587
|
tag('warning').log('⚠️ Vision model is not available. Visual checks are disabled for this session.');
|
|
606
588
|
return failedToolResult('see', `See tool failed: ${errorMessage}`, {
|
|
@@ -629,7 +611,6 @@ export function createAgentTools({
|
|
|
629
611
|
}),
|
|
630
612
|
execute: async ({ reason }) => {
|
|
631
613
|
try {
|
|
632
|
-
const stateManager = explorer.getStateManager();
|
|
633
614
|
const currentState = stateManager.getCurrentState();
|
|
634
615
|
|
|
635
616
|
if (!currentState) {
|
|
@@ -649,7 +630,7 @@ export function createAgentTools({
|
|
|
649
630
|
reminder: 'Context provided. Do not call context() again until you perform actions or suspect page changed.',
|
|
650
631
|
});
|
|
651
632
|
} catch (error) {
|
|
652
|
-
const errorMessage = error
|
|
633
|
+
const errorMessage = errorText(error);
|
|
653
634
|
return failedToolResult('context', `Context tool failed: ${errorMessage}`);
|
|
654
635
|
}
|
|
655
636
|
},
|
|
@@ -670,7 +651,7 @@ export function createAgentTools({
|
|
|
670
651
|
}),
|
|
671
652
|
execute: async ({ assertion }) => {
|
|
672
653
|
try {
|
|
673
|
-
const currentState =
|
|
654
|
+
const currentState = stateManager.getCurrentState();
|
|
674
655
|
const verifications = currentState?.verifications;
|
|
675
656
|
|
|
676
657
|
if (verifications?.[assertion] !== undefined) {
|
|
@@ -681,7 +662,7 @@ export function createAgentTools({
|
|
|
681
662
|
});
|
|
682
663
|
}
|
|
683
664
|
|
|
684
|
-
const actionResult = await explorer.
|
|
665
|
+
const actionResult = await explorer.capture();
|
|
685
666
|
const result = await navigator.verifyState(assertion, actionResult);
|
|
686
667
|
|
|
687
668
|
if (result.verified) {
|
|
@@ -700,7 +681,7 @@ export function createAgentTools({
|
|
|
700
681
|
});
|
|
701
682
|
} catch (error) {
|
|
702
683
|
throwIfFatalBrowserError(error);
|
|
703
|
-
const errorMessage = error
|
|
684
|
+
const errorMessage = errorText(error);
|
|
704
685
|
return failedToolResult('verify', `Verify tool failed: ${errorMessage}`, {
|
|
705
686
|
error: errorMessage,
|
|
706
687
|
});
|
|
@@ -731,7 +712,6 @@ export function createAgentTools({
|
|
|
731
712
|
}),
|
|
732
713
|
execute: async ({ reason }) => {
|
|
733
714
|
try {
|
|
734
|
-
const stateManager = explorer.getStateManager();
|
|
735
715
|
const currentState = stateManager.getCurrentState();
|
|
736
716
|
|
|
737
717
|
if (!currentState) {
|
|
@@ -756,7 +736,7 @@ export function createAgentTools({
|
|
|
756
736
|
});
|
|
757
737
|
} catch (error) {
|
|
758
738
|
throwIfFatalBrowserError(error);
|
|
759
|
-
const errorMessage = error
|
|
739
|
+
const errorMessage = errorText(error);
|
|
760
740
|
return failedToolResult('research', `Research tool failed: ${errorMessage}`, {
|
|
761
741
|
error: errorMessage,
|
|
762
742
|
});
|
|
@@ -775,7 +755,6 @@ export function createAgentTools({
|
|
|
775
755
|
}),
|
|
776
756
|
execute: async ({ instruction }) => {
|
|
777
757
|
try {
|
|
778
|
-
const stateManager = explorer.getStateManager();
|
|
779
758
|
const currentState = stateManager.getCurrentState();
|
|
780
759
|
|
|
781
760
|
if (!currentState) {
|
|
@@ -801,7 +780,7 @@ export function createAgentTools({
|
|
|
801
780
|
});
|
|
802
781
|
} catch (error) {
|
|
803
782
|
throwIfFatalBrowserError(error);
|
|
804
|
-
const errorMessage = error
|
|
783
|
+
const errorMessage = errorText(error);
|
|
805
784
|
return failedToolResult('interact', `Interact tool failed: ${errorMessage}`, {
|
|
806
785
|
error: errorMessage,
|
|
807
786
|
});
|
|
@@ -830,7 +809,6 @@ export function createAgentTools({
|
|
|
830
809
|
}
|
|
831
810
|
|
|
832
811
|
try {
|
|
833
|
-
const stateManager = explorer.getStateManager();
|
|
834
812
|
const currentState = stateManager.getCurrentState();
|
|
835
813
|
|
|
836
814
|
if (!currentState) {
|
|
@@ -838,8 +816,8 @@ export function createAgentTools({
|
|
|
838
816
|
}
|
|
839
817
|
|
|
840
818
|
const previousState = ActionResult.fromState(currentState);
|
|
841
|
-
const action = explorer.
|
|
842
|
-
const actionResult = await explorer.
|
|
819
|
+
const action = explorer.action();
|
|
820
|
+
const actionResult = await explorer.capture({ screenshot: true });
|
|
843
821
|
|
|
844
822
|
if (!actionResult.screenshot) {
|
|
845
823
|
return failedToolResult('visualClick', 'Failed to capture screenshot for visual analysis');
|
|
@@ -881,7 +859,7 @@ export function createAgentTools({
|
|
|
881
859
|
});
|
|
882
860
|
} catch (error) {
|
|
883
861
|
throwIfFatalBrowserError(error);
|
|
884
|
-
const errorMessage = error
|
|
862
|
+
const errorMessage = errorText(error);
|
|
885
863
|
visionDisabled = true;
|
|
886
864
|
tag('warning').log('⚠️ Vision model is not available. Visual clicks are disabled for this session.');
|
|
887
865
|
return failedToolResult('visualClick', `visualClick tool failed: ${errorMessage}`, {
|
|
@@ -901,7 +879,6 @@ export function createAgentTools({
|
|
|
901
879
|
reason: z.string().describe('Why you need to go back'),
|
|
902
880
|
}),
|
|
903
881
|
execute: async ({ reason }) => {
|
|
904
|
-
const stateManager = explorer.getStateManager();
|
|
905
882
|
const currentState = stateManager.getCurrentState();
|
|
906
883
|
const currentUrl = currentState?.fullUrl || currentState?.url;
|
|
907
884
|
const history = stateManager.getStateHistory();
|
|
@@ -921,22 +898,24 @@ export function createAgentTools({
|
|
|
921
898
|
});
|
|
922
899
|
}
|
|
923
900
|
|
|
924
|
-
|
|
901
|
+
let previousState: ActionResult | null = null;
|
|
902
|
+
if (currentState) previousState = ActionResult.fromState(currentState);
|
|
903
|
+
|
|
904
|
+
const action = explorer.action();
|
|
925
905
|
const success = await action.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, `${reason} (BACK to ${targetUrl})`);
|
|
926
906
|
|
|
927
907
|
if (success) {
|
|
928
|
-
const
|
|
929
|
-
const toolResult = await
|
|
908
|
+
const newState = ActionResult.fromState(stateManager.getCurrentState()!);
|
|
909
|
+
const toolResult = await newState.toToolResult(previousState, `I.amOnPage("${targetUrl}")`);
|
|
930
910
|
return successToolResult('back', {
|
|
931
911
|
...toolResult,
|
|
932
912
|
message: `Navigated back to ${targetUrl}`,
|
|
933
913
|
});
|
|
934
914
|
}
|
|
935
915
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
});
|
|
916
|
+
const failData: Record<string, any> = { suggestion: 'Try reset() to return to the starting page.' };
|
|
917
|
+
if (action.lastError) failData.error = action.lastError.toString();
|
|
918
|
+
return failedToolResult('back', `Failed to navigate back to ${targetUrl}`, failData);
|
|
940
919
|
},
|
|
941
920
|
}),
|
|
942
921
|
|
|
@@ -944,7 +923,7 @@ export function createAgentTools({
|
|
|
944
923
|
description: 'List all previously visited page states (deduped by URL). Use to find pages to navigate back to.',
|
|
945
924
|
inputSchema: z.object({}),
|
|
946
925
|
execute: async () => {
|
|
947
|
-
const history =
|
|
926
|
+
const history = stateManager.getStateHistory();
|
|
948
927
|
const seen = new Set<string>();
|
|
949
928
|
const states = history
|
|
950
929
|
.map((t) => t.toState)
|
|
@@ -954,7 +933,7 @@ export function createAgentTools({
|
|
|
954
933
|
return true;
|
|
955
934
|
})
|
|
956
935
|
.map((s, i) => ({ index: i, url: s.url, title: s.title, h1: s.h1 }));
|
|
957
|
-
return {
|
|
936
|
+
return successToolResult('getVisitedStates', { states });
|
|
958
937
|
},
|
|
959
938
|
}),
|
|
960
939
|
|
|
@@ -977,7 +956,6 @@ export function createAgentTools({
|
|
|
977
956
|
reason: z.string().describe('What element you are looking for and why'),
|
|
978
957
|
}),
|
|
979
958
|
execute: async ({ xpath, reason }) => {
|
|
980
|
-
const stateManager = explorer.getStateManager();
|
|
981
959
|
const currentState = stateManager.getCurrentState();
|
|
982
960
|
|
|
983
961
|
if (!currentState) {
|
|
@@ -1003,44 +981,42 @@ export function createAgentTools({
|
|
|
1003
981
|
});
|
|
1004
982
|
}
|
|
1005
983
|
|
|
1006
|
-
const action = explorer.
|
|
1007
|
-
const visible = await action.attempt(`I.seeElement(${JSON.stringify(xpath)})`, 'xpathCheck visibility'
|
|
984
|
+
const action = explorer.action();
|
|
985
|
+
const visible = await action.attempt(`I.seeElement(${JSON.stringify(xpath)})`, 'xpathCheck visibility');
|
|
986
|
+
const liveElement = await explorer.withPage((page) => WebElement.fromPlaywrightLocator(page.locator(`xpath=${xpath}`)));
|
|
1008
987
|
|
|
1009
988
|
const matchesSummary = result.elements.map((el, i) => `${i + 1}. <${el.tag} ${el.keyAttrs}> text="${el.text}" html: ${el.outerHTML}`).join('\n');
|
|
1010
989
|
|
|
1011
990
|
const visibilityNote = visible ? 'Element IS visible in browser — Tester can use this XPath as locator.' : 'Element exists in DOM but is NOT visible. May need scrolling, a click to reveal, or is hidden.';
|
|
1012
991
|
|
|
992
|
+
const hit = liveElement?.ourAttr('hit') || null;
|
|
993
|
+
const coveredBy = liveElement?.ourAttr('coveredBy') || null;
|
|
994
|
+
let resolvedVisibilityNote = visibilityNote;
|
|
995
|
+
if (hit === 'covered') {
|
|
996
|
+
resolvedVisibilityNote = `Element exists in DOM and is visible, but is covered by ${coveredBy || 'another UI layer'}. Dismiss or move the covering UI before interacting with it.`;
|
|
997
|
+
}
|
|
998
|
+
if (hit === 'offscreen') {
|
|
999
|
+
resolvedVisibilityNote = 'Element exists in DOM but its interaction point is outside the viewport. Scroll or reveal it before interacting.';
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1013
1002
|
return successToolResult('xpathCheck', {
|
|
1014
1003
|
totalFound: result.totalFound,
|
|
1015
1004
|
matches: matchesSummary,
|
|
1016
|
-
visibilityNote,
|
|
1005
|
+
visibilityNote: resolvedVisibilityNote,
|
|
1006
|
+
hit,
|
|
1007
|
+
coveredBy,
|
|
1017
1008
|
xpath,
|
|
1018
1009
|
});
|
|
1019
1010
|
},
|
|
1020
1011
|
}),
|
|
1021
1012
|
};
|
|
1022
1013
|
|
|
1023
|
-
if (
|
|
1024
|
-
tools.learnExperience =
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
`,
|
|
1030
|
-
inputSchema: z.object({
|
|
1031
|
-
fileTag: z.string().describe('File tag from the TOC, e.g. "A", "B", "AA"'),
|
|
1032
|
-
sectionIndex: z.number().int().positive().describe('1-based section index within that file'),
|
|
1033
|
-
}),
|
|
1034
|
-
execute: async ({ fileTag, sectionIndex }) => {
|
|
1035
|
-
const state = getState();
|
|
1036
|
-
if (!state) {
|
|
1037
|
-
return { error: 'No current page state available.' };
|
|
1038
|
-
}
|
|
1039
|
-
const section = experienceTracker.getExperienceSection(fileTag, sectionIndex, state);
|
|
1040
|
-
if (!section) {
|
|
1041
|
-
return { error: 'Section not found. Experience may have been updated; re-read the latest TOC.' };
|
|
1042
|
-
}
|
|
1043
|
-
return section;
|
|
1014
|
+
if (withExperience !== false) {
|
|
1015
|
+
tools.learnExperience = createLearnExperienceTool({
|
|
1016
|
+
getExperienceTracker: () => stateManager.getExperienceTracker(),
|
|
1017
|
+
getState: () => {
|
|
1018
|
+
const currentState = stateManager.getCurrentState();
|
|
1019
|
+
return currentState ? ActionResult.fromState(currentState) : null;
|
|
1044
1020
|
},
|
|
1045
1021
|
});
|
|
1046
1022
|
}
|
|
@@ -1131,11 +1107,16 @@ function transformContainsCommand(command: string): string {
|
|
|
1131
1107
|
return command;
|
|
1132
1108
|
}
|
|
1133
1109
|
|
|
1134
|
-
function
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1110
|
+
function errorText(error: unknown): string {
|
|
1111
|
+
if (error instanceof Error) return error.toString();
|
|
1112
|
+
return 'Unknown error occurred';
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
async function commitNote(activeNote: any, result: TestResult, toolResult: any, action: any): Promise<void> {
|
|
1116
|
+
if (toolResult?.pageDiff?.ariaChanges || toolResult?.pageDiff?.urlChanged) {
|
|
1117
|
+
activeNote.screenshot = await action.saveScreenshot();
|
|
1118
|
+
}
|
|
1119
|
+
activeNote.commit(result);
|
|
1139
1120
|
}
|
|
1140
1121
|
|
|
1141
1122
|
function successToolResult(action: string, data?: Record<string, any>, source?: { playwrightGroupId?: string | null; assertionSteps?: any[] }) {
|
|
@@ -1151,7 +1132,7 @@ function successToolResult(action: string, data?: Record<string, any>, source?:
|
|
|
1151
1132
|
const ariaChanges = data.pageDiff.ariaChanges || '';
|
|
1152
1133
|
const urlChanged = data.pageDiff.urlChanged === true;
|
|
1153
1134
|
const hasHtmlParts = Array.isArray(data.pageDiff.htmlParts) && data.pageDiff.htmlParts.length > 0;
|
|
1154
|
-
if (
|
|
1135
|
+
if (isMajorPageChange(data.pageDiff)) {
|
|
1155
1136
|
suggestion = `MAJOR PAGE CHANGE. Page entered a different mode. Check htmlParts and iframes in pageDiff before next action. ${suggestion}`;
|
|
1156
1137
|
} else if (!urlChanged && !ariaChanges && !hasHtmlParts) {
|
|
1157
1138
|
suggestion = 'Action ran without error but produced no observable change (URL, ARIA and HTML all unchanged). The locator likely matched a non-interactive ancestor or an element outside the intended control. Re-locate via xpathCheck() or verify with see() before treating this as success.';
|
|
@@ -1163,6 +1144,17 @@ function successToolResult(action: string, data?: Record<string, any>, source?:
|
|
|
1163
1144
|
return result;
|
|
1164
1145
|
}
|
|
1165
1146
|
|
|
1147
|
+
export function isMajorPageChange(pageDiff: PageDiff): boolean {
|
|
1148
|
+
return pageDiff.urlChanged !== true && (pageDiff.ariaChangeCount ?? 0) >= LARGE_ARIA_CHANGE_THRESHOLD;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function hasObservablePageChange(data?: Record<string, any>): boolean {
|
|
1152
|
+
if (!data?.pageDiff) return false;
|
|
1153
|
+
if (data.pageDiff.urlChanged === true) return true;
|
|
1154
|
+
if (data.pageDiff.ariaChanges) return true;
|
|
1155
|
+
return Array.isArray(data.pageDiff.htmlParts) && data.pageDiff.htmlParts.length > 0;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1166
1158
|
async function failedToolResult(action: string, message: string, data?: Record<string, any>, error?: Error | null) {
|
|
1167
1159
|
const result: Record<string, any> = { success: false, action, message, ...data };
|
|
1168
1160
|
if (data?.pageDiff) {
|
|
@@ -1257,7 +1249,7 @@ async function disambiguateElements(error: Error | null | undefined, explanation
|
|
|
1257
1249
|
},
|
|
1258
1250
|
],
|
|
1259
1251
|
schema,
|
|
1260
|
-
provider.
|
|
1252
|
+
provider.getModelForAgent(),
|
|
1261
1253
|
{ agentName: 'disambiguator', timeout: 15000 }
|
|
1262
1254
|
);
|
|
1263
1255
|
|
|
@@ -1273,7 +1265,7 @@ async function disambiguateElements(error: Error | null | undefined, explanation
|
|
|
1273
1265
|
}
|
|
1274
1266
|
|
|
1275
1267
|
function getNotFoundSuggestion(errorMessage: string): string | null {
|
|
1276
|
-
if (!errorMessage.includes('
|
|
1268
|
+
if (!errorMessage.includes('not found')) {
|
|
1277
1269
|
return null;
|
|
1278
1270
|
}
|
|
1279
1271
|
|