explorbot 0.1.27 → 0.1.29
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 +83 -245
- package/bin/explorbot-cli.ts +1 -0
- package/dist/bin/explorbot-cli.js +1 -0
- package/dist/package.json +8 -6
- package/dist/rules/navigator/verification-actions.md +2 -0
- package/dist/src/action-result.js +2 -0
- package/dist/src/action.js +88 -7
- package/dist/src/ai/captain/file-tools.js +100 -0
- package/dist/src/ai/captain/idle-mode.js +70 -6
- package/dist/src/ai/captain/web-mode.js +36 -6
- package/dist/src/ai/captain.js +87 -19
- package/dist/src/ai/fisherman.js +14 -3
- package/dist/src/ai/historian/screencast.js +11 -2
- package/dist/src/ai/navigator.js +5 -2
- package/dist/src/ai/pilot.js +52 -9
- package/dist/src/ai/planner.js +16 -5
- package/dist/src/ai/provider.js +53 -18
- package/dist/src/ai/researcher/coordinates.js +2 -3
- package/dist/src/ai/researcher/deep-analysis.js +3 -4
- package/dist/src/ai/researcher/locators.js +1 -2
- package/dist/src/ai/researcher.js +24 -19
- package/dist/src/ai/rules.js +44 -0
- package/dist/src/ai/task-agent.js +1 -0
- package/dist/src/ai/tester.js +161 -46
- package/dist/src/ai/tools.js +84 -8
- package/dist/src/commands/explore-command.js +6 -1
- package/dist/src/components/LogPane.js +4 -3
- package/dist/src/explorbot.js +7 -2
- package/dist/src/explorer.js +270 -35
- package/dist/src/stats.js +16 -0
- package/dist/src/utils/aria.js +66 -6
- package/dist/src/utils/browser-errors.js +23 -0
- package/dist/src/utils/error-page.js +17 -2
- package/dist/src/utils/logger.js +2 -2
- package/package.json +8 -6
- package/rules/navigator/verification-actions.md +2 -0
- package/src/action-result.ts +2 -0
- package/src/action.ts +83 -7
- package/src/ai/captain/file-tools.ts +126 -0
- package/src/ai/captain/idle-mode.ts +72 -6
- package/src/ai/captain/mixin.ts +1 -1
- package/src/ai/captain/web-mode.ts +40 -5
- package/src/ai/captain.ts +94 -20
- package/src/ai/fisherman.ts +14 -3
- package/src/ai/historian/screencast.ts +11 -2
- package/src/ai/navigator.ts +6 -2
- package/src/ai/pilot.ts +53 -9
- package/src/ai/planner.ts +16 -5
- package/src/ai/provider.ts +51 -19
- package/src/ai/researcher/coordinates.ts +2 -3
- package/src/ai/researcher/deep-analysis.ts +3 -4
- package/src/ai/researcher/locators.ts +1 -2
- package/src/ai/researcher.ts +25 -19
- package/src/ai/rules.ts +46 -0
- package/src/ai/task-agent.ts +1 -1
- package/src/ai/tester.ts +175 -48
- package/src/ai/tools.ts +97 -8
- package/src/commands/explore-command.ts +6 -1
- package/src/components/LogPane.tsx +4 -3
- package/src/config.ts +1 -0
- package/src/explorbot.ts +6 -2
- package/src/explorer.ts +295 -38
- package/src/state-manager.ts +2 -0
- package/src/stats.ts +18 -0
- package/src/utils/aria.ts +63 -6
- package/src/utils/browser-errors.ts +25 -0
- package/src/utils/error-page.ts +16 -3
- package/src/utils/logger.ts +3 -3
package/dist/src/ai/tools.js
CHANGED
|
@@ -4,13 +4,14 @@ import { z } from 'zod';
|
|
|
4
4
|
import { ActionResult } from "../action-result.js";
|
|
5
5
|
import { TestResult } from '../test-plan.js';
|
|
6
6
|
import { extractFocusedElement } from "../utils/aria.js";
|
|
7
|
+
import { isFatalBrowserError } from "../utils/browser-errors.js";
|
|
7
8
|
import { createDebug, tag } from '../utils/logger.js';
|
|
8
9
|
import { pause } from '../utils/loop.js';
|
|
9
10
|
import { WebElement } from "../utils/web-element.js";
|
|
10
11
|
import { sectionContextRule } from "./rules.js";
|
|
11
12
|
import { isInteractive } from "./task-agent.js";
|
|
12
13
|
const debugLog = createDebug('explorbot:tools');
|
|
13
|
-
export const CODECEPT_TOOLS = ['click', 'pressKey', 'form'];
|
|
14
|
+
export const CODECEPT_TOOLS = ['click', 'hover', 'pressKey', 'form'];
|
|
14
15
|
export const ASSERTION_TOOLS = ['verify'];
|
|
15
16
|
export function createCodeceptJSTools(explorer, task) {
|
|
16
17
|
const stateManager = explorer.getStateManager();
|
|
@@ -132,6 +133,71 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
132
133
|
}, action.lastError);
|
|
133
134
|
},
|
|
134
135
|
}),
|
|
136
|
+
hover: tool({
|
|
137
|
+
description: dedent `
|
|
138
|
+
Move the mouse cursor to an element to reveal hover-only controls.
|
|
139
|
+
|
|
140
|
+
Use this before clicking row actions, icon buttons, menus, or toolbars that appear only
|
|
141
|
+
when the user hovers a list item, table row, card, or tree node.
|
|
142
|
+
|
|
143
|
+
This tool ONLY accepts I.moveCursorTo(locator) commands. It does not click.
|
|
144
|
+
After hovering, use context(), see(), or click() the revealed control.
|
|
145
|
+
`,
|
|
146
|
+
inputSchema: z.object({
|
|
147
|
+
commands: z.array(z.string()).describe(dedent `
|
|
148
|
+
FALLBACK LOCATORS for ONE element to hover.
|
|
149
|
+
Order by reliability:
|
|
150
|
+
1. I.moveCursorTo(text, container)
|
|
151
|
+
2. I.moveCursorTo(ARIA, container)
|
|
152
|
+
3. I.moveCursorTo(CSS, container)
|
|
153
|
+
4. I.moveCursorTo(CSS) or I.moveCursorTo(XPath)
|
|
154
|
+
`),
|
|
155
|
+
explanation: z.string().describe('Why you are hovering this element'),
|
|
156
|
+
}),
|
|
157
|
+
execute: async ({ commands: rawCommands, explanation }) => {
|
|
158
|
+
const activeNote = task.startNote(explanation);
|
|
159
|
+
if (rawCommands.length === 0) {
|
|
160
|
+
activeNote.commit(TestResult.FAILED);
|
|
161
|
+
return failedToolResult('hover', 'No commands provided');
|
|
162
|
+
}
|
|
163
|
+
const invalidCommands = rawCommands.map((cmd) => cmd.trim()).filter((cmd) => cmd.startsWith('I.') && !cmd.startsWith('I.moveCursorTo'));
|
|
164
|
+
if (invalidCommands.length > 0) {
|
|
165
|
+
activeNote.commit(TestResult.FAILED);
|
|
166
|
+
return failedToolResult('hover', `Invalid commands: ${invalidCommands.join(', ')}. Hover tool only accepts I.moveCursorTo() commands.`, {
|
|
167
|
+
suggestion: 'Use click() to click elements, or form() for typing/selecting.',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const commands = rawCommands.map((cmd) => {
|
|
171
|
+
const trimmed = cmd.trim();
|
|
172
|
+
if (trimmed.startsWith('I.moveCursorTo'))
|
|
173
|
+
return trimmed;
|
|
174
|
+
return `I.moveCursorTo(${JSON.stringify(trimmed)})`;
|
|
175
|
+
});
|
|
176
|
+
const previousState = ActionResult.fromState(stateManager.getCurrentState());
|
|
177
|
+
const action = explorer.createAction();
|
|
178
|
+
const attempts = [];
|
|
179
|
+
for (const command of commands) {
|
|
180
|
+
const success = await action.attempt(command, explanation, true);
|
|
181
|
+
attempts.push({
|
|
182
|
+
command,
|
|
183
|
+
success,
|
|
184
|
+
...(action.lastError && { error: action.lastError.toString() }),
|
|
185
|
+
});
|
|
186
|
+
if (!success)
|
|
187
|
+
continue;
|
|
188
|
+
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, command);
|
|
189
|
+
activeNote.commit(TestResult.PASSED);
|
|
190
|
+
return successToolResult('hover', { ...toolResult, attempts, code: command }, action);
|
|
191
|
+
}
|
|
192
|
+
const toolResult = await ActionResult.fromState(stateManager.getCurrentState()).toToolResult(previousState, commands[0]);
|
|
193
|
+
activeNote.commit(TestResult.FAILED);
|
|
194
|
+
return failedToolResult('hover', 'All hover commands failed', {
|
|
195
|
+
...toolResult,
|
|
196
|
+
attempts,
|
|
197
|
+
suggestion: 'Use xpathCheck() to locate the row/card/tree node, or visualClick() if the hover target is only visually identifiable.',
|
|
198
|
+
}, action.lastError);
|
|
199
|
+
},
|
|
200
|
+
}),
|
|
135
201
|
pressKey: tool({
|
|
136
202
|
description: dedent `
|
|
137
203
|
Press a keyboard key or key combination. Use this for special keys like Enter, Escape, Tab, Arrow keys, or key combinations with modifiers.
|
|
@@ -243,6 +309,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
243
309
|
});
|
|
244
310
|
}
|
|
245
311
|
catch (error) {
|
|
312
|
+
throwIfFatalBrowserError(error);
|
|
246
313
|
activeNote.commit(TestResult.FAILED);
|
|
247
314
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
248
315
|
return failedToolResult('pressKey', `PressKey tool failed: ${errorMessage}`);
|
|
@@ -343,6 +410,7 @@ export function createCodeceptJSTools(explorer, task) {
|
|
|
343
410
|
}, action);
|
|
344
411
|
}
|
|
345
412
|
catch (error) {
|
|
413
|
+
throwIfFatalBrowserError(error);
|
|
346
414
|
activeNote.commit(TestResult.FAILED);
|
|
347
415
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
348
416
|
return failedToolResult('form', `Form tool failed: ${errorMessage}`);
|
|
@@ -376,8 +444,7 @@ export function createSpecialContextTools(explorer, context) {
|
|
|
376
444
|
});
|
|
377
445
|
}
|
|
378
446
|
await explorer.switchToMainFrame();
|
|
379
|
-
const
|
|
380
|
-
const nextState = await action.capturePageState();
|
|
447
|
+
const nextState = await explorer.capturePageState();
|
|
381
448
|
const toolResult = await nextState.toToolResult(previousState, 'I.switchTo()');
|
|
382
449
|
return successToolResult('exitIframe', {
|
|
383
450
|
...toolResult,
|
|
@@ -386,6 +453,7 @@ export function createSpecialContextTools(explorer, context) {
|
|
|
386
453
|
});
|
|
387
454
|
}
|
|
388
455
|
catch (error) {
|
|
456
|
+
throwIfFatalBrowserError(error);
|
|
389
457
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
390
458
|
return failedToolResult('exitIframe', `Failed to exit iframe: ${errorMessage}`);
|
|
391
459
|
}
|
|
@@ -401,6 +469,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
401
469
|
Check the page contents based on current page state and screenshot.
|
|
402
470
|
This tool will trigger visual research to check the page contents on request.
|
|
403
471
|
Use it to verify the actions were performed correctly and the page is in the expected state.
|
|
472
|
+
Input schema has exactly one field: request. Do not pass text, reason, assertion, or other fields.
|
|
404
473
|
|
|
405
474
|
<example>
|
|
406
475
|
request: "Check current state of the Login form"
|
|
@@ -415,8 +484,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
415
484
|
return failedToolResult('see', 'Vision tools are disabled for this session. Use context() to get fresh ARIA snapshot and analyze page state from ARIA data.');
|
|
416
485
|
}
|
|
417
486
|
try {
|
|
418
|
-
const
|
|
419
|
-
const actionResult = await action.caputrePageWithScreenshot();
|
|
487
|
+
const actionResult = await explorer.capturePageWithScreenshot();
|
|
420
488
|
if (!actionResult.screenshot) {
|
|
421
489
|
return failedToolResult('see', 'Failed to capture screenshot for analysis');
|
|
422
490
|
}
|
|
@@ -431,6 +499,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
431
499
|
});
|
|
432
500
|
}
|
|
433
501
|
catch (error) {
|
|
502
|
+
throwIfFatalBrowserError(error);
|
|
434
503
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
435
504
|
visionDisabled = true;
|
|
436
505
|
tag('warning').log('⚠️ Vision model is not available. Visual checks are disabled for this session.');
|
|
@@ -506,8 +575,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
506
575
|
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.',
|
|
507
576
|
});
|
|
508
577
|
}
|
|
509
|
-
const
|
|
510
|
-
const actionResult = await action.capturePageState();
|
|
578
|
+
const actionResult = await explorer.capturePageState();
|
|
511
579
|
const result = await navigator.verifyState(assertion, actionResult);
|
|
512
580
|
if (result.verified) {
|
|
513
581
|
return successToolResult('verify', {
|
|
@@ -520,6 +588,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
520
588
|
});
|
|
521
589
|
}
|
|
522
590
|
catch (error) {
|
|
591
|
+
throwIfFatalBrowserError(error);
|
|
523
592
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
524
593
|
return failedToolResult('verify', `Verify tool failed: ${errorMessage}`, {
|
|
525
594
|
error: errorMessage,
|
|
@@ -572,6 +641,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
572
641
|
});
|
|
573
642
|
}
|
|
574
643
|
catch (error) {
|
|
644
|
+
throwIfFatalBrowserError(error);
|
|
575
645
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
576
646
|
return failedToolResult('research', `Research tool failed: ${errorMessage}`, {
|
|
577
647
|
error: errorMessage,
|
|
@@ -611,6 +681,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
611
681
|
});
|
|
612
682
|
}
|
|
613
683
|
catch (error) {
|
|
684
|
+
throwIfFatalBrowserError(error);
|
|
614
685
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
615
686
|
return failedToolResult('interact', `Interact tool failed: ${errorMessage}`, {
|
|
616
687
|
error: errorMessage,
|
|
@@ -645,7 +716,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
645
716
|
}
|
|
646
717
|
const previousState = ActionResult.fromState(currentState);
|
|
647
718
|
const action = explorer.createAction();
|
|
648
|
-
const actionResult = await
|
|
719
|
+
const actionResult = await explorer.capturePageWithScreenshot();
|
|
649
720
|
if (!actionResult.screenshot) {
|
|
650
721
|
return failedToolResult('visualClick', 'Failed to capture screenshot for visual analysis');
|
|
651
722
|
}
|
|
@@ -678,6 +749,7 @@ export function createAgentTools({ explorer, researcher, navigator, experienceTr
|
|
|
678
749
|
});
|
|
679
750
|
}
|
|
680
751
|
catch (error) {
|
|
752
|
+
throwIfFatalBrowserError(error);
|
|
681
753
|
const errorMessage = error instanceof Error ? error.toString() : 'Unknown error occurred';
|
|
682
754
|
visionDisabled = true;
|
|
683
755
|
tag('warning').log('⚠️ Vision model is not available. Visual clicks are disabled for this session.');
|
|
@@ -874,6 +946,10 @@ function cap(text, max) {
|
|
|
874
946
|
return text;
|
|
875
947
|
return `${text.slice(0, max)}\n[...truncated; ${text.length - max} chars omitted...]`;
|
|
876
948
|
}
|
|
949
|
+
function throwIfFatalBrowserError(error) {
|
|
950
|
+
if (isFatalBrowserError(error))
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
877
953
|
function transformContainsCommand(command) {
|
|
878
954
|
if (!command.includes(':contains('))
|
|
879
955
|
return command;
|
|
@@ -5,7 +5,7 @@ import { normalizeUrl } from '../state-manager.js';
|
|
|
5
5
|
import { Stats } from '../stats.js';
|
|
6
6
|
import { TestResult } from '../test-plan.js';
|
|
7
7
|
import { getCliName } from "../utils/cli-name.js";
|
|
8
|
-
import { ErrorPageError } from "../utils/error-page.js";
|
|
8
|
+
import { ErrorPageError, getStateErrorPageError } from "../utils/error-page.js";
|
|
9
9
|
import { tag } from '../utils/logger.js';
|
|
10
10
|
import { printNextSteps, relativeToCwd } from "../utils/next-steps.js";
|
|
11
11
|
import { safeFilename } from "../utils/strings.js";
|
|
@@ -53,6 +53,11 @@ export class ExploreCommand extends BaseCommand {
|
|
|
53
53
|
Stats.mode ??= 'explore';
|
|
54
54
|
Stats.focus ??= feature;
|
|
55
55
|
const mainUrl = this.getCurrentPageUrl();
|
|
56
|
+
const error = getStateErrorPageError(this.explorBot.getExplorer().getStateManager().getCurrentState());
|
|
57
|
+
if (error) {
|
|
58
|
+
tag('warning').log(error.message);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
56
61
|
if (cfg.enabled) {
|
|
57
62
|
await this.runReuseMode(mainUrl, feature, cfg);
|
|
58
63
|
}
|
|
@@ -94,6 +94,7 @@ const LogPane = React.memo(({ verboseMode }) => {
|
|
|
94
94
|
case 'step':
|
|
95
95
|
return { color: 'cyan', dimColor: true };
|
|
96
96
|
case 'multiline':
|
|
97
|
+
case 'details':
|
|
97
98
|
return { color: 'gray', dimColor: true };
|
|
98
99
|
case 'html':
|
|
99
100
|
return { color: 'gray' };
|
|
@@ -113,14 +114,14 @@ const LogPane = React.memo(({ verboseMode }) => {
|
|
|
113
114
|
return null;
|
|
114
115
|
}
|
|
115
116
|
const styles = getLogStyles(log.type);
|
|
116
|
-
if (log.type === 'multiline') {
|
|
117
|
+
if (log.type === 'multiline' || log.type === 'details') {
|
|
117
118
|
const cleaned = stripAnsi(dedent(log.content));
|
|
118
119
|
const parsed = parseMarkdownToTerminal(cleaned);
|
|
119
120
|
const lines = parsed.split('\n');
|
|
120
121
|
const maxLines = log.maxLines || 16;
|
|
121
|
-
const
|
|
122
|
+
const content = log.type === 'details' ? parsed : lines.length > maxLines ? `${lines.slice(0, maxLines).join('\n')}\n... (${lines.length - maxLines} more lines)` : parsed;
|
|
122
123
|
return (React.createElement(Box, { key: index, borderStyle: "classic", borderLeft: false, borderRight: false, marginY: 1, padding: 1, borderColor: "dim", overflow: "hidden" },
|
|
123
|
-
React.createElement(Text, { color: "gray", dimColor: true },
|
|
124
|
+
React.createElement(Text, { color: "gray", dimColor: true }, content)));
|
|
124
125
|
}
|
|
125
126
|
if (log.type === 'html') {
|
|
126
127
|
// Convert HTML to markdown, then render as multiline
|
package/dist/src/explorbot.js
CHANGED
|
@@ -23,11 +23,12 @@ import { ConfigParser } from "./config.js";
|
|
|
23
23
|
import { ExperienceTracker } from "./experience-tracker.js";
|
|
24
24
|
import Explorer from "./explorer.js";
|
|
25
25
|
import { KnowledgeTracker } from "./knowledge-tracker.js";
|
|
26
|
+
import { Stats } from "./stats.js";
|
|
26
27
|
import { Plan } from "./test-plan.js";
|
|
27
|
-
import { parsePlansFromMarkdown } from "./utils/test-plan-markdown.js";
|
|
28
28
|
import { setVerboseMode, tag } from "./utils/logger.js";
|
|
29
29
|
import { relativeToCwd } from "./utils/next-steps.js";
|
|
30
30
|
import { sanitizeFilename } from "./utils/strings.js";
|
|
31
|
+
import { parsePlansFromMarkdown } from "./utils/test-plan-markdown.js";
|
|
31
32
|
export class ExplorBot {
|
|
32
33
|
configParser;
|
|
33
34
|
explorer;
|
|
@@ -427,7 +428,11 @@ export class ExplorBot {
|
|
|
427
428
|
tag('info').log(`Session report saved: ${relativeToCwd(filePath)}`);
|
|
428
429
|
const reporter = this.explorer?.getReporter();
|
|
429
430
|
if (reporter?.isEnabled()) {
|
|
430
|
-
|
|
431
|
+
let description = markdown;
|
|
432
|
+
const modelsTable = Stats.modelsTable(this.provider.getConfiguredModels());
|
|
433
|
+
if (modelsTable)
|
|
434
|
+
description = `${markdown}\n\n${modelsTable}`;
|
|
435
|
+
await reporter.setRunDescription(description);
|
|
431
436
|
}
|
|
432
437
|
this.lastReportedTestCount = tests.length;
|
|
433
438
|
}
|