explorbot 0.2.0 → 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 +21 -5
- package/bin/explorbot-cli.ts +29 -7
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +5 -2
- package/boat/doc-collector/src/ai/tools.ts +12 -11
- package/boat/doc-collector/src/cli.ts +1 -6
- package/boat/doc-collector/src/docbot.ts +3 -3
- package/boat/doc-collector/src/screenshots.ts +2 -2
- package/dist/bin/explorbot-cli.js +26 -6
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +4 -2
- package/dist/boat/doc-collector/src/ai/tools.js +11 -11
- package/dist/boat/doc-collector/src/cli.js +1 -5
- package/dist/boat/doc-collector/src/docbot.js +3 -3
- package/dist/boat/doc-collector/src/screenshots.js +2 -2
- package/dist/models.json +30 -0
- package/dist/package.json +5 -2
- package/dist/src/action.d.ts +11 -3
- package/dist/src/action.js +22 -5
- 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 -22
- package/dist/src/ai/captain.js +13 -26
- package/dist/src/ai/driller.d.ts +2 -13
- package/dist/src/ai/driller.js +23 -38
- package/dist/src/ai/historian/codeceptjs.js +1 -1
- package/dist/src/ai/historian/playwright.d.ts +3 -2
- package/dist/src/ai/historian/playwright.js +1 -1
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -2
- package/dist/src/ai/historian.d.ts +5 -4
- package/dist/src/ai/navigator.d.ts +6 -2
- package/dist/src/ai/navigator.js +26 -22
- package/dist/src/ai/pilot.d.ts +13 -6
- package/dist/src/ai/pilot.js +49 -44
- package/dist/src/ai/planner.d.ts +3 -2
- package/dist/src/ai/planner.js +19 -17
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.d.ts +3 -2
- package/dist/src/ai/quartermaster.js +4 -4
- package/dist/src/ai/rerunner.d.ts +2 -11
- package/dist/src/ai/rerunner.js +9 -22
- 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 +6 -6
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- package/dist/src/ai/researcher/sections.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +2 -2
- package/dist/src/ai/researcher.js +29 -54
- package/dist/src/ai/task-agent.d.ts +18 -5
- package/dist/src/ai/task-agent.js +35 -6
- package/dist/src/ai/tester.d.ts +6 -12
- package/dist/src/ai/tester.js +37 -51
- package/dist/src/ai/tools.d.ts +11 -10
- package/dist/src/ai/tools.js +24 -36
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +7 -5
- 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 +1 -1
- package/dist/src/commands/drill-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/learn-command.js +4 -4
- 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/App.js +1 -1
- package/dist/src/config.d.ts +17 -0
- package/dist/src/config.js +144 -10
- package/dist/src/experience-tracker.js +1 -1
- package/dist/src/explorbot.d.ts +17 -8
- package/dist/src/explorbot.js +74 -36
- package/dist/src/explorer.d.ts +66 -91
- package/dist/src/explorer.js +326 -567
- package/dist/src/state-manager.d.ts +6 -1
- package/dist/src/state-manager.js +1 -0
- package/dist/src/utils/hooks-runner.js +2 -4
- 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 +1 -0
- package/dist/src/utils/web-element.js +29 -0
- package/models.json +30 -0
- package/package.json +5 -2
- package/src/action.ts +25 -5
- 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 -23
- package/src/ai/captain.ts +12 -25
- package/src/ai/driller.ts +50 -67
- package/src/ai/historian/codeceptjs.ts +1 -1
- package/src/ai/historian/playwright.ts +4 -3
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian.ts +3 -2
- package/src/ai/navigator.ts +30 -25
- package/src/ai/pilot.ts +55 -47
- package/src/ai/planner.ts +22 -20
- package/src/ai/provider.ts +0 -1
- package/src/ai/quartermaster.ts +5 -4
- package/src/ai/rerunner.ts +10 -29
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +8 -6
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/sections.ts +4 -2
- package/src/ai/researcher.ts +32 -61
- package/src/ai/task-agent.ts +45 -11
- package/src/ai/tester.ts +40 -57
- package/src/ai/tools.ts +32 -53
- package/src/command-handler.ts +1 -1
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +7 -5
- 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 +1 -1
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/learn-command.ts +4 -4
- 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/App.tsx +1 -1
- package/src/config.ts +173 -11
- package/src/experience-tracker.ts +1 -1
- package/src/explorbot.ts +78 -39
- package/src/explorer.ts +389 -628
- package/src/state-manager.ts +7 -1
- package/src/utils/hooks-runner.ts +2 -4
- 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 +30 -0
package/src/ai/driller.ts
CHANGED
|
@@ -3,13 +3,9 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from '../action-result.ts';
|
|
5
5
|
import { setActivity } from '../activity.ts';
|
|
6
|
-
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
7
|
-
import type Explorer from '../explorer.ts';
|
|
8
|
-
import type { KnowledgeTracker } from '../knowledge-tracker.ts';
|
|
9
6
|
import { Observability } from '../observability.ts';
|
|
10
7
|
import { Plan, Test, TestResult } from '../test-plan.ts';
|
|
11
8
|
import { collectInteractiveNodes } from '../utils/aria.ts';
|
|
12
|
-
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
13
9
|
import {
|
|
14
10
|
EXPLORBOT_ATTRS,
|
|
15
11
|
HTML_COMPOSITE_AREA_HINTS,
|
|
@@ -26,8 +22,10 @@ import {
|
|
|
26
22
|
} from '../utils/html.ts';
|
|
27
23
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
28
24
|
import { loop, pause } from '../utils/loop.ts';
|
|
25
|
+
import { annotatePageElements } from '../utils/web-annotate.ts';
|
|
26
|
+
import { eidxInContainer } from '../utils/web-eidx.ts';
|
|
29
27
|
import { WebElement } from '../utils/web-element.ts';
|
|
30
|
-
import type { Agent } from './agent.ts';
|
|
28
|
+
import type { Agent, AgentDeps } from './agent.ts';
|
|
31
29
|
import type { Navigator } from './navigator.ts';
|
|
32
30
|
import type { Provider } from './provider.ts';
|
|
33
31
|
import { drillLocatorRule } from './rules.ts';
|
|
@@ -79,10 +77,7 @@ interface DrillOptions {
|
|
|
79
77
|
export class Driller extends TaskAgent implements Agent {
|
|
80
78
|
protected readonly ACTION_TOOLS = ['click', 'pressKey', 'form'];
|
|
81
79
|
emoji = 'D';
|
|
82
|
-
private explorer: Explorer;
|
|
83
|
-
private provider: Provider;
|
|
84
80
|
private navigator: Navigator;
|
|
85
|
-
private hooksRunner: HooksRunner;
|
|
86
81
|
private currentPlan?: Plan;
|
|
87
82
|
private allResults: InteractionResult[] = [];
|
|
88
83
|
private verifiedAction: { componentId: string; toolName: string; code?: string; canonicalCode?: string } | null = null;
|
|
@@ -90,32 +85,17 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
90
85
|
|
|
91
86
|
MAX_COMPONENT_ITERATIONS = 12;
|
|
92
87
|
|
|
93
|
-
constructor(
|
|
94
|
-
super();
|
|
95
|
-
this.explorer = explorer;
|
|
96
|
-
this.provider = provider;
|
|
88
|
+
constructor(deps: AgentDeps, navigator: Navigator) {
|
|
89
|
+
super(deps);
|
|
97
90
|
this.navigator = navigator;
|
|
98
|
-
this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
|
|
99
91
|
}
|
|
100
92
|
|
|
101
93
|
protected getNavigator(): Navigator {
|
|
102
94
|
return this.navigator;
|
|
103
95
|
}
|
|
104
96
|
|
|
105
|
-
protected getExperienceTracker(): ExperienceTracker {
|
|
106
|
-
return this.explorer.getStateManager().getExperienceTracker();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
protected getKnowledgeTracker(): KnowledgeTracker {
|
|
110
|
-
return this.explorer.getKnowledgeTracker();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
protected getProvider(): Provider {
|
|
114
|
-
return this.provider;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
97
|
getSystemMessage(component?: ComponentInfo): string {
|
|
118
|
-
const currentUrl = this.
|
|
98
|
+
const currentUrl = this.stateManager.getCurrentState()?.url;
|
|
119
99
|
const customPrompt = this.provider.getSystemPromptForAgent('driller', currentUrl);
|
|
120
100
|
|
|
121
101
|
return dedent`
|
|
@@ -159,7 +139,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
159
139
|
|
|
160
140
|
async drill(opts: DrillOptions = {}): Promise<Plan> {
|
|
161
141
|
const { knowledgePath, maxComponents = 30, interactive = isInteractive() } = opts;
|
|
162
|
-
const currentState = this.
|
|
142
|
+
const currentState = this.stateManager.getCurrentState();
|
|
163
143
|
if (!currentState) throw new Error('No page state available');
|
|
164
144
|
|
|
165
145
|
const sessionName = `driller_${Date.now().toString(36)}`;
|
|
@@ -207,15 +187,15 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
207
187
|
|
|
208
188
|
private async captureAnnotatedState(): Promise<ActionResult> {
|
|
209
189
|
setActivity(`${this.emoji} Capturing annotated page state...`, 'action');
|
|
210
|
-
const action = this.explorer.
|
|
190
|
+
const action = this.explorer.action();
|
|
211
191
|
try {
|
|
212
192
|
const annotated = await Promise.race([
|
|
213
|
-
this.explorer.
|
|
193
|
+
this.explorer.withPage(annotatePageElements),
|
|
214
194
|
new Promise<never>((_, reject) => {
|
|
215
195
|
setTimeout(() => reject(new Error('annotateElements timeout')), 15000);
|
|
216
196
|
}),
|
|
217
197
|
]);
|
|
218
|
-
return action.capturePageState(
|
|
198
|
+
return action.capturePageState();
|
|
219
199
|
} catch (error) {
|
|
220
200
|
tag('warning').log(`Annotated capture failed, falling back to plain page state: ${error instanceof Error ? error.message : error}`);
|
|
221
201
|
return action.capturePageState();
|
|
@@ -226,9 +206,10 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
226
206
|
|
|
227
207
|
private async collectComponents(state: ActionResult, maxComponents: number): Promise<ComponentInfo[]> {
|
|
228
208
|
setActivity(`${this.emoji} Collecting components...`, 'action');
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
209
|
+
const webElements = await this.explorer.withPage(async (page) => {
|
|
210
|
+
const eidxList = await eidxInContainer(page, null);
|
|
211
|
+
return WebElement.fromEidxList(page, eidxList);
|
|
212
|
+
});
|
|
232
213
|
const ariaNodes = collectInteractiveNodes(state.ariaSnapshot);
|
|
233
214
|
const scored = webElements
|
|
234
215
|
.filter((element) => isDrillableElement(element))
|
|
@@ -324,7 +305,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
324
305
|
conversation.addUserText(await this.buildComponentPrompt(originalState, component));
|
|
325
306
|
|
|
326
307
|
let finished = false;
|
|
327
|
-
const actionTools = this.createVerifiedActionTools(createCodeceptJSTools(this.
|
|
308
|
+
const actionTools = this.createVerifiedActionTools(createCodeceptJSTools(this.toolDeps, test), component);
|
|
328
309
|
const tools = { ...actionTools, ...this.createDrillFlowTools(originalState, test, interactive) };
|
|
329
310
|
|
|
330
311
|
await loop(
|
|
@@ -333,7 +314,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
333
314
|
setActivity(`${this.emoji} Drilling ${component.name}...`, 'action');
|
|
334
315
|
|
|
335
316
|
if (iteration > 1) {
|
|
336
|
-
const currentState = ActionResult.fromState(this.
|
|
317
|
+
const currentState = ActionResult.fromState(this.stateManager.getCurrentState() || originalState);
|
|
337
318
|
conversation.addUserText(await this.buildContextUpdate(currentState, component));
|
|
338
319
|
if (this.pendingNestedContext) {
|
|
339
320
|
conversation.addUserText(this.pendingNestedContext);
|
|
@@ -577,7 +558,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
577
558
|
execute: async ({ reason }) => {
|
|
578
559
|
await this.restoreOriginalState(originalState, reason);
|
|
579
560
|
await this.captureAnnotatedState();
|
|
580
|
-
const currentState = this.
|
|
561
|
+
const currentState = this.stateManager.getCurrentState();
|
|
581
562
|
return { success: true, url: currentState?.url || originalState.url };
|
|
582
563
|
},
|
|
583
564
|
}),
|
|
@@ -598,9 +579,9 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
598
579
|
}
|
|
599
580
|
|
|
600
581
|
private async restoreOriginalState(originalState: ActionResult, reason: string): Promise<void> {
|
|
601
|
-
const currentState = this.
|
|
582
|
+
const currentState = this.stateManager.getCurrentState();
|
|
602
583
|
const targetUrl = originalState.fullUrl || originalState.url;
|
|
603
|
-
const action = this.explorer.
|
|
584
|
+
const action = this.explorer.action();
|
|
604
585
|
|
|
605
586
|
if (currentState?.url !== originalState.url) {
|
|
606
587
|
await action.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, `${reason} (restore URL)`);
|
|
@@ -665,7 +646,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
665
646
|
const overlayHtml = await this.getVisibleOverlayHtml();
|
|
666
647
|
if (!overlayHtml) return null;
|
|
667
648
|
|
|
668
|
-
const state = this.
|
|
649
|
+
const state = this.stateManager.getCurrentState();
|
|
669
650
|
if (!state) return null;
|
|
670
651
|
const currentState = ActionResult.fromState(state);
|
|
671
652
|
return dedent`
|
|
@@ -686,40 +667,42 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
686
667
|
}
|
|
687
668
|
|
|
688
669
|
private async getVisibleOverlayHtml(): Promise<string> {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
},
|
|
695
|
-
{
|
|
696
|
-
extractorSource: getVisibleOverlayHtmlExtractorSource(),
|
|
697
|
-
config: {
|
|
698
|
-
interactiveContentSelector: HTML_SELECTORS.interactiveContent,
|
|
699
|
-
limits: HTML_EXTRACTION_LIMITS,
|
|
700
|
-
overlaySelectors: HTML_SELECTORS.semanticOverlays,
|
|
701
|
-
visibilityLimits: HTML_VISIBILITY_LIMITS,
|
|
670
|
+
return this.explorer.withPage((page) =>
|
|
671
|
+
page.evaluate(
|
|
672
|
+
({ extractorSource, config }) => {
|
|
673
|
+
const extract = new Function(`return ${extractorSource}`)() as (config: any) => string;
|
|
674
|
+
return extract(config);
|
|
702
675
|
},
|
|
703
|
-
|
|
676
|
+
{
|
|
677
|
+
extractorSource: getVisibleOverlayHtmlExtractorSource(),
|
|
678
|
+
config: {
|
|
679
|
+
interactiveContentSelector: HTML_SELECTORS.interactiveContent,
|
|
680
|
+
limits: HTML_EXTRACTION_LIMITS,
|
|
681
|
+
overlaySelectors: HTML_SELECTORS.semanticOverlays,
|
|
682
|
+
visibilityLimits: HTML_VISIBILITY_LIMITS,
|
|
683
|
+
},
|
|
684
|
+
}
|
|
685
|
+
)
|
|
704
686
|
);
|
|
705
687
|
}
|
|
706
688
|
|
|
707
689
|
private async getComponentScopeHtml(component: ComponentInfo, originalState: ActionResult): Promise<string> {
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
},
|
|
714
|
-
{
|
|
715
|
-
eidx: component.eidx,
|
|
716
|
-
extractorSource: getComponentScopeHtmlExtractorSource(),
|
|
717
|
-
config: {
|
|
718
|
-
eidxAttr: EXPLORBOT_ATTRS.eidx,
|
|
719
|
-
interactiveControlSelector: HTML_SELECTORS.interactiveControl,
|
|
720
|
-
limits: HTML_EXTRACTION_LIMITS,
|
|
690
|
+
const scopedHtml = await this.explorer.withPage((page) =>
|
|
691
|
+
page.evaluate(
|
|
692
|
+
({ eidx, extractorSource, config }) => {
|
|
693
|
+
const extract = new Function(`return ${extractorSource}`)() as (eidx: string, config: any) => string;
|
|
694
|
+
return extract(eidx, config);
|
|
721
695
|
},
|
|
722
|
-
|
|
696
|
+
{
|
|
697
|
+
eidx: component.eidx,
|
|
698
|
+
extractorSource: getComponentScopeHtmlExtractorSource(),
|
|
699
|
+
config: {
|
|
700
|
+
eidxAttr: EXPLORBOT_ATTRS.eidx,
|
|
701
|
+
interactiveControlSelector: HTML_SELECTORS.interactiveControl,
|
|
702
|
+
limits: HTML_EXTRACTION_LIMITS,
|
|
703
|
+
},
|
|
704
|
+
}
|
|
705
|
+
)
|
|
723
706
|
);
|
|
724
707
|
|
|
725
708
|
if (scopedHtml) return scopedHtml;
|
|
@@ -6,8 +6,8 @@ import type { StateManager } from '../../state-manager.ts';
|
|
|
6
6
|
import type { Plan } from '../../test-plan.ts';
|
|
7
7
|
import { tag } from '../../utils/logger.ts';
|
|
8
8
|
import { relativeToCwd } from '../../utils/next-steps.ts';
|
|
9
|
-
import { safeFilename } from '../../utils/strings.ts';
|
|
10
9
|
import { CODECEPT_TOOLS, isNonReusableCode, stripComments } from '../../utils/step-analyzer.ts';
|
|
10
|
+
import { safeFilename } from '../../utils/strings.ts';
|
|
11
11
|
import type { Conversation } from '../conversation.ts';
|
|
12
12
|
import { ASSERTION_TOOLS } from '../tools.ts';
|
|
13
13
|
import type { Constructor } from './mixin.ts';
|
|
@@ -2,13 +2,14 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ActionResult } from '../../action-result.ts';
|
|
4
4
|
import { ConfigParser } from '../../config.ts';
|
|
5
|
-
import type
|
|
5
|
+
import type Explorer from '../../explorer.ts';
|
|
6
6
|
import { type PlaywrightRecorder, type TraceCall, renderAssertion, renderCall } from '../../playwright-recorder.ts';
|
|
7
|
+
import type { StateManager } from '../../state-manager.ts';
|
|
7
8
|
import type { Plan } from '../../test-plan.ts';
|
|
8
9
|
import { tag } from '../../utils/logger.ts';
|
|
9
10
|
import { relativeToCwd } from '../../utils/next-steps.ts';
|
|
10
|
-
import { safeFilename } from '../../utils/strings.ts';
|
|
11
11
|
import { CODECEPT_TOOLS } from '../../utils/step-analyzer.ts';
|
|
12
|
+
import { safeFilename } from '../../utils/strings.ts';
|
|
12
13
|
import type { Conversation } from '../conversation.ts';
|
|
13
14
|
import { ASSERTION_TOOLS } from '../tools.ts';
|
|
14
15
|
import type { Constructor } from './mixin.ts';
|
|
@@ -23,7 +24,7 @@ export interface PlaywrightMethods {
|
|
|
23
24
|
|
|
24
25
|
export function WithPlaywright<T extends Constructor>(Base: T) {
|
|
25
26
|
return class extends Base {
|
|
26
|
-
declare playwright: { recorder: PlaywrightRecorder;
|
|
27
|
+
declare playwright: { recorder: PlaywrightRecorder; explorer: Explorer } | undefined;
|
|
27
28
|
declare savedFiles: Set<string>;
|
|
28
29
|
declare stateManager?: StateManager;
|
|
29
30
|
|
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import * as codeceptjs from 'codeceptjs';
|
|
5
5
|
import { outputPath } from '../../config.ts';
|
|
6
6
|
import type { ExplorbotConfig } from '../../config.ts';
|
|
7
|
+
import type Explorer from '../../explorer.ts';
|
|
7
8
|
import type { PlaywrightRecorder } from '../../playwright-recorder.ts';
|
|
8
9
|
import { tag } from '../../utils/logger.ts';
|
|
9
10
|
import { relativeToCwd } from '../../utils/next-steps.ts';
|
|
@@ -22,7 +23,7 @@ export function WithScreencast<T extends Constructor>(Base: T) {
|
|
|
22
23
|
return class extends Base {
|
|
23
24
|
declare config: ExplorbotConfig | undefined;
|
|
24
25
|
declare savedFiles: Set<string>;
|
|
25
|
-
declare playwright: { recorder: PlaywrightRecorder;
|
|
26
|
+
declare playwright: { recorder: PlaywrightRecorder; explorer: Explorer } | undefined;
|
|
26
27
|
|
|
27
28
|
private screencastPage: any = null;
|
|
28
29
|
private screencastActive = false;
|
|
@@ -41,7 +42,7 @@ export function WithScreencast<T extends Constructor>(Base: T) {
|
|
|
41
42
|
attachScreencast(): void {
|
|
42
43
|
if (this.screencastListenersInstalled) return;
|
|
43
44
|
if (!this.config?.ai?.agents?.historian?.screencast) return;
|
|
44
|
-
if (!this.playwright?.
|
|
45
|
+
if (!this.playwright?.explorer) return;
|
|
45
46
|
|
|
46
47
|
this.onTestBefore = (test: any) => {
|
|
47
48
|
void this.startScreencast(test);
|
|
@@ -62,7 +63,7 @@ export function WithScreencast<T extends Constructor>(Base: T) {
|
|
|
62
63
|
|
|
63
64
|
private async startScreencast(test: any): Promise<void> {
|
|
64
65
|
if (this.screencastActive) return;
|
|
65
|
-
const page = this.playwright?.
|
|
66
|
+
const page = this.playwright?.explorer?.page;
|
|
66
67
|
if (!page?.screencast?.start) return;
|
|
67
68
|
|
|
68
69
|
const task = test?._explorbotTest;
|
package/src/ai/historian.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import type { ExplorbotConfig } from '../config.ts';
|
|
3
3
|
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
4
|
+
import type Explorer from '../explorer.ts';
|
|
4
5
|
import type { PlaywrightRecorder } from '../playwright-recorder.ts';
|
|
5
6
|
import type { Reporter } from '../reporter.ts';
|
|
6
7
|
import type { StateManager } from '../state-manager.ts';
|
|
@@ -23,10 +24,10 @@ export class Historian extends HistorianBase {
|
|
|
23
24
|
declare reporter: Reporter | undefined;
|
|
24
25
|
declare stateManager: StateManager | undefined;
|
|
25
26
|
declare config: ExplorbotConfig | undefined;
|
|
26
|
-
declare playwright: { recorder: PlaywrightRecorder;
|
|
27
|
+
declare playwright: { recorder: PlaywrightRecorder; explorer: Explorer } | undefined;
|
|
27
28
|
declare savedFiles: Set<string>;
|
|
28
29
|
|
|
29
|
-
constructor(provider: Provider, experienceTracker: ExperienceTracker, reporter?: Reporter, stateManager?: StateManager, config?: ExplorbotConfig, playwright?: { recorder: PlaywrightRecorder;
|
|
30
|
+
constructor(provider: Provider, experienceTracker: ExperienceTracker, reporter?: Reporter, stateManager?: StateManager, config?: ExplorbotConfig, playwright?: { recorder: PlaywrightRecorder; explorer: Explorer }) {
|
|
30
31
|
super();
|
|
31
32
|
this.provider = provider;
|
|
32
33
|
this.experienceTracker = experienceTracker;
|
package/src/ai/navigator.ts
CHANGED
|
@@ -3,17 +3,18 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from '../action-result.js';
|
|
5
5
|
import type Action from '../action.ts';
|
|
6
|
+
import type { ExplorbotConfig } from '../config.ts';
|
|
6
7
|
import type { ExperienceTracker } from '../experience-tracker.js';
|
|
7
8
|
import Explorer from '../explorer.ts';
|
|
8
9
|
import type { KnowledgeTracker } from '../knowledge-tracker.js';
|
|
9
|
-
import { normalizeUrl } from '../state-manager.js';
|
|
10
|
+
import { type StateManager, normalizeUrl } from '../state-manager.js';
|
|
10
11
|
import { extractCodeBlocks } from '../utils/code-extractor.js';
|
|
11
12
|
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
12
13
|
import { createDebug, pluralize, tag } from '../utils/logger.js';
|
|
13
14
|
import { loop, pause } from '../utils/loop.js';
|
|
14
15
|
import { RulesLoader } from '../utils/rules-loader.ts';
|
|
15
16
|
import { extractStatePath } from '../utils/url-matcher.js';
|
|
16
|
-
import type { Agent } from './agent.js';
|
|
17
|
+
import type { Agent, AgentDeps } from './agent.js';
|
|
17
18
|
import type { Conversation } from './conversation.js';
|
|
18
19
|
import type { Provider } from './provider.js';
|
|
19
20
|
import { Researcher } from './researcher.ts';
|
|
@@ -70,25 +71,29 @@ class Navigator implements Agent {
|
|
|
70
71
|
</rules>
|
|
71
72
|
`;
|
|
72
73
|
private explorer: Explorer;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
74
|
+
private config: ExplorbotConfig;
|
|
75
|
+
private stateManager: StateManager;
|
|
76
|
+
|
|
77
|
+
constructor(deps: AgentDeps) {
|
|
78
|
+
this.provider = deps.ai;
|
|
79
|
+
this.explorer = deps.explorer;
|
|
80
|
+
this.config = deps.config;
|
|
81
|
+
this.stateManager = deps.stateManager;
|
|
82
|
+
this.knowledgeTracker = deps.knowledgeTracker;
|
|
83
|
+
this.experienceTracker = deps.stateManager.getExperienceTracker();
|
|
84
|
+
this.hooksRunner = new HooksRunner(deps.explorer, deps.config);
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
private get verifyAttempts(): number {
|
|
83
|
-
return this.
|
|
88
|
+
return this.config.ai?.agents?.navigator?.verifyAttempts ?? 3;
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
private get verifyTimeout(): number {
|
|
87
|
-
return this.
|
|
92
|
+
return this.config.ai?.agents?.navigator?.verifyTimeout ?? 1500;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
private getBaseOrigin(): string | null {
|
|
91
|
-
const baseUrl = this.
|
|
96
|
+
const baseUrl = this.config.playwright.url;
|
|
92
97
|
try {
|
|
93
98
|
return new URL(baseUrl).origin;
|
|
94
99
|
} catch {
|
|
@@ -133,12 +138,12 @@ class Navigator implements Agent {
|
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
async visit(url: string): Promise<void> {
|
|
136
|
-
return this.
|
|
141
|
+
return this.visitOnce(url);
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
private async visitOnce(url: string): Promise<void> {
|
|
140
145
|
try {
|
|
141
|
-
const action = this.explorer.
|
|
146
|
+
const action = this.explorer.action();
|
|
142
147
|
|
|
143
148
|
await action.execute(`I.amOnPage('${url}')`);
|
|
144
149
|
|
|
@@ -171,7 +176,7 @@ class Navigator implements Agent {
|
|
|
171
176
|
throw new Error(`Navigation to ${url} failed: ${action.lastError?.message}`);
|
|
172
177
|
}
|
|
173
178
|
}
|
|
174
|
-
await this.explorer.
|
|
179
|
+
await this.explorer.capture({ screenshot: true });
|
|
175
180
|
await this.hooksRunner.runAfterHook('navigator', url);
|
|
176
181
|
} catch (error) {
|
|
177
182
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -188,7 +193,7 @@ class Navigator implements Agent {
|
|
|
188
193
|
tag('info').log('AI Navigator resolving state at', actionResult.url);
|
|
189
194
|
debugLog('Resolution message:', message);
|
|
190
195
|
|
|
191
|
-
const action = opts?.action ?? this.explorer.
|
|
196
|
+
const action = opts?.action ?? this.explorer.action();
|
|
192
197
|
const expectedUrl = opts?.expectedUrl;
|
|
193
198
|
|
|
194
199
|
const knowledge = this.knowledgeTracker.renderRelevantKnowledge(actionResult);
|
|
@@ -341,7 +346,7 @@ class Navigator implements Agent {
|
|
|
341
346
|
codeBlockIndex++;
|
|
342
347
|
totalAttempts++;
|
|
343
348
|
|
|
344
|
-
await
|
|
349
|
+
await action.exitIframe();
|
|
345
350
|
|
|
346
351
|
const prevActionResult = action.actionResult ?? actionResult;
|
|
347
352
|
const prevHash = prevActionResult.getStateHash();
|
|
@@ -373,7 +378,7 @@ class Navigator implements Agent {
|
|
|
373
378
|
// URL did not transition to expectedUrl within timeout
|
|
374
379
|
}
|
|
375
380
|
}
|
|
376
|
-
const freshState = await this.explorer.
|
|
381
|
+
const freshState = await this.explorer.capture();
|
|
377
382
|
const currentUrl = /^https?:\/\//i.test(expectedUrl) ? freshState.fullUrl || freshState.url || '' : freshState.url || '';
|
|
378
383
|
const urlMatches = this.isSameExpectedOrigin(expectedUrl, action.stateManager) && normalizeUrl(currentUrl) === normalizeUrl(expectedUrl);
|
|
379
384
|
const stateChanged = freshState.getStateHash() !== actionResult.getStateHash();
|
|
@@ -473,7 +478,7 @@ class Navigator implements Agent {
|
|
|
473
478
|
|
|
474
479
|
private buildExperienceTools(): { learnExperience: unknown } | undefined {
|
|
475
480
|
if (!this.experienceTracker) return undefined;
|
|
476
|
-
const stateManager = this.
|
|
481
|
+
const stateManager = this.stateManager;
|
|
477
482
|
const getState = () => {
|
|
478
483
|
const s = stateManager.getCurrentState();
|
|
479
484
|
return s ? ActionResult.fromState(s) : null;
|
|
@@ -482,7 +487,7 @@ class Navigator implements Agent {
|
|
|
482
487
|
}
|
|
483
488
|
|
|
484
489
|
async freeSail(opts?: { strategy?: 'deep' | 'shallow'; scope?: string; visitedUrls?: Set<string> }, actionResult?: ActionResult): Promise<{ target: string; reason: string } | null> {
|
|
485
|
-
const stateManager = this.
|
|
490
|
+
const stateManager = this.stateManager;
|
|
486
491
|
const state = stateManager.getCurrentState();
|
|
487
492
|
if (!state) {
|
|
488
493
|
return null;
|
|
@@ -690,11 +695,11 @@ class Navigator implements Agent {
|
|
|
690
695
|
const successfulCodes: string[] = [];
|
|
691
696
|
const assertionSteps: Array<{ name: string; args: any[] }> = [];
|
|
692
697
|
|
|
693
|
-
const action = this.explorer.
|
|
698
|
+
const action = this.explorer.action();
|
|
694
699
|
let failures = 0;
|
|
695
700
|
|
|
696
|
-
const page = this.explorer.
|
|
697
|
-
const originalTimeout = this.
|
|
701
|
+
const page = this.explorer.page;
|
|
702
|
+
const originalTimeout = this.config.playwright.timeout ?? 3000;
|
|
698
703
|
page?.setDefaultTimeout(this.verifyTimeout);
|
|
699
704
|
|
|
700
705
|
try {
|
|
@@ -726,7 +731,7 @@ class Navigator implements Agent {
|
|
|
726
731
|
return;
|
|
727
732
|
}
|
|
728
733
|
|
|
729
|
-
await
|
|
734
|
+
await action.exitIframe();
|
|
730
735
|
|
|
731
736
|
const verified = await action.attempt(codeBlock, message);
|
|
732
737
|
|
|
@@ -764,7 +769,7 @@ class Navigator implements Agent {
|
|
|
764
769
|
if (alreadyVerified) verified = true;
|
|
765
770
|
|
|
766
771
|
actionResult.addVerification(message, verified);
|
|
767
|
-
this.
|
|
772
|
+
this.stateManager.updateState(actionResult);
|
|
768
773
|
|
|
769
774
|
return { verified, successfulCodes, assertionSteps, totalAttempted };
|
|
770
775
|
}
|