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/dist/src/ai/tools.js
CHANGED
|
@@ -3,7 +3,7 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from "../action-result.js";
|
|
5
5
|
import { TestResult } from '../test-plan.js';
|
|
6
|
-
import { extractFocusedElement } from "../utils/aria.js";
|
|
6
|
+
import { LARGE_ARIA_CHANGE_THRESHOLD, extractFocusedElement } from "../utils/aria.js";
|
|
7
7
|
import { isFatalBrowserError } from "../utils/browser-errors.js";
|
|
8
8
|
import { createDebug, tag } from '../utils/logger.js';
|
|
9
9
|
import { pause } from '../utils/loop.js';
|
|
@@ -12,8 +12,7 @@ import { sectionContextRule } from "./rules.js";
|
|
|
12
12
|
import { isInteractive } from "./task-agent.js";
|
|
13
13
|
const debugLog = createDebug('explorbot:tools');
|
|
14
14
|
export const ASSERTION_TOOLS = ['verify'];
|
|
15
|
-
export function createCodeceptJSTools(explorer, task) {
|
|
16
|
-
const stateManager = explorer.getStateManager();
|
|
15
|
+
export function createCodeceptJSTools({ explorer, stateManager, ai }, task) {
|
|
17
16
|
return {
|
|
18
17
|
click: tool({
|
|
19
18
|
description: dedent `
|
|
@@ -31,6 +30,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
31
30
|
CRITICAL: All commands MUST target the SAME element using different locators.
|
|
32
31
|
This is a FALLBACK list, NOT a sequence of different clicks.
|
|
33
32
|
If you need to click multiple different elements, make SEPARATE click() calls.
|
|
33
|
+
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.
|
|
34
34
|
`,
|
|
35
35
|
inputSchema: z.object({
|
|
36
36
|
commands: z.array(z.string()).describe(dedent `
|
|
@@ -67,29 +67,24 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
67
67
|
return `I.click(${JSON.stringify(trimmed)})`;
|
|
68
68
|
});
|
|
69
69
|
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
70
|
-
const action = explorer.
|
|
70
|
+
const action = explorer.action();
|
|
71
71
|
const attempts = [];
|
|
72
72
|
for (let i = 0; i < commands.length; i++) {
|
|
73
73
|
const command = transformContainsCommand(commands[i]);
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
...(action.lastError && { error: action.lastError.toString() }),
|
|
80
|
-
});
|
|
74
|
+
const success = await action.attempt(command, explanation);
|
|
75
|
+
const attempt = { command, success };
|
|
76
|
+
if (action.lastError)
|
|
77
|
+
attempt.error = action.lastError.toString();
|
|
78
|
+
attempts.push(attempt);
|
|
81
79
|
if (success) {
|
|
82
80
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, command);
|
|
83
|
-
|
|
84
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
85
|
-
}
|
|
86
|
-
activeNote.commit(TestResult.PASSED);
|
|
81
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
87
82
|
return successToolResult('click', { ...toolResult, attempts, code: command }, action);
|
|
88
83
|
}
|
|
89
84
|
}
|
|
90
85
|
let disambiguated = null;
|
|
91
86
|
if (attempts.some((a) => a.error?.toLowerCase().includes(MULTIPLE_ELEMENTS_PATTERN))) {
|
|
92
|
-
disambiguated = await disambiguateElements(action.lastError, explanation,
|
|
87
|
+
disambiguated = await disambiguateElements(action.lastError, explanation, ai);
|
|
93
88
|
}
|
|
94
89
|
if (disambiguated) {
|
|
95
90
|
debugLog('Disambiguation picked element %d', disambiguated.position);
|
|
@@ -100,23 +95,17 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
100
95
|
}
|
|
101
96
|
retryCommands.push(`I.click('${disambiguated.xpath.replace(/'/g, "\\'")}')`);
|
|
102
97
|
for (const retryCmd of retryCommands) {
|
|
103
|
-
if (!(await action.attempt(retryCmd, explanation
|
|
98
|
+
if (!(await action.attempt(retryCmd, explanation))) {
|
|
104
99
|
attempts.push({ command: retryCmd, success: false, error: action.lastError?.toString() });
|
|
105
100
|
continue;
|
|
106
101
|
}
|
|
107
102
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, retryCmd);
|
|
108
|
-
|
|
109
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
110
|
-
}
|
|
111
|
-
activeNote.commit(TestResult.PASSED);
|
|
103
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
112
104
|
return successToolResult('click', { ...toolResult, attempts, code: retryCmd, disambiguated: true }, action);
|
|
113
105
|
}
|
|
114
106
|
}
|
|
115
107
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, commands[0]);
|
|
116
|
-
|
|
117
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
118
|
-
}
|
|
119
|
-
activeNote.commit(TestResult.FAILED);
|
|
108
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
120
109
|
let suggestion = "Try xpathCheck() to find the element's actual position, see() for visual analysis, or visualClick() to click by visual appearance.";
|
|
121
110
|
const lastError = attempts[attempts.length - 1]?.error || '';
|
|
122
111
|
if (lastError.includes('was not found') || lastError.includes('not found by text')) {
|
|
@@ -173,15 +162,14 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
173
162
|
return `I.moveCursorTo(${JSON.stringify(trimmed)})`;
|
|
174
163
|
});
|
|
175
164
|
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
176
|
-
const action = explorer.
|
|
165
|
+
const action = explorer.action();
|
|
177
166
|
const attempts = [];
|
|
178
167
|
for (const command of commands) {
|
|
179
|
-
const success = await action.attempt(command, explanation
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
168
|
+
const success = await action.attempt(command, explanation);
|
|
169
|
+
const attempt = { command, success };
|
|
170
|
+
if (action.lastError)
|
|
171
|
+
attempt.error = action.lastError.toString();
|
|
172
|
+
attempts.push(attempt);
|
|
185
173
|
if (!success)
|
|
186
174
|
continue;
|
|
187
175
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, command);
|
|
@@ -234,15 +222,12 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
234
222
|
const keyToUse = matchingStandardKey || key;
|
|
235
223
|
if (!isSingleChar && !isStandardKey) {
|
|
236
224
|
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
237
|
-
const action = explorer.
|
|
225
|
+
const action = explorer.action();
|
|
238
226
|
const typeCommand = `I.type(${JSON.stringify(key)})`;
|
|
239
227
|
await action.attempt(typeCommand, explanation);
|
|
240
228
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, key);
|
|
241
229
|
if (!action.lastError) {
|
|
242
|
-
|
|
243
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
244
|
-
}
|
|
245
|
-
activeNote.commit(TestResult.PASSED);
|
|
230
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
246
231
|
return successToolResult('pressKey', {
|
|
247
232
|
...toolResult,
|
|
248
233
|
message: `Automatically used type() for "${key}" (not a standard key press)`,
|
|
@@ -251,10 +236,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
251
236
|
}, action);
|
|
252
237
|
}
|
|
253
238
|
const errorMsg = `pressKey fallback to type() failed: ${action.lastError?.toString()}`;
|
|
254
|
-
|
|
255
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
256
|
-
}
|
|
257
|
-
activeNote.commit(TestResult.FAILED);
|
|
239
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
258
240
|
return failedToolResult('pressKey', errorMsg, {
|
|
259
241
|
...toolResult,
|
|
260
242
|
code: typeCommand,
|
|
@@ -274,7 +256,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
274
256
|
}
|
|
275
257
|
}
|
|
276
258
|
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
277
|
-
const action = explorer.
|
|
259
|
+
const action = explorer.action();
|
|
278
260
|
let pressKeyCommand;
|
|
279
261
|
if (modifier) {
|
|
280
262
|
const modifiers = Array.isArray(modifier) ? modifier : [modifier];
|
|
@@ -286,10 +268,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
286
268
|
await action.attempt(pressKeyCommand, explanation);
|
|
287
269
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, key);
|
|
288
270
|
if (!action.lastError) {
|
|
289
|
-
|
|
290
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
291
|
-
}
|
|
292
|
-
activeNote.commit(TestResult.PASSED);
|
|
271
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
293
272
|
return successToolResult('pressKey', {
|
|
294
273
|
...toolResult,
|
|
295
274
|
message: `Pressed key: ${key}${modifier ? ` with modifier(s): ${Array.isArray(modifier) ? modifier.join('+') : modifier}` : ''}`,
|
|
@@ -297,10 +276,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
297
276
|
}, action);
|
|
298
277
|
}
|
|
299
278
|
const errorMsg = `pressKey() failed: ${action.lastError?.toString()}`;
|
|
300
|
-
|
|
301
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
302
|
-
}
|
|
303
|
-
activeNote.commit(TestResult.FAILED);
|
|
279
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
304
280
|
return failedToolResult('pressKey', errorMsg, {
|
|
305
281
|
...toolResult,
|
|
306
282
|
code: pressKeyCommand,
|
|
@@ -310,7 +286,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
310
286
|
catch (error) {
|
|
311
287
|
throwIfFatalBrowserError(error);
|
|
312
288
|
activeNote.commit(TestResult.FAILED);
|
|
313
|
-
const errorMessage = error
|
|
289
|
+
const errorMessage = errorText(error);
|
|
314
290
|
return failedToolResult('pressKey', `PressKey tool failed: ${errorMessage}`);
|
|
315
291
|
}
|
|
316
292
|
},
|
|
@@ -370,22 +346,15 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
370
346
|
}
|
|
371
347
|
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
372
348
|
const formLocator = codeLines[0] || 'form';
|
|
373
|
-
const action = explorer.
|
|
374
|
-
const wasInIframe = await explorer.isInsideIframe();
|
|
349
|
+
const action = explorer.action();
|
|
375
350
|
await action.attempt(codeBlock, explanation);
|
|
376
|
-
if (action.lastError && !wasInIframe && (await explorer.isInsideIframe())) {
|
|
377
|
-
await explorer.switchToMainFrame();
|
|
378
|
-
}
|
|
379
351
|
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, formLocator);
|
|
380
352
|
if (action.lastError) {
|
|
381
353
|
const message = action.lastError ? String(action.lastError) : 'Unknown error';
|
|
382
|
-
|
|
383
|
-
activeNote.screenshot = await action.saveScreenshot();
|
|
384
|
-
}
|
|
385
|
-
activeNote.commit(TestResult.FAILED);
|
|
354
|
+
await commitNote(activeNote, TestResult.FAILED, toolResult, action);
|
|
386
355
|
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.';
|
|
387
356
|
if (message.toLowerCase().includes(MULTIPLE_ELEMENTS_PATTERN)) {
|
|
388
|
-
const disambiguated = await disambiguateElements(action.lastError, explanation,
|
|
357
|
+
const disambiguated = await disambiguateElements(action.lastError, explanation, ai);
|
|
389
358
|
if (disambiguated) {
|
|
390
359
|
formSuggestion = `Multiple elements matched. Add step.opts({ elementIndex: ${disambiguated.position} }) to the failing command. Fallback locator: ${disambiguated.xpath}`;
|
|
391
360
|
}
|
|
@@ -396,10 +365,15 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
396
365
|
suggestion: formSuggestion,
|
|
397
366
|
}, action.lastError);
|
|
398
367
|
}
|
|
399
|
-
if (toolResult
|
|
400
|
-
activeNote.
|
|
368
|
+
if (!hasObservablePageChange(toolResult)) {
|
|
369
|
+
activeNote.commit(TestResult.FAILED);
|
|
370
|
+
return failedToolResult('form', 'Form command executed, but no observable page or form-state change was captured.', {
|
|
371
|
+
...toolResult,
|
|
372
|
+
code: codeBlock,
|
|
373
|
+
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.',
|
|
374
|
+
});
|
|
401
375
|
}
|
|
402
|
-
activeNote
|
|
376
|
+
await commitNote(activeNote, TestResult.PASSED, toolResult, action);
|
|
403
377
|
return successToolResult('form', {
|
|
404
378
|
...toolResult,
|
|
405
379
|
message: `Form completed successfully with ${lines.length} commands.`,
|
|
@@ -411,18 +385,14 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
411
385
|
catch (error) {
|
|
412
386
|
throwIfFatalBrowserError(error);
|
|
413
387
|
activeNote.commit(TestResult.FAILED);
|
|
414
|
-
const errorMessage = error
|
|
388
|
+
const errorMessage = errorText(error);
|
|
415
389
|
return failedToolResult('form', `Form tool failed: ${errorMessage}`);
|
|
416
390
|
}
|
|
417
391
|
},
|
|
418
392
|
}),
|
|
419
393
|
};
|
|
420
394
|
}
|
|
421
|
-
export function
|
|
422
|
-
const stateManager = explorer.getStateManager();
|
|
423
|
-
if (context !== 'iframe') {
|
|
424
|
-
return {};
|
|
425
|
-
}
|
|
395
|
+
export function createIframeTools({ explorer, stateManager }) {
|
|
426
396
|
return {
|
|
427
397
|
exitIframe: tool({
|
|
428
398
|
description: dedent `
|
|
@@ -442,8 +412,9 @@ export function createSpecialContextTools(explorer, context) {
|
|
|
442
412
|
suggestion: 'Continue interacting with the current page context.',
|
|
443
413
|
});
|
|
444
414
|
}
|
|
445
|
-
|
|
446
|
-
|
|
415
|
+
const action = explorer.action();
|
|
416
|
+
await action.execute('I.switchTo()');
|
|
417
|
+
const nextState = action.getActionResult();
|
|
447
418
|
const toolResult = await nextState.toToolResult(previousState, 'I.switchTo()');
|
|
448
419
|
return successToolResult('exitIframe', {
|
|
449
420
|
...toolResult,
|
|
@@ -453,14 +424,38 @@ export function createSpecialContextTools(explorer, context) {
|
|
|
453
424
|
}
|
|
454
425
|
catch (error) {
|
|
455
426
|
throwIfFatalBrowserError(error);
|
|
456
|
-
const errorMessage = error
|
|
427
|
+
const errorMessage = errorText(error);
|
|
457
428
|
return failedToolResult('exitIframe', `Failed to exit iframe: ${errorMessage}`);
|
|
458
429
|
}
|
|
459
430
|
},
|
|
460
431
|
}),
|
|
461
432
|
};
|
|
462
433
|
}
|
|
463
|
-
export function
|
|
434
|
+
export function createLearnExperienceTool({ getExperienceTracker, getState }) {
|
|
435
|
+
return tool({
|
|
436
|
+
description: dedent `
|
|
437
|
+
Read the full body of a specific experience section listed in <experience>.
|
|
438
|
+
The TOC shows entries like "A.1 ## FLOW: ..." or "A.2 ## ACTION: ...". Pass the fileTag and sectionIndex.
|
|
439
|
+
Only call when a TOC entry looks directly relevant to the current step.
|
|
440
|
+
`,
|
|
441
|
+
inputSchema: z.object({
|
|
442
|
+
fileTag: z.string().describe('File tag from the TOC, e.g. "A", "B", "AA"'),
|
|
443
|
+
sectionIndex: z.number().int().positive().describe('1-based section index within that file'),
|
|
444
|
+
}),
|
|
445
|
+
execute: async ({ fileTag, sectionIndex }) => {
|
|
446
|
+
const state = getState();
|
|
447
|
+
if (!state) {
|
|
448
|
+
return { error: 'No current page state available.' };
|
|
449
|
+
}
|
|
450
|
+
const section = getExperienceTracker().getExperienceSection(fileTag, sectionIndex, state);
|
|
451
|
+
if (!section) {
|
|
452
|
+
return { error: 'Section not found. Experience may have been updated; re-read the latest TOC.' };
|
|
453
|
+
}
|
|
454
|
+
return section;
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
export function createAgentTools({ explorer, stateManager, ai, researcher, navigator, supervisor, withExperience }) {
|
|
464
459
|
let visionDisabled = false;
|
|
465
460
|
const tools = {
|
|
466
461
|
see: tool({
|
|
@@ -483,7 +478,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
483
478
|
return failedToolResult('see', 'Vision tools are disabled for this session. Use context() to get fresh ARIA snapshot and analyze page state from ARIA data.');
|
|
484
479
|
}
|
|
485
480
|
try {
|
|
486
|
-
const actionResult = await explorer.
|
|
481
|
+
const actionResult = await explorer.capture({ screenshot: true });
|
|
487
482
|
if (!actionResult.screenshot) {
|
|
488
483
|
return failedToolResult('see', 'Failed to capture screenshot for analysis');
|
|
489
484
|
}
|
|
@@ -499,7 +494,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
499
494
|
}
|
|
500
495
|
catch (error) {
|
|
501
496
|
throwIfFatalBrowserError(error);
|
|
502
|
-
const errorMessage = error
|
|
497
|
+
const errorMessage = errorText(error);
|
|
503
498
|
visionDisabled = true;
|
|
504
499
|
tag('warning').log('⚠️ Vision model is not available. Visual checks are disabled for this session.');
|
|
505
500
|
return failedToolResult('see', `See tool failed: ${errorMessage}`, {
|
|
@@ -527,7 +522,6 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
527
522
|
}),
|
|
528
523
|
execute: async ({ reason }) => {
|
|
529
524
|
try {
|
|
530
|
-
const stateManager = explorer.getStateManager();
|
|
531
525
|
const currentState = stateManager.getCurrentState();
|
|
532
526
|
if (!currentState) {
|
|
533
527
|
return failedToolResult('context', 'No current page state available.');
|
|
@@ -545,7 +539,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
545
539
|
});
|
|
546
540
|
}
|
|
547
541
|
catch (error) {
|
|
548
|
-
const errorMessage = error
|
|
542
|
+
const errorMessage = errorText(error);
|
|
549
543
|
return failedToolResult('context', `Context tool failed: ${errorMessage}`);
|
|
550
544
|
}
|
|
551
545
|
},
|
|
@@ -565,7 +559,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
565
559
|
}),
|
|
566
560
|
execute: async ({ assertion }) => {
|
|
567
561
|
try {
|
|
568
|
-
const currentState =
|
|
562
|
+
const currentState = stateManager.getCurrentState();
|
|
569
563
|
const verifications = currentState?.verifications;
|
|
570
564
|
if (verifications?.[assertion] !== undefined) {
|
|
571
565
|
return failedToolResult('verify', `Already verified: "${assertion}" → ${verifications[assertion] ? 'PASS' : 'FAIL'}`, {
|
|
@@ -574,7 +568,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
574
568
|
suggestion: verifications[assertion] ? 'This verification already passed. Call finish() to complete the test.' : 'This verification already failed. Perform actions to change the page state, then try again.',
|
|
575
569
|
});
|
|
576
570
|
}
|
|
577
|
-
const actionResult = await explorer.
|
|
571
|
+
const actionResult = await explorer.capture();
|
|
578
572
|
const result = await navigator.verifyState(assertion, actionResult);
|
|
579
573
|
if (result.verified) {
|
|
580
574
|
return successToolResult('verify', {
|
|
@@ -588,7 +582,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
588
582
|
}
|
|
589
583
|
catch (error) {
|
|
590
584
|
throwIfFatalBrowserError(error);
|
|
591
|
-
const errorMessage = error
|
|
585
|
+
const errorMessage = errorText(error);
|
|
592
586
|
return failedToolResult('verify', `Verify tool failed: ${errorMessage}`, {
|
|
593
587
|
error: errorMessage,
|
|
594
588
|
});
|
|
@@ -618,7 +612,6 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
618
612
|
}),
|
|
619
613
|
execute: async ({ reason }) => {
|
|
620
614
|
try {
|
|
621
|
-
const stateManager = explorer.getStateManager();
|
|
622
615
|
const currentState = stateManager.getCurrentState();
|
|
623
616
|
if (!currentState) {
|
|
624
617
|
return failedToolResult('research', 'No current page state available. Navigate to a page first.');
|
|
@@ -641,7 +634,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
641
634
|
}
|
|
642
635
|
catch (error) {
|
|
643
636
|
throwIfFatalBrowserError(error);
|
|
644
|
-
const errorMessage = error
|
|
637
|
+
const errorMessage = errorText(error);
|
|
645
638
|
return failedToolResult('research', `Research tool failed: ${errorMessage}`, {
|
|
646
639
|
error: errorMessage,
|
|
647
640
|
});
|
|
@@ -659,7 +652,6 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
659
652
|
}),
|
|
660
653
|
execute: async ({ instruction }) => {
|
|
661
654
|
try {
|
|
662
|
-
const stateManager = explorer.getStateManager();
|
|
663
655
|
const currentState = stateManager.getCurrentState();
|
|
664
656
|
if (!currentState) {
|
|
665
657
|
return failedToolResult('interact', 'No current page state available. Navigate to a page first.');
|
|
@@ -681,7 +673,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
681
673
|
}
|
|
682
674
|
catch (error) {
|
|
683
675
|
throwIfFatalBrowserError(error);
|
|
684
|
-
const errorMessage = error
|
|
676
|
+
const errorMessage = errorText(error);
|
|
685
677
|
return failedToolResult('interact', `Interact tool failed: ${errorMessage}`, {
|
|
686
678
|
error: errorMessage,
|
|
687
679
|
});
|
|
@@ -708,14 +700,13 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
708
700
|
return failedToolResult('visualClick', 'Vision tools are disabled for this session. Use xpathCheck() to find the element, then click() with the discovered locator.');
|
|
709
701
|
}
|
|
710
702
|
try {
|
|
711
|
-
const stateManager = explorer.getStateManager();
|
|
712
703
|
const currentState = stateManager.getCurrentState();
|
|
713
704
|
if (!currentState) {
|
|
714
705
|
return failedToolResult('visualClick', 'No current page state available.');
|
|
715
706
|
}
|
|
716
707
|
const previousState = ActionResult.fromState(currentState);
|
|
717
|
-
const action = explorer.
|
|
718
|
-
const actionResult = await explorer.
|
|
708
|
+
const action = explorer.action();
|
|
709
|
+
const actionResult = await explorer.capture({ screenshot: true });
|
|
719
710
|
if (!actionResult.screenshot) {
|
|
720
711
|
return failedToolResult('visualClick', 'Failed to capture screenshot for visual analysis');
|
|
721
712
|
}
|
|
@@ -749,7 +740,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
749
740
|
}
|
|
750
741
|
catch (error) {
|
|
751
742
|
throwIfFatalBrowserError(error);
|
|
752
|
-
const errorMessage = error
|
|
743
|
+
const errorMessage = errorText(error);
|
|
753
744
|
visionDisabled = true;
|
|
754
745
|
tag('warning').log('⚠️ Vision model is not available. Visual clicks are disabled for this session.');
|
|
755
746
|
return failedToolResult('visualClick', `visualClick tool failed: ${errorMessage}`, {
|
|
@@ -768,7 +759,6 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
768
759
|
reason: z.string().describe('Why you need to go back'),
|
|
769
760
|
}),
|
|
770
761
|
execute: async ({ reason }) => {
|
|
771
|
-
const stateManager = explorer.getStateManager();
|
|
772
762
|
const currentState = stateManager.getCurrentState();
|
|
773
763
|
const currentUrl = currentState?.fullUrl || currentState?.url;
|
|
774
764
|
const history = stateManager.getStateHistory();
|
|
@@ -785,27 +775,30 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
785
775
|
suggestion: 'Use reset() to navigate to the starting page.',
|
|
786
776
|
});
|
|
787
777
|
}
|
|
788
|
-
|
|
778
|
+
let previousState = null;
|
|
779
|
+
if (currentState)
|
|
780
|
+
previousState = ActionResult.fromState(currentState);
|
|
781
|
+
const action = explorer.action();
|
|
789
782
|
const success = await action.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, `${reason} (BACK to ${targetUrl})`);
|
|
790
783
|
if (success) {
|
|
791
|
-
const
|
|
792
|
-
const toolResult = await
|
|
784
|
+
const newState = ActionResult.fromState(stateManager.getCurrentState());
|
|
785
|
+
const toolResult = await newState.toToolResult(previousState, `I.amOnPage("${targetUrl}")`);
|
|
793
786
|
return successToolResult('back', {
|
|
794
787
|
...toolResult,
|
|
795
788
|
message: `Navigated back to ${targetUrl}`,
|
|
796
789
|
});
|
|
797
790
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
});
|
|
791
|
+
const failData = { suggestion: 'Try reset() to return to the starting page.' };
|
|
792
|
+
if (action.lastError)
|
|
793
|
+
failData.error = action.lastError.toString();
|
|
794
|
+
return failedToolResult('back', `Failed to navigate back to ${targetUrl}`, failData);
|
|
802
795
|
},
|
|
803
796
|
}),
|
|
804
797
|
getVisitedStates: tool({
|
|
805
798
|
description: 'List all previously visited page states (deduped by URL). Use to find pages to navigate back to.',
|
|
806
799
|
inputSchema: z.object({}),
|
|
807
800
|
execute: async () => {
|
|
808
|
-
const history =
|
|
801
|
+
const history = stateManager.getStateHistory();
|
|
809
802
|
const seen = new Set();
|
|
810
803
|
const states = history
|
|
811
804
|
.map((t) => t.toState)
|
|
@@ -816,7 +809,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
816
809
|
return true;
|
|
817
810
|
})
|
|
818
811
|
.map((s, i) => ({ index: i, url: s.url, title: s.title, h1: s.h1 }));
|
|
819
|
-
return {
|
|
812
|
+
return successToolResult('getVisitedStates', { states });
|
|
820
813
|
},
|
|
821
814
|
}),
|
|
822
815
|
xpathCheck: tool({
|
|
@@ -838,7 +831,6 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
838
831
|
reason: z.string().describe('What element you are looking for and why'),
|
|
839
832
|
}),
|
|
840
833
|
execute: async ({ xpath, reason }) => {
|
|
841
|
-
const stateManager = explorer.getStateManager();
|
|
842
834
|
const currentState = stateManager.getCurrentState();
|
|
843
835
|
if (!currentState) {
|
|
844
836
|
return failedToolResult('xpathCheck', 'No current page state available.');
|
|
@@ -858,40 +850,37 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
858
850
|
suggestion: 'Try a broader expression. Examples: //*[contains(@class, "btn")], //button, //*[contains(text(), "keyword")]',
|
|
859
851
|
});
|
|
860
852
|
}
|
|
861
|
-
const action = explorer.
|
|
862
|
-
const visible = await action.attempt(`I.seeElement(${JSON.stringify(xpath)})`, 'xpathCheck visibility'
|
|
853
|
+
const action = explorer.action();
|
|
854
|
+
const visible = await action.attempt(`I.seeElement(${JSON.stringify(xpath)})`, 'xpathCheck visibility');
|
|
855
|
+
const liveElement = await explorer.withPage((page) => WebElement.fromPlaywrightLocator(page.locator(`xpath=${xpath}`)));
|
|
863
856
|
const matchesSummary = result.elements.map((el, i) => `${i + 1}. <${el.tag} ${el.keyAttrs}> text="${el.text}" html: ${el.outerHTML}`).join('\n');
|
|
864
857
|
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.';
|
|
858
|
+
const hit = liveElement?.ourAttr('hit') || null;
|
|
859
|
+
const coveredBy = liveElement?.ourAttr('coveredBy') || null;
|
|
860
|
+
let resolvedVisibilityNote = visibilityNote;
|
|
861
|
+
if (hit === 'covered') {
|
|
862
|
+
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.`;
|
|
863
|
+
}
|
|
864
|
+
if (hit === 'offscreen') {
|
|
865
|
+
resolvedVisibilityNote = 'Element exists in DOM but its interaction point is outside the viewport. Scroll or reveal it before interacting.';
|
|
866
|
+
}
|
|
865
867
|
return successToolResult('xpathCheck', {
|
|
866
868
|
totalFound: result.totalFound,
|
|
867
869
|
matches: matchesSummary,
|
|
868
|
-
visibilityNote,
|
|
870
|
+
visibilityNote: resolvedVisibilityNote,
|
|
871
|
+
hit,
|
|
872
|
+
coveredBy,
|
|
869
873
|
xpath,
|
|
870
874
|
});
|
|
871
875
|
},
|
|
872
876
|
}),
|
|
873
877
|
};
|
|
874
|
-
if (
|
|
875
|
-
tools.learnExperience =
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
`,
|
|
881
|
-
inputSchema: z.object({
|
|
882
|
-
fileTag: z.string().describe('File tag from the TOC, e.g. "A", "B", "AA"'),
|
|
883
|
-
sectionIndex: z.number().int().positive().describe('1-based section index within that file'),
|
|
884
|
-
}),
|
|
885
|
-
execute: async ({ fileTag, sectionIndex }) => {
|
|
886
|
-
const state = getState();
|
|
887
|
-
if (!state) {
|
|
888
|
-
return { error: 'No current page state available.' };
|
|
889
|
-
}
|
|
890
|
-
const section = experienceTracker.getExperienceSection(fileTag, sectionIndex, state);
|
|
891
|
-
if (!section) {
|
|
892
|
-
return { error: 'Section not found. Experience may have been updated; re-read the latest TOC.' };
|
|
893
|
-
}
|
|
894
|
-
return section;
|
|
878
|
+
if (withExperience !== false) {
|
|
879
|
+
tools.learnExperience = createLearnExperienceTool({
|
|
880
|
+
getExperienceTracker: () => stateManager.getExperienceTracker(),
|
|
881
|
+
getState: () => {
|
|
882
|
+
const currentState = stateManager.getCurrentState();
|
|
883
|
+
return currentState ? ActionResult.fromState(currentState) : null;
|
|
895
884
|
},
|
|
896
885
|
});
|
|
897
886
|
}
|
|
@@ -970,11 +959,16 @@ function transformContainsCommand(command) {
|
|
|
970
959
|
}
|
|
971
960
|
return command;
|
|
972
961
|
}
|
|
973
|
-
function
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
962
|
+
function errorText(error) {
|
|
963
|
+
if (error instanceof Error)
|
|
964
|
+
return error.toString();
|
|
965
|
+
return 'Unknown error occurred';
|
|
966
|
+
}
|
|
967
|
+
async function commitNote(activeNote, result, toolResult, action) {
|
|
968
|
+
if (toolResult?.pageDiff?.ariaChanges || toolResult?.pageDiff?.urlChanged) {
|
|
969
|
+
activeNote.screenshot = await action.saveScreenshot();
|
|
970
|
+
}
|
|
971
|
+
activeNote.commit(result);
|
|
978
972
|
}
|
|
979
973
|
function successToolResult(action, data, source) {
|
|
980
974
|
const result = { success: true, action, ...data };
|
|
@@ -989,7 +983,7 @@ function successToolResult(action, data, source) {
|
|
|
989
983
|
const ariaChanges = data.pageDiff.ariaChanges || '';
|
|
990
984
|
const urlChanged = data.pageDiff.urlChanged === true;
|
|
991
985
|
const hasHtmlParts = Array.isArray(data.pageDiff.htmlParts) && data.pageDiff.htmlParts.length > 0;
|
|
992
|
-
if (
|
|
986
|
+
if (isMajorPageChange(data.pageDiff)) {
|
|
993
987
|
suggestion = `MAJOR PAGE CHANGE. Page entered a different mode. Check htmlParts and iframes in pageDiff before next action. ${suggestion}`;
|
|
994
988
|
}
|
|
995
989
|
else if (!urlChanged && !ariaChanges && !hasHtmlParts) {
|
|
@@ -1002,6 +996,18 @@ function successToolResult(action, data, source) {
|
|
|
1002
996
|
}
|
|
1003
997
|
return result;
|
|
1004
998
|
}
|
|
999
|
+
export function isMajorPageChange(pageDiff) {
|
|
1000
|
+
return pageDiff.urlChanged !== true && (pageDiff.ariaChangeCount ?? 0) >= LARGE_ARIA_CHANGE_THRESHOLD;
|
|
1001
|
+
}
|
|
1002
|
+
function hasObservablePageChange(data) {
|
|
1003
|
+
if (!data?.pageDiff)
|
|
1004
|
+
return false;
|
|
1005
|
+
if (data.pageDiff.urlChanged === true)
|
|
1006
|
+
return true;
|
|
1007
|
+
if (data.pageDiff.ariaChanges)
|
|
1008
|
+
return true;
|
|
1009
|
+
return Array.isArray(data.pageDiff.htmlParts) && data.pageDiff.htmlParts.length > 0;
|
|
1010
|
+
}
|
|
1005
1011
|
async function failedToolResult(action, message, data, error) {
|
|
1006
1012
|
const result = { success: false, action, message, ...data };
|
|
1007
1013
|
if (data?.pageDiff) {
|
|
@@ -1085,7 +1091,7 @@ async function disambiguateElements(error, explanation, provider) {
|
|
|
1085
1091
|
Return the position number, or null if none of them match.
|
|
1086
1092
|
`,
|
|
1087
1093
|
},
|
|
1088
|
-
], schema, provider.
|
|
1094
|
+
], schema, provider.getModelForAgent(), { agentName: 'disambiguator', timeout: 15000 });
|
|
1089
1095
|
const position = result?.object?.position;
|
|
1090
1096
|
if (position && position >= 1 && position <= elementDetails.length) {
|
|
1091
1097
|
return { position, xpath: elementDetails[position - 1].xpath };
|
|
@@ -1098,7 +1104,7 @@ async function disambiguateElements(error, explanation, provider) {
|
|
|
1098
1104
|
}
|
|
1099
1105
|
}
|
|
1100
1106
|
function getNotFoundSuggestion(errorMessage) {
|
|
1101
|
-
if (!errorMessage.includes('
|
|
1107
|
+
if (!errorMessage.includes('not found')) {
|
|
1102
1108
|
return null;
|
|
1103
1109
|
}
|
|
1104
1110
|
return dedent `
|
|
@@ -131,7 +131,7 @@ export class CommandHandler {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
async executeBrowserCommand(input) {
|
|
134
|
-
const action = this.explorBot.getExplorer().
|
|
134
|
+
const action = this.explorBot.getExplorer().action();
|
|
135
135
|
await action.execute(input);
|
|
136
136
|
}
|
|
137
137
|
isCommand(input) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExperienceFile } from '../
|
|
1
|
+
import type { ExperienceFile } from '../experience-tracker.js';
|
|
2
2
|
import type { ExperienceTracker } from '../experience-tracker.js';
|
|
3
3
|
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
4
4
|
export declare class CompactCommand extends BaseCommand {
|