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.
Files changed (141) hide show
  1. package/README.md +21 -5
  2. package/bin/explorbot-cli.ts +29 -7
  3. package/boat/api-tester/src/config.ts +45 -3
  4. package/boat/doc-collector/src/ai/documentarian.ts +5 -2
  5. package/boat/doc-collector/src/ai/tools.ts +12 -11
  6. package/boat/doc-collector/src/cli.ts +1 -6
  7. package/boat/doc-collector/src/docbot.ts +3 -3
  8. package/boat/doc-collector/src/screenshots.ts +2 -2
  9. package/dist/bin/explorbot-cli.js +26 -6
  10. package/dist/boat/api-tester/src/config.js +43 -4
  11. package/dist/boat/doc-collector/src/ai/documentarian.js +4 -2
  12. package/dist/boat/doc-collector/src/ai/tools.js +11 -11
  13. package/dist/boat/doc-collector/src/cli.js +1 -5
  14. package/dist/boat/doc-collector/src/docbot.js +3 -3
  15. package/dist/boat/doc-collector/src/screenshots.js +2 -2
  16. package/dist/models.json +30 -0
  17. package/dist/package.json +5 -2
  18. package/dist/src/action.d.ts +11 -3
  19. package/dist/src/action.js +22 -5
  20. package/dist/src/ai/agent.d.ts +17 -0
  21. package/dist/src/ai/captain/idle-mode.js +1 -1
  22. package/dist/src/ai/captain/test-mode.js +1 -1
  23. package/dist/src/ai/captain/web-mode.js +18 -22
  24. package/dist/src/ai/captain.js +13 -26
  25. package/dist/src/ai/driller.d.ts +2 -13
  26. package/dist/src/ai/driller.js +23 -38
  27. package/dist/src/ai/historian/codeceptjs.js +1 -1
  28. package/dist/src/ai/historian/playwright.d.ts +3 -2
  29. package/dist/src/ai/historian/playwright.js +1 -1
  30. package/dist/src/ai/historian/screencast.d.ts +2 -1
  31. package/dist/src/ai/historian/screencast.js +2 -2
  32. package/dist/src/ai/historian.d.ts +5 -4
  33. package/dist/src/ai/navigator.d.ts +6 -2
  34. package/dist/src/ai/navigator.js +26 -22
  35. package/dist/src/ai/pilot.d.ts +13 -6
  36. package/dist/src/ai/pilot.js +49 -44
  37. package/dist/src/ai/planner.d.ts +3 -2
  38. package/dist/src/ai/planner.js +19 -17
  39. package/dist/src/ai/provider.js +0 -1
  40. package/dist/src/ai/quartermaster.d.ts +3 -2
  41. package/dist/src/ai/quartermaster.js +4 -4
  42. package/dist/src/ai/rerunner.d.ts +2 -11
  43. package/dist/src/ai/rerunner.js +9 -22
  44. package/dist/src/ai/researcher/coordinates.js +4 -3
  45. package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
  46. package/dist/src/ai/researcher/deep-analysis.js +6 -6
  47. package/dist/src/ai/researcher/locators.d.ts +9 -2
  48. package/dist/src/ai/researcher/locators.js +65 -8
  49. package/dist/src/ai/researcher/sections.d.ts +2 -0
  50. package/dist/src/ai/researcher/sections.js +2 -2
  51. package/dist/src/ai/researcher.js +29 -54
  52. package/dist/src/ai/task-agent.d.ts +18 -5
  53. package/dist/src/ai/task-agent.js +35 -6
  54. package/dist/src/ai/tester.d.ts +6 -12
  55. package/dist/src/ai/tester.js +37 -51
  56. package/dist/src/ai/tools.d.ts +11 -10
  57. package/dist/src/ai/tools.js +24 -36
  58. package/dist/src/command-handler.js +1 -1
  59. package/dist/src/commands/context-aria-command.js +1 -1
  60. package/dist/src/commands/context-command.js +7 -5
  61. package/dist/src/commands/context-data-command.js +2 -2
  62. package/dist/src/commands/context-experience-command.js +2 -2
  63. package/dist/src/commands/context-html-command.js +2 -2
  64. package/dist/src/commands/context-knowledge-command.js +1 -1
  65. package/dist/src/commands/drill-command.js +1 -1
  66. package/dist/src/commands/explore-command.js +4 -3
  67. package/dist/src/commands/freesail-command.js +2 -2
  68. package/dist/src/commands/learn-command.js +4 -4
  69. package/dist/src/commands/path-command.js +1 -1
  70. package/dist/src/commands/research-command.js +1 -1
  71. package/dist/src/commands/test-command.js +1 -1
  72. package/dist/src/components/App.js +1 -1
  73. package/dist/src/config.d.ts +17 -0
  74. package/dist/src/config.js +144 -10
  75. package/dist/src/experience-tracker.js +1 -1
  76. package/dist/src/explorbot.d.ts +17 -8
  77. package/dist/src/explorbot.js +74 -36
  78. package/dist/src/explorer.d.ts +66 -91
  79. package/dist/src/explorer.js +326 -567
  80. package/dist/src/state-manager.d.ts +6 -1
  81. package/dist/src/state-manager.js +1 -0
  82. package/dist/src/utils/hooks-runner.js +2 -4
  83. package/dist/src/utils/test-files.js +1 -1
  84. package/dist/src/utils/web-annotate.d.ts +5 -0
  85. package/dist/src/utils/web-annotate.js +58 -0
  86. package/dist/src/utils/web-eidx.d.ts +2 -0
  87. package/dist/src/utils/web-eidx.js +20 -0
  88. package/dist/src/utils/web-element.d.ts +1 -0
  89. package/dist/src/utils/web-element.js +29 -0
  90. package/models.json +30 -0
  91. package/package.json +5 -2
  92. package/src/action.ts +25 -5
  93. package/src/ai/agent.ts +20 -0
  94. package/src/ai/captain/idle-mode.ts +1 -1
  95. package/src/ai/captain/test-mode.ts +1 -1
  96. package/src/ai/captain/web-mode.ts +18 -23
  97. package/src/ai/captain.ts +12 -25
  98. package/src/ai/driller.ts +50 -67
  99. package/src/ai/historian/codeceptjs.ts +1 -1
  100. package/src/ai/historian/playwright.ts +4 -3
  101. package/src/ai/historian/screencast.ts +4 -3
  102. package/src/ai/historian.ts +3 -2
  103. package/src/ai/navigator.ts +30 -25
  104. package/src/ai/pilot.ts +55 -47
  105. package/src/ai/planner.ts +22 -20
  106. package/src/ai/provider.ts +0 -1
  107. package/src/ai/quartermaster.ts +5 -4
  108. package/src/ai/rerunner.ts +10 -29
  109. package/src/ai/researcher/coordinates.ts +4 -3
  110. package/src/ai/researcher/deep-analysis.ts +8 -6
  111. package/src/ai/researcher/locators.ts +66 -9
  112. package/src/ai/researcher/sections.ts +4 -2
  113. package/src/ai/researcher.ts +32 -61
  114. package/src/ai/task-agent.ts +45 -11
  115. package/src/ai/tester.ts +40 -57
  116. package/src/ai/tools.ts +32 -53
  117. package/src/command-handler.ts +1 -1
  118. package/src/commands/context-aria-command.ts +1 -1
  119. package/src/commands/context-command.ts +7 -5
  120. package/src/commands/context-data-command.ts +2 -2
  121. package/src/commands/context-experience-command.ts +2 -2
  122. package/src/commands/context-html-command.ts +2 -2
  123. package/src/commands/context-knowledge-command.ts +1 -1
  124. package/src/commands/drill-command.ts +1 -1
  125. package/src/commands/explore-command.ts +4 -3
  126. package/src/commands/freesail-command.ts +2 -2
  127. package/src/commands/learn-command.ts +4 -4
  128. package/src/commands/path-command.ts +1 -1
  129. package/src/commands/research-command.ts +1 -1
  130. package/src/commands/test-command.ts +1 -1
  131. package/src/components/App.tsx +1 -1
  132. package/src/config.ts +173 -11
  133. package/src/experience-tracker.ts +1 -1
  134. package/src/explorbot.ts +78 -39
  135. package/src/explorer.ts +389 -628
  136. package/src/state-manager.ts +7 -1
  137. package/src/utils/hooks-runner.ts +2 -4
  138. package/src/utils/test-files.ts +1 -1
  139. package/src/utils/web-annotate.ts +64 -0
  140. package/src/utils/web-eidx.ts +21 -0
  141. 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(explorer: Explorer, provider: Provider, navigator: Navigator) {
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.explorer.getStateManager().getCurrentState()?.url;
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.explorer.getStateManager().getCurrentState();
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.createAction();
190
+ const action = this.explorer.action();
211
191
  try {
212
192
  const annotated = await Promise.race([
213
- this.explorer.annotateElements(),
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({ ariaSnapshot: annotated.ariaSnapshot });
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 page = this.explorer.playwrightHelper.page;
230
- const eidxList = await this.explorer.getEidxInContainer(null);
231
- const webElements = await WebElement.fromEidxList(page, eidxList);
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.explorer, test), component);
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.explorer.getStateManager().getCurrentState() || originalState);
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.explorer.getStateManager().getCurrentState();
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.explorer.getStateManager().getCurrentState();
582
+ const currentState = this.stateManager.getCurrentState();
602
583
  const targetUrl = originalState.fullUrl || originalState.url;
603
- const action = this.explorer.createAction();
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.explorer.getStateManager().getCurrentState();
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
- const page = this.explorer.playwrightHelper.page;
690
- return page.evaluate(
691
- ({ extractorSource, config }) => {
692
- const extract = new Function(`return ${extractorSource}`)() as (config: any) => string;
693
- return extract(config);
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 page = this.explorer.playwrightHelper.page;
709
- const scopedHtml = await page.evaluate(
710
- ({ eidx, extractorSource, config }) => {
711
- const extract = new Function(`return ${extractorSource}`)() as (eidx: string, config: any) => string;
712
- return extract(eidx, config);
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 { StateManager } from '../../state-manager.ts';
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; helper: any } | undefined;
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; helper: any } | undefined;
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?.helper) return;
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?.helper?.page;
66
+ const page = this.playwright?.explorer?.page;
66
67
  if (!page?.screencast?.start) return;
67
68
 
68
69
  const task = test?._explorbotTest;
@@ -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; helper: any } | undefined;
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; helper: any }) {
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;
@@ -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
- constructor(explorer: Explorer, provider: Provider) {
75
- this.provider = provider;
76
- this.explorer = explorer;
77
- this.knowledgeTracker = explorer.getKnowledgeTracker();
78
- this.experienceTracker = explorer.getStateManager().getExperienceTracker();
79
- this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
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.explorer.getConfig().ai?.agents?.navigator?.verifyAttempts ?? 3;
88
+ return this.config.ai?.agents?.navigator?.verifyAttempts ?? 3;
84
89
  }
85
90
 
86
91
  private get verifyTimeout(): number {
87
- return this.explorer.getConfig().ai?.agents?.navigator?.verifyTimeout ?? 1500;
92
+ return this.config.ai?.agents?.navigator?.verifyTimeout ?? 1500;
88
93
  }
89
94
 
90
95
  private getBaseOrigin(): string | null {
91
- const baseUrl = this.explorer.getConfig().playwright.url;
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.explorer.runWithBrowserRecovery('navigator.visit', () => this.visitOnce(url));
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.createAction();
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.capturePageWithScreenshot();
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.createAction();
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 this.explorer.switchToMainFrame();
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.capturePageState();
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.explorer.getStateManager();
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.explorer.getStateManager();
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.createAction();
698
+ const action = this.explorer.action();
694
699
  let failures = 0;
695
700
 
696
- const page = this.explorer.playwrightHelper?.page;
697
- const originalTimeout = this.explorer.playwrightHelper?.options?.timeout ?? 3000;
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 this.explorer.switchToMainFrame();
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.explorer.getStateManager().updateState(actionResult);
772
+ this.stateManager.updateState(actionResult);
768
773
 
769
774
  return { verified, successfulCodes, assertionSteps, totalAttempted };
770
775
  }