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/action.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { faker } from '@faker-js/faker';
|
|
4
3
|
import { context, trace } from '@opentelemetry/api';
|
|
5
4
|
import { container, recorder } from 'codeceptjs';
|
|
6
5
|
import * as codeceptjs from 'codeceptjs';
|
|
7
|
-
import { hopeThat, retryTo, tryTo, within } from 'codeceptjs/lib/effects';
|
|
8
|
-
import step from 'codeceptjs/steps';
|
|
9
6
|
import { ActionResult } from './action-result.js';
|
|
10
7
|
import { clearActivity, setActivity } from "./activity.js";
|
|
11
8
|
import { ConfigParser, outputPath } from './config.js';
|
|
12
9
|
import { Observability } from "./observability.js";
|
|
13
|
-
import { isFatalBrowserError, isNavigationTransitionError } from "./utils/browser-errors.js";
|
|
14
|
-
import { htmlCombinedSnapshot, minifyHtml } from './utils/html.js';
|
|
10
|
+
import { browserErrorMessage, isFatalBrowserError, isNavigationTransitionError } from "./utils/browser-errors.js";
|
|
11
|
+
import { captureHtmlForSnapshot, htmlCombinedSnapshot, minifyHtml } from './utils/html.js';
|
|
15
12
|
import { createDebug, setStepSpanParent, tag } from './utils/logger.js';
|
|
16
|
-
import { waitForPageReadiness } from "./utils/page-readiness.js";
|
|
13
|
+
import { sleep, waitForPageReadiness } from "./utils/page-readiness.js";
|
|
17
14
|
import { safeFilename } from "./utils/strings.js";
|
|
15
|
+
import { codeceptJSSandbox, hasPlaywrightCommands, playwrightSandbox, sanitizeCodeBlock } from "./utils/web-sandbox.js";
|
|
18
16
|
const debugLog = createDebug('explorbot:action');
|
|
19
17
|
const CAPTURE_NAVIGATION_TRANSITION_ATTEMPTS = 3;
|
|
20
18
|
class Action {
|
|
@@ -24,22 +22,20 @@ class Action {
|
|
|
24
22
|
config;
|
|
25
23
|
// action info
|
|
26
24
|
action = null;
|
|
27
|
-
expectation = null;
|
|
28
25
|
lastError = null;
|
|
29
26
|
playwrightHelper;
|
|
30
27
|
playwrightGroupId = null;
|
|
31
28
|
assertionSteps = [];
|
|
32
29
|
recorder;
|
|
30
|
+
recovery;
|
|
33
31
|
mainDocumentStatus = undefined;
|
|
34
|
-
constructor(actor, stateManager, recorder) {
|
|
32
|
+
constructor(actor, stateManager, recorder, recovery) {
|
|
35
33
|
this.actor = actor;
|
|
36
34
|
this.stateManager = stateManager;
|
|
37
35
|
this.config = ConfigParser.getInstance().getConfig();
|
|
38
36
|
this.playwrightHelper = container.helpers('Playwright');
|
|
39
37
|
this.recorder = recorder;
|
|
40
|
-
|
|
41
|
-
async caputrePageWithScreenshot() {
|
|
42
|
-
return this.capturePageState({ includeScreenshot: true });
|
|
38
|
+
this.recovery = recovery || ((fn) => fn());
|
|
43
39
|
}
|
|
44
40
|
async saveScreenshot() {
|
|
45
41
|
const currentState = this.stateManager.getCurrentState();
|
|
@@ -58,7 +54,13 @@ class Action {
|
|
|
58
54
|
return undefined;
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
|
-
async capturePageState(
|
|
57
|
+
async capturePageState(opts = {}) {
|
|
58
|
+
return this.recovery(() => this.captureOnce(opts));
|
|
59
|
+
}
|
|
60
|
+
async execute(code) {
|
|
61
|
+
return this.recovery(() => this.executeOnce(code));
|
|
62
|
+
}
|
|
63
|
+
async captureOnce({ includeScreenshot = false, codeBlock } = {}) {
|
|
62
64
|
try {
|
|
63
65
|
const currentState = this.stateManager.getCurrentState();
|
|
64
66
|
const stateHash = currentState?.hash || 'screenshot';
|
|
@@ -145,11 +147,11 @@ class Action {
|
|
|
145
147
|
ariaSnapshotFile,
|
|
146
148
|
iframeURL: frame ? frame.url?.() || 'iframe' : undefined,
|
|
147
149
|
});
|
|
148
|
-
this.stateManager.updateState(result);
|
|
150
|
+
this.stateManager.updateState(result, codeBlock);
|
|
149
151
|
return result;
|
|
150
152
|
}
|
|
151
153
|
catch (err) {
|
|
152
|
-
const msg =
|
|
154
|
+
const msg = browserErrorMessage(err);
|
|
153
155
|
if (isFatalBrowserError(err))
|
|
154
156
|
throw err;
|
|
155
157
|
debugLog('capturePageState failed with non-fatal error:', msg);
|
|
@@ -249,7 +251,7 @@ class Action {
|
|
|
249
251
|
return [];
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
|
-
async
|
|
254
|
+
async executeOnce(code) {
|
|
253
255
|
let error = null;
|
|
254
256
|
setActivity('🔎 Browsing...', 'action');
|
|
255
257
|
let codeString = code.replace(/^\(I\) => /, '').trim();
|
|
@@ -272,21 +274,18 @@ class Action {
|
|
|
272
274
|
}
|
|
273
275
|
if (isPlaywright) {
|
|
274
276
|
const page = this.playwrightHelper.page;
|
|
275
|
-
|
|
276
|
-
await asyncFn(page);
|
|
277
|
+
await playwrightSandbox(page, sanitizedCode);
|
|
277
278
|
await sleep(this.config.action?.delay || 500);
|
|
278
279
|
}
|
|
279
280
|
else {
|
|
280
|
-
|
|
281
|
-
codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step, faker);
|
|
281
|
+
codeceptJSSandbox(this.actor, sanitizedCode);
|
|
282
282
|
await recorder.add(() => sleep(this.config.action?.delay || 500));
|
|
283
283
|
await recorder.promise();
|
|
284
284
|
}
|
|
285
|
-
const pageState = await this.capturePageState();
|
|
286
285
|
if (executedSteps.length > 0) {
|
|
287
286
|
codeString = executedSteps.join('\n');
|
|
288
287
|
}
|
|
289
|
-
this.
|
|
288
|
+
const pageState = await this.captureOnce({ codeBlock: codeString });
|
|
290
289
|
this.actionResult = pageState;
|
|
291
290
|
this.assertionSteps = assertionSteps;
|
|
292
291
|
}
|
|
@@ -313,98 +312,37 @@ class Action {
|
|
|
313
312
|
}
|
|
314
313
|
return this;
|
|
315
314
|
}
|
|
316
|
-
async
|
|
317
|
-
const
|
|
318
|
-
this.expectation = codeString.toString();
|
|
319
|
-
const expectationPreview = sanitizeCodeBlock(codeString)
|
|
320
|
-
.split('\n')
|
|
321
|
-
.map((line) => line.trim())
|
|
322
|
-
.filter(Boolean)
|
|
323
|
-
.slice(0, 2)
|
|
324
|
-
.join(' ');
|
|
325
|
-
tag('step').log(`Expecting: ${expectationPreview || 'assertion'}`);
|
|
326
|
-
try {
|
|
327
|
-
debugLog('Executing expectation:', codeString);
|
|
328
|
-
let codeFunction;
|
|
329
|
-
if (typeof codeOrFunction === 'function') {
|
|
330
|
-
codeFunction = codeOrFunction;
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
const sanitizedCode = sanitizeCodeBlock(codeString);
|
|
334
|
-
if (!sanitizedCode) {
|
|
335
|
-
throw new Error('No valid I.* commands found in code block');
|
|
336
|
-
}
|
|
337
|
-
codeFunction = createSandboxedFunction(['I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step'], sanitizedCode);
|
|
338
|
-
}
|
|
339
|
-
codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step);
|
|
340
|
-
await recorder.promise();
|
|
341
|
-
debugLog('Expectation executed successfully');
|
|
342
|
-
// Get current state from state manager
|
|
343
|
-
const currentState = this.stateManager.getCurrentState();
|
|
344
|
-
if (currentState) {
|
|
345
|
-
// Create ActionResult from current state for compatibility
|
|
346
|
-
this.actionResult = new ActionResult({
|
|
347
|
-
url: currentState.fullUrl || '',
|
|
348
|
-
title: currentState.title,
|
|
349
|
-
timestamp: currentState.timestamp,
|
|
350
|
-
html: '', // Empty HTML for expectation state
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
return this;
|
|
354
|
-
}
|
|
355
|
-
catch (err) {
|
|
356
|
-
tag('error').log('Expectation failed:', errorToString(err));
|
|
357
|
-
this.lastError = err;
|
|
358
|
-
await recorder.reset();
|
|
359
|
-
await recorder.start();
|
|
360
|
-
debugLog('Expectation failed:', errorToString(err));
|
|
361
|
-
}
|
|
362
|
-
finally {
|
|
363
|
-
clearActivity();
|
|
364
|
-
}
|
|
365
|
-
return this;
|
|
366
|
-
}
|
|
367
|
-
async waitForInteraction() {
|
|
368
|
-
// start with basic approach
|
|
369
|
-
await this.actor.wait(0.5);
|
|
370
|
-
return this;
|
|
371
|
-
}
|
|
372
|
-
async attempt(codeBlock, originalMessage, experience = true) {
|
|
315
|
+
async attempt(codeBlock, originalMessage) {
|
|
316
|
+
const wasInFrame = !!this.playwrightHelper.frame;
|
|
373
317
|
try {
|
|
374
318
|
debugLog('Resolution attempt...');
|
|
375
319
|
setActivity('🦾 Acting in browser...', 'action');
|
|
376
320
|
if (!this.actionResult) {
|
|
377
321
|
this.actionResult = ActionResult.fromState(this.stateManager.getCurrentState());
|
|
378
322
|
}
|
|
379
|
-
const prevActionResult = this.actionResult;
|
|
380
323
|
this.lastError = null;
|
|
381
324
|
await this.execute(codeBlock);
|
|
382
|
-
if (!this.expectation && originalMessage) {
|
|
383
|
-
this.expectation = originalMessage;
|
|
384
|
-
}
|
|
385
|
-
if (!this.expectation) {
|
|
386
|
-
return true;
|
|
387
|
-
}
|
|
388
|
-
debugLog('Resolved Expectation:', this.expectation);
|
|
389
325
|
return true;
|
|
390
326
|
}
|
|
391
327
|
catch (error) {
|
|
392
328
|
this.lastError = error;
|
|
393
329
|
if (isFatalBrowserError(error))
|
|
394
330
|
throw error;
|
|
331
|
+
if (!wasInFrame)
|
|
332
|
+
await this.exitIframe().catch(() => { });
|
|
395
333
|
debugLog(`Attempt failed: ${codeBlock}: ${errorToString(error) || this.lastError?.toString()}`);
|
|
396
334
|
return false;
|
|
397
335
|
}
|
|
398
336
|
}
|
|
337
|
+
async exitIframe() {
|
|
338
|
+
if (!this.playwrightHelper.frame)
|
|
339
|
+
return;
|
|
340
|
+
debugLog('Switching to main frame');
|
|
341
|
+
await this.playwrightHelper.switchTo();
|
|
342
|
+
}
|
|
399
343
|
getActor() {
|
|
400
344
|
return this.actor;
|
|
401
345
|
}
|
|
402
|
-
setActor(actor) {
|
|
403
|
-
this.actor = actor;
|
|
404
|
-
}
|
|
405
|
-
getCurrentState() {
|
|
406
|
-
return this.actionResult;
|
|
407
|
-
}
|
|
408
346
|
getActionResult() {
|
|
409
347
|
return this.actionResult;
|
|
410
348
|
}
|
|
@@ -423,6 +361,13 @@ function errorToString(error) {
|
|
|
423
361
|
return error.message || error.toString();
|
|
424
362
|
}
|
|
425
363
|
async function captureHtml(page, frame, actor) {
|
|
364
|
+
for (const scope of [frame, page]) {
|
|
365
|
+
if (!scope?.evaluate)
|
|
366
|
+
continue;
|
|
367
|
+
const html = await scope.evaluate(captureHtmlForSnapshot).catch(() => null);
|
|
368
|
+
if (typeof html === 'string')
|
|
369
|
+
return html;
|
|
370
|
+
}
|
|
426
371
|
if (frame?.content)
|
|
427
372
|
return frame.content();
|
|
428
373
|
if (page?.content)
|
|
@@ -438,27 +383,6 @@ async function captureTitle(page, actor) {
|
|
|
438
383
|
return actor.grabTitle();
|
|
439
384
|
return '';
|
|
440
385
|
}
|
|
441
|
-
const SHADOWED_GLOBALS = ['process', 'global', 'globalThis', 'fetch', 'Bun', 'require', 'module', 'exports'];
|
|
442
|
-
export function sanitizeCodeBlock(code) {
|
|
443
|
-
return code
|
|
444
|
-
.split('\n')
|
|
445
|
-
.map((line) => line.trim())
|
|
446
|
-
.filter((line) => line.startsWith('I.') || line.startsWith('page.') || line.startsWith('await page.') || line.startsWith('await I.'))
|
|
447
|
-
.join('\n');
|
|
448
|
-
}
|
|
449
|
-
export function createSandboxedFunction(argNames, body) {
|
|
450
|
-
const fn = new Function(...argNames, ...SHADOWED_GLOBALS, `'use strict';\n${body}`);
|
|
451
|
-
return (...args) => fn(...args);
|
|
452
|
-
}
|
|
453
|
-
function hasPlaywrightCommands(code) {
|
|
454
|
-
return code.split('\n').some((line) => {
|
|
455
|
-
const trimmed = line.trim();
|
|
456
|
-
return trimmed.startsWith('page.') || trimmed.startsWith('await page.');
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
function sleep(ms) {
|
|
460
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
461
|
-
}
|
|
462
386
|
const ASSERTION_STEP_NAMES = new Set(['see', 'dontSee', 'seeElement', 'dontSeeElement', 'seeInField', 'dontSeeInField', 'seeInCurrentUrl', 'dontSeeInCurrentUrl']);
|
|
463
387
|
const attachStepLogger = (target, assertionsTarget) => {
|
|
464
388
|
const listener = (step, error) => {
|
package/dist/src/ai/agent.d.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import type { RequestStore } from '../api/request-store.js';
|
|
2
|
+
import type { ExplorbotConfig } from '../config.js';
|
|
3
|
+
import type Explorer from '../explorer.js';
|
|
4
|
+
import type { KnowledgeTracker } from '../knowledge-tracker.js';
|
|
5
|
+
import type { PlaywrightRecorder } from '../playwright-recorder.js';
|
|
6
|
+
import type { StateManager } from '../state-manager.js';
|
|
7
|
+
import type { AIProvider } from './provider.js';
|
|
1
8
|
export interface Agent {
|
|
2
9
|
emoji?: string;
|
|
3
10
|
}
|
|
11
|
+
export interface AgentDeps {
|
|
12
|
+
explorer: Explorer;
|
|
13
|
+
ai: AIProvider;
|
|
14
|
+
config: ExplorbotConfig;
|
|
15
|
+
stateManager: StateManager;
|
|
16
|
+
knowledgeTracker: KnowledgeTracker;
|
|
17
|
+
requestStore: RequestStore;
|
|
18
|
+
playwrightRecorder: PlaywrightRecorder;
|
|
19
|
+
}
|
|
20
|
+
export type ToolDeps = Pick<AgentDeps, 'explorer' | 'stateManager' | 'ai'>;
|
|
@@ -60,7 +60,7 @@ export function WithIdleMode(Base) {
|
|
|
60
60
|
if (!action || action === 'replace') {
|
|
61
61
|
plan.tests.length = 0;
|
|
62
62
|
}
|
|
63
|
-
const currentUrl = ctx.explorBot.
|
|
63
|
+
const currentUrl = ctx.explorBot.stateManager().getCurrentState()?.url || '';
|
|
64
64
|
for (const testInput of tests) {
|
|
65
65
|
const priority = testInput.priority || 'normal';
|
|
66
66
|
const expected = testInput.expected?.length ? testInput.expected : [];
|
|
@@ -144,7 +144,7 @@ export function WithTestMode(Base) {
|
|
|
144
144
|
states = t.states;
|
|
145
145
|
}
|
|
146
146
|
else {
|
|
147
|
-
const stateManager = ctx.explorBot.
|
|
147
|
+
const stateManager = ctx.explorBot.stateManager();
|
|
148
148
|
const history = stateManager.getStateHistory();
|
|
149
149
|
const seen = new Set();
|
|
150
150
|
states = history
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { tool } from 'ai';
|
|
2
2
|
import dedent from 'dedent';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { ActionResult } from "../../action-result.js";
|
|
5
4
|
import { actionRule, locatorRule, sectionContextRule } from "../rules.js";
|
|
6
5
|
import { createAgentTools, createCodeceptJSTools } from "../tools.js";
|
|
7
6
|
import { debugLog } from "./mixin.js";
|
|
@@ -9,17 +8,12 @@ export function WithWebMode(Base) {
|
|
|
9
8
|
return class extends Base {
|
|
10
9
|
webModeTools(ctx) {
|
|
11
10
|
const explorer = ctx.explorBot.getExplorer();
|
|
12
|
-
const
|
|
13
|
-
const codeceptTools = createCodeceptJSTools(
|
|
11
|
+
const toolDeps = { explorer, stateManager: ctx.explorBot.stateManager(), ai: ctx.explorBot.getProvider() };
|
|
12
|
+
const codeceptTools = createCodeceptJSTools(toolDeps, ctx.task);
|
|
14
13
|
const agentTools = createAgentTools({
|
|
15
|
-
|
|
14
|
+
...toolDeps,
|
|
16
15
|
researcher: ctx.explorBot.agentResearcher(),
|
|
17
16
|
navigator: ctx.explorBot.agentNavigator(),
|
|
18
|
-
experienceTracker: stateManager.getExperienceTracker(),
|
|
19
|
-
getState: () => {
|
|
20
|
-
const state = stateManager.getCurrentState();
|
|
21
|
-
return state ? ActionResult.fromState(state) : null;
|
|
22
|
-
},
|
|
23
17
|
});
|
|
24
18
|
const { see, context, visualClick, learnExperience } = agentTools;
|
|
25
19
|
return {
|
|
@@ -32,7 +26,7 @@ export function WithWebMode(Base) {
|
|
|
32
26
|
try {
|
|
33
27
|
debugLog('navigate', destination);
|
|
34
28
|
await ctx.explorBot.agentNavigator().visit(destination);
|
|
35
|
-
const stateManager = ctx.explorBot.
|
|
29
|
+
const stateManager = ctx.explorBot.stateManager();
|
|
36
30
|
const state = stateManager.getCurrentState();
|
|
37
31
|
return { success: true, url: state?.url, title: state?.title };
|
|
38
32
|
}
|
|
@@ -50,43 +44,38 @@ export function WithWebMode(Base) {
|
|
|
50
44
|
- closeTabs: Close all browser tabs except the current one
|
|
51
45
|
- reload: Reload the current page
|
|
52
46
|
- screenshot: Take a screenshot of current page
|
|
53
|
-
- recover: Recover from a closed/crashed page
|
|
54
|
-
-
|
|
55
|
-
- openFreshTab: Open a fresh tab in the current browser context
|
|
47
|
+
- recover: Recover from a closed/crashed page, escalating to a full browser restart when needed
|
|
48
|
+
- openTab: Open a fresh tab in the current browser context
|
|
56
49
|
`,
|
|
57
50
|
inputSchema: z.object({
|
|
58
|
-
action: z.enum(['status', 'evaluate', 'closeTabs', 'reload', 'screenshot', 'recover', '
|
|
51
|
+
action: z.enum(['status', 'evaluate', 'closeTabs', 'reload', 'screenshot', 'recover', 'openTab']).describe('Browser action to perform'),
|
|
59
52
|
code: z.string().optional().describe('JavaScript code for evaluate action'),
|
|
60
53
|
}),
|
|
61
54
|
execute: async ({ action, code }) => {
|
|
62
55
|
const explorer = ctx.explorBot.getExplorer();
|
|
63
56
|
if (action === 'status') {
|
|
64
|
-
const page = explorer.
|
|
57
|
+
const page = explorer.page;
|
|
65
58
|
const pages = page?.context?.().pages?.() || [];
|
|
66
59
|
return {
|
|
67
60
|
success: true,
|
|
68
61
|
hasPage: !!page,
|
|
69
|
-
isClosed: page
|
|
70
|
-
url: page
|
|
71
|
-
title: page
|
|
62
|
+
isClosed: !page,
|
|
63
|
+
url: page ? await page.url() : null,
|
|
64
|
+
title: page ? await page.title().catch(() => null) : null,
|
|
72
65
|
tabs: pages.length,
|
|
73
66
|
};
|
|
74
67
|
}
|
|
75
68
|
if (action === 'recover') {
|
|
76
|
-
const
|
|
77
|
-
return { success:
|
|
69
|
+
const { ok } = await explorer.recover();
|
|
70
|
+
return { success: ok, message: ok ? 'Browser page recovered' : 'Browser recovery failed' };
|
|
78
71
|
}
|
|
79
|
-
if (action === '
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
if (action === 'openFreshTab') {
|
|
84
|
-
await ctx.explorBot.openFreshTab();
|
|
85
|
-
const state = explorer.getStateManager().getCurrentState();
|
|
72
|
+
if (action === 'openTab') {
|
|
73
|
+
await ctx.explorBot.openTab();
|
|
74
|
+
const state = ctx.explorBot.stateManager().getCurrentState();
|
|
86
75
|
return { success: true, url: state?.url, title: state?.title };
|
|
87
76
|
}
|
|
88
|
-
const page = explorer.
|
|
89
|
-
if (!page
|
|
77
|
+
const page = explorer.page;
|
|
78
|
+
if (!page)
|
|
90
79
|
return { success: false, message: 'No browser page available. Try browser({ action: "recover" }) first.' };
|
|
91
80
|
if (action === 'evaluate') {
|
|
92
81
|
if (!code)
|
package/dist/src/ai/captain.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExperienceTracker } from '../experience-tracker.js';
|
|
1
|
+
import type { ExperienceTracker } from '../experience-tracker.js';
|
|
2
2
|
import type { ExplorBot } from '../explorbot.js';
|
|
3
3
|
import type { WebPageState } from '../state-manager.js';
|
|
4
4
|
import { Task, Test } from '../test-plan.js';
|
|
@@ -55,8 +55,6 @@ export declare class Captain extends CaptainBase implements Agent {
|
|
|
55
55
|
systemPrompt(): string;
|
|
56
56
|
resetConversation(): Conversation;
|
|
57
57
|
ensureConversation(): Conversation;
|
|
58
|
-
getConversation(): Conversation | null;
|
|
59
|
-
cleanConversation(): void;
|
|
60
58
|
getPageContext(): Promise<string>;
|
|
61
59
|
planSummary(): string;
|
|
62
60
|
reinjectContextIfNeeded(conversation: Conversation, currentState: WebPageState): Promise<void>;
|
package/dist/src/ai/captain.js
CHANGED
|
@@ -2,8 +2,8 @@ import { tool } from 'ai';
|
|
|
2
2
|
import dedent from 'dedent';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from '../action-result.js';
|
|
5
|
-
import { ExperienceTracker } from '../experience-tracker.js';
|
|
6
5
|
import { Task } from "../test-plan.js";
|
|
6
|
+
import { formatHeadings } from "../utils/context-formatter.js";
|
|
7
7
|
import { HooksRunner } from "../utils/hooks-runner.js";
|
|
8
8
|
import { startLogCapture, stopLogCapture, tag } from '../utils/logger.js';
|
|
9
9
|
import { loop } from '../utils/loop.js';
|
|
@@ -28,7 +28,7 @@ export class Captain extends CaptainBase {
|
|
|
28
28
|
constructor(explorBot) {
|
|
29
29
|
super();
|
|
30
30
|
this.explorBot = explorBot;
|
|
31
|
-
this.experienceTracker =
|
|
31
|
+
this.experienceTracker = explorBot.experienceTracker();
|
|
32
32
|
}
|
|
33
33
|
setCommandExecutor(fn, descriptions) {
|
|
34
34
|
this.commandExecutor = fn;
|
|
@@ -37,7 +37,7 @@ export class Captain extends CaptainBase {
|
|
|
37
37
|
getHooksRunner() {
|
|
38
38
|
if (!this.hooksRunner) {
|
|
39
39
|
const explorer = this.explorBot.getExplorer();
|
|
40
|
-
this.hooksRunner = new HooksRunner(explorer,
|
|
40
|
+
this.hooksRunner = new HooksRunner(explorer, this.explorBot.getConfig());
|
|
41
41
|
}
|
|
42
42
|
return this.hooksRunner;
|
|
43
43
|
}
|
|
@@ -48,7 +48,7 @@ export class Captain extends CaptainBase {
|
|
|
48
48
|
return this.experienceTracker;
|
|
49
49
|
}
|
|
50
50
|
getKnowledgeTracker() {
|
|
51
|
-
return this.explorBot.
|
|
51
|
+
return this.explorBot.knowledgeTracker();
|
|
52
52
|
}
|
|
53
53
|
getProvider() {
|
|
54
54
|
return this.explorBot.getProvider();
|
|
@@ -72,18 +72,17 @@ export class Captain extends CaptainBase {
|
|
|
72
72
|
getMode() {
|
|
73
73
|
const explorer = this.explorBot.getExplorer();
|
|
74
74
|
const activeTest = explorer.activeTest;
|
|
75
|
-
|
|
76
|
-
if (activeTest && (!page || page.isClosed?.()))
|
|
75
|
+
if (activeTest && !explorer.page)
|
|
77
76
|
return 'heal';
|
|
78
77
|
if (activeTest)
|
|
79
78
|
return 'test';
|
|
80
|
-
if (
|
|
79
|
+
if (this.explorBot.stateManager().getCurrentState())
|
|
81
80
|
return 'web';
|
|
82
81
|
return 'idle';
|
|
83
82
|
}
|
|
84
83
|
systemPrompt() {
|
|
85
84
|
const mode = this.getMode();
|
|
86
|
-
const currentUrl = this.explorBot.
|
|
85
|
+
const currentUrl = this.explorBot.stateManager().getCurrentState()?.url;
|
|
87
86
|
const customPrompt = this.explorBot.getProvider().getSystemPromptForAgent('captain', currentUrl);
|
|
88
87
|
return dedent `
|
|
89
88
|
<role>
|
|
@@ -126,30 +125,15 @@ export class Captain extends CaptainBase {
|
|
|
126
125
|
}
|
|
127
126
|
return this.conversation;
|
|
128
127
|
}
|
|
129
|
-
getConversation() {
|
|
130
|
-
return this.conversation;
|
|
131
|
-
}
|
|
132
|
-
cleanConversation() {
|
|
133
|
-
this.conversation = null;
|
|
134
|
-
tag('info').log('Conversation cleaned');
|
|
135
|
-
}
|
|
136
128
|
async getPageContext() {
|
|
137
|
-
const state = this.explorBot.
|
|
129
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
138
130
|
if (!state) {
|
|
139
131
|
return 'No page loaded';
|
|
140
132
|
}
|
|
141
133
|
const actionResult = ActionResult.fromState(state);
|
|
142
134
|
const knowledge = this.getKnowledge(actionResult);
|
|
143
135
|
const experience = this.getExperience(actionResult);
|
|
144
|
-
const headingLines =
|
|
145
|
-
if (state.h1)
|
|
146
|
-
headingLines.push(`H1: ${state.h1}`);
|
|
147
|
-
if (state.h2)
|
|
148
|
-
headingLines.push(`H2: ${state.h2}`);
|
|
149
|
-
if (state.h3)
|
|
150
|
-
headingLines.push(`H3: ${state.h3}`);
|
|
151
|
-
if (state.h4)
|
|
152
|
-
headingLines.push(`H4: ${state.h4}`);
|
|
136
|
+
const headingLines = formatHeadings(state);
|
|
153
137
|
const headingsBlock = headingLines.join('\n');
|
|
154
138
|
let pageSummary = '';
|
|
155
139
|
const cachedResearch = Researcher.getCachedResearch(state);
|
|
@@ -212,10 +196,9 @@ export class Captain extends CaptainBase {
|
|
|
212
196
|
`;
|
|
213
197
|
}
|
|
214
198
|
async reinjectContextIfNeeded(conversation, currentState) {
|
|
215
|
-
if (conversation.hasTag('
|
|
216
|
-
return
|
|
199
|
+
if (conversation.hasTag('page_aria', 5))
|
|
200
|
+
return;
|
|
217
201
|
const actionResult = ActionResult.fromState(currentState);
|
|
218
|
-
const html = await actionResult.combinedHtml();
|
|
219
202
|
const context = dedent `
|
|
220
203
|
Context:
|
|
221
204
|
|
|
@@ -227,13 +210,8 @@ export class Captain extends CaptainBase {
|
|
|
227
210
|
<page_aria>
|
|
228
211
|
${actionResult.getInteractiveARIA()}
|
|
229
212
|
</page_aria>
|
|
230
|
-
|
|
231
|
-
<page_html>
|
|
232
|
-
${html}
|
|
233
|
-
</page_html>
|
|
234
213
|
`;
|
|
235
214
|
conversation.addUserText(context);
|
|
236
|
-
return Promise.resolve();
|
|
237
215
|
}
|
|
238
216
|
coreTools(task, onDone) {
|
|
239
217
|
return {
|
|
@@ -263,15 +241,8 @@ export class Captain extends CaptainBase {
|
|
|
263
241
|
runCommand: tool({
|
|
264
242
|
description: dedent `
|
|
265
243
|
Execute a TUI command. Returns log output from command execution.
|
|
266
|
-
Use only when the user
|
|
267
|
-
|
|
268
|
-
Never run a slash command unless the user request itself starts with that slash command.
|
|
269
|
-
${this.commandDescriptions
|
|
270
|
-
.map((c) => {
|
|
271
|
-
const opts = c.options ? ` (${c.options})` : '';
|
|
272
|
-
return `${c.name} — ${c.description}${opts}`;
|
|
273
|
-
})
|
|
274
|
-
.join('\n')}
|
|
244
|
+
Use only when the user request itself starts with a slash command; pass it through verbatim.
|
|
245
|
+
Available commands: ${this.commandDescriptions.map((c) => c.name).join(', ')}
|
|
275
246
|
`,
|
|
276
247
|
inputSchema: z.object({
|
|
277
248
|
command: z.string().describe('Slash command to execute, e.g. "/research", "/plan authentication", "/test brave-fox123"'),
|
|
@@ -329,7 +300,7 @@ export class Captain extends CaptainBase {
|
|
|
329
300
|
recentToolSummary = execs.map((e) => `${e.toolName}: ${e.wasSuccessful ? 'ok' : 'fail'} ${truncateJson(e.input)}`).join('\n');
|
|
330
301
|
}
|
|
331
302
|
const pilotAnalysis = this.explorBot.agentPilot().getLastAnalysis() || '';
|
|
332
|
-
const currentUrl = this.explorBot.
|
|
303
|
+
const currentUrl = this.explorBot.stateManager().getCurrentState()?.url || '';
|
|
333
304
|
const schema = z.object({
|
|
334
305
|
action: z.enum(['inject', 'stop', 'pass', 'skip']),
|
|
335
306
|
message: z.string().describe('Message to pass to the tester or display to user'),
|
|
@@ -371,7 +342,7 @@ export class Captain extends CaptainBase {
|
|
|
371
342
|
}
|
|
372
343
|
async processExecutionError(error, activeTest) {
|
|
373
344
|
const explorer = this.explorBot.getExplorer();
|
|
374
|
-
const result = await explorer.
|
|
345
|
+
const result = await explorer.recover(error);
|
|
375
346
|
return {
|
|
376
347
|
...result,
|
|
377
348
|
message: result.recovered ? `${result.message}\nContinue the test "${activeTest.scenario}" from the restored page.` : result.message,
|
|
@@ -381,7 +352,7 @@ export class Captain extends CaptainBase {
|
|
|
381
352
|
return (this.recentToolCalls || []).some(hasBrowserCompletionEvidence);
|
|
382
353
|
}
|
|
383
354
|
async handle(input, options = {}) {
|
|
384
|
-
const stateManager = this.explorBot.
|
|
355
|
+
const stateManager = this.explorBot.stateManager();
|
|
385
356
|
const initialState = stateManager.getCurrentState();
|
|
386
357
|
const conversation = options.reset ? this.resetConversation() : this.ensureConversation();
|
|
387
358
|
let isDone = false;
|
|
@@ -441,6 +412,7 @@ export class Captain extends CaptainBase {
|
|
|
441
412
|
const result = await this.explorBot.getProvider().invokeConversation(conversation, tools, {
|
|
442
413
|
maxToolRoundtrips: 5,
|
|
443
414
|
toolChoice: 'auto',
|
|
415
|
+
stopWhen: () => isDone,
|
|
444
416
|
});
|
|
445
417
|
if (!result) {
|
|
446
418
|
stop();
|
|
@@ -5,18 +5,16 @@ export interface ToolExecution {
|
|
|
5
5
|
output: any;
|
|
6
6
|
wasSuccessful: boolean;
|
|
7
7
|
}
|
|
8
|
+
export declare function toToolExecution(toolName: string, input: any, rawOutput: any): ToolExecution;
|
|
8
9
|
export declare function toolExecutionLabel(input: Record<string, any> | undefined): string;
|
|
9
10
|
export declare class Conversation {
|
|
10
|
-
id: string;
|
|
11
11
|
messages: ModelMessage[];
|
|
12
12
|
model: any;
|
|
13
|
-
telemetryFunctionId?: string;
|
|
14
13
|
protectedPrefixCount: number;
|
|
15
14
|
autoTrimRules: Map<string, number>;
|
|
16
|
-
constructor(messages?: ModelMessage[], model?: any
|
|
15
|
+
constructor(messages?: ModelMessage[], model?: any);
|
|
17
16
|
protectPrefix(count: number): void;
|
|
18
17
|
addUserText(text: string): void;
|
|
19
|
-
addUserImage(image: string): void;
|
|
20
18
|
addAssistantText(text: string): void;
|
|
21
19
|
getLastMessage(): string;
|
|
22
20
|
clone(): Conversation;
|
|
@@ -26,6 +24,5 @@ export declare class Conversation {
|
|
|
26
24
|
markLastMessageCacheable(): void;
|
|
27
25
|
hasTag(tagName: string, lastN?: number): boolean;
|
|
28
26
|
applyAutoTrim(text: string): string;
|
|
29
|
-
generateId(): string;
|
|
30
27
|
getToolExecutions(): ToolExecution[];
|
|
31
28
|
}
|