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.
Files changed (222) hide show
  1. package/README.md +35 -19
  2. package/bin/explorbot-cli.ts +33 -10
  3. package/boat/api-tester/src/config.ts +45 -3
  4. package/boat/doc-collector/src/ai/documentarian.ts +26 -28
  5. package/boat/doc-collector/src/ai/tools.ts +74 -16
  6. package/boat/doc-collector/src/cli.ts +1 -6
  7. package/boat/doc-collector/src/docbot.ts +30 -9
  8. package/boat/doc-collector/src/docs-renderer.ts +19 -24
  9. package/boat/doc-collector/src/screenshots.ts +34 -2
  10. package/boat/doc-collector/src/state-diagram.ts +281 -0
  11. package/dist/bin/explorbot-cli.js +29 -9
  12. package/dist/boat/api-tester/src/config.js +43 -4
  13. package/dist/boat/doc-collector/src/ai/documentarian.js +19 -7
  14. package/dist/boat/doc-collector/src/ai/tools.js +53 -16
  15. package/dist/boat/doc-collector/src/cli.js +1 -5
  16. package/dist/boat/doc-collector/src/docbot.js +27 -8
  17. package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
  18. package/dist/boat/doc-collector/src/screenshots.js +30 -1
  19. package/dist/boat/doc-collector/src/state-diagram.js +200 -0
  20. package/dist/models.json +30 -0
  21. package/dist/package.json +23 -3
  22. package/dist/src/action-result.d.ts +8 -10
  23. package/dist/src/action-result.js +72 -164
  24. package/dist/src/action.d.ts +13 -12
  25. package/dist/src/action.js +37 -113
  26. package/dist/src/ai/agent.d.ts +17 -0
  27. package/dist/src/ai/captain/idle-mode.js +1 -1
  28. package/dist/src/ai/captain/test-mode.js +1 -1
  29. package/dist/src/ai/captain/web-mode.js +18 -29
  30. package/dist/src/ai/captain.d.ts +1 -3
  31. package/dist/src/ai/captain.js +17 -45
  32. package/dist/src/ai/conversation.d.ts +2 -5
  33. package/dist/src/ai/conversation.js +9 -28
  34. package/dist/src/ai/driller.d.ts +2 -17
  35. package/dist/src/ai/driller.js +26 -50
  36. package/dist/src/ai/experience-compactor.d.ts +2 -2
  37. package/dist/src/ai/experience-compactor.js +16 -33
  38. package/dist/src/ai/fisherman-tools.js +1 -1
  39. package/dist/src/ai/fisherman.d.ts +0 -1
  40. package/dist/src/ai/fisherman.js +0 -3
  41. package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
  42. package/dist/src/ai/historian/codeceptjs.js +4 -3
  43. package/dist/src/ai/historian/playwright.d.ts +4 -1
  44. package/dist/src/ai/historian/playwright.js +4 -3
  45. package/dist/src/ai/historian/screencast.d.ts +2 -1
  46. package/dist/src/ai/historian/screencast.js +2 -2
  47. package/dist/src/ai/historian/utils.d.ts +0 -1
  48. package/dist/src/ai/historian/utils.js +0 -1
  49. package/dist/src/ai/historian.d.ts +10 -8
  50. package/dist/src/ai/historian.js +1 -5
  51. package/dist/src/ai/navigator.d.ts +8 -6
  52. package/dist/src/ai/navigator.js +34 -65
  53. package/dist/src/ai/pilot.d.ts +13 -8
  54. package/dist/src/ai/pilot.js +53 -59
  55. package/dist/src/ai/planner.d.ts +5 -4
  56. package/dist/src/ai/planner.js +66 -52
  57. package/dist/src/ai/provider.d.ts +4 -1
  58. package/dist/src/ai/provider.js +77 -137
  59. package/dist/src/ai/quartermaster.d.ts +22 -22
  60. package/dist/src/ai/quartermaster.js +8 -14
  61. package/dist/src/ai/rerunner.d.ts +3 -13
  62. package/dist/src/ai/rerunner.js +23 -44
  63. package/dist/src/ai/researcher/cache.js +11 -18
  64. package/dist/src/ai/researcher/coordinates.js +4 -3
  65. package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
  66. package/dist/src/ai/researcher/deep-analysis.js +8 -11
  67. package/dist/src/ai/researcher/locators.d.ts +9 -2
  68. package/dist/src/ai/researcher/locators.js +65 -8
  69. package/dist/src/ai/researcher/parser.js +1 -1
  70. package/dist/src/ai/researcher/research-result.d.ts +0 -1
  71. package/dist/src/ai/researcher/research-result.js +0 -19
  72. package/dist/src/ai/researcher/sections.d.ts +2 -0
  73. package/dist/src/ai/researcher/sections.js +7 -3
  74. package/dist/src/ai/researcher.js +40 -106
  75. package/dist/src/ai/rules.d.ts +1 -0
  76. package/dist/src/ai/rules.js +22 -15
  77. package/dist/src/ai/session-analyst.js +8 -5
  78. package/dist/src/ai/task-agent.d.ts +19 -6
  79. package/dist/src/ai/task-agent.js +37 -33
  80. package/dist/src/ai/tester.d.ts +6 -14
  81. package/dist/src/ai/tester.js +46 -111
  82. package/dist/src/ai/tools.d.ts +26 -14
  83. package/dist/src/ai/tools.js +138 -132
  84. package/dist/src/command-handler.js +1 -1
  85. package/dist/src/commands/compact-command.d.ts +1 -1
  86. package/dist/src/commands/compact-command.js +6 -6
  87. package/dist/src/commands/context-aria-command.js +1 -1
  88. package/dist/src/commands/context-command.js +8 -6
  89. package/dist/src/commands/context-data-command.js +2 -2
  90. package/dist/src/commands/context-experience-command.js +2 -2
  91. package/dist/src/commands/context-html-command.js +2 -2
  92. package/dist/src/commands/context-knowledge-command.js +2 -2
  93. package/dist/src/commands/drill-command.js +1 -1
  94. package/dist/src/commands/experience-command.js +1 -1
  95. package/dist/src/commands/explore-command.js +4 -3
  96. package/dist/src/commands/freesail-command.js +2 -2
  97. package/dist/src/commands/knows-command.js +1 -1
  98. package/dist/src/commands/learn-command.js +5 -3
  99. package/dist/src/commands/path-command.js +1 -1
  100. package/dist/src/commands/research-command.js +1 -1
  101. package/dist/src/commands/test-command.js +1 -1
  102. package/dist/src/components/AddKnowledge.d.ts +2 -0
  103. package/dist/src/components/AddKnowledge.js +3 -7
  104. package/dist/src/components/App.js +3 -3
  105. package/dist/src/config.d.ts +19 -0
  106. package/dist/src/config.js +156 -10
  107. package/dist/src/experience-tracker.d.ts +11 -6
  108. package/dist/src/experience-tracker.js +82 -155
  109. package/dist/src/explorbot.d.ts +21 -13
  110. package/dist/src/explorbot.js +84 -80
  111. package/dist/src/explorer.d.ts +66 -102
  112. package/dist/src/explorer.js +332 -623
  113. package/dist/src/knowledge-tracker.d.ts +2 -2
  114. package/dist/src/knowledge-tracker.js +38 -45
  115. package/dist/src/state-manager.d.ts +10 -30
  116. package/dist/src/state-manager.js +11 -129
  117. package/dist/src/utils/aria.d.ts +6 -1
  118. package/dist/src/utils/aria.js +4 -1
  119. package/dist/src/utils/cache.d.ts +15 -0
  120. package/dist/src/utils/cache.js +34 -0
  121. package/dist/src/utils/context-formatter.d.ts +6 -0
  122. package/dist/src/utils/context-formatter.js +15 -27
  123. package/dist/src/utils/hooks-runner.js +2 -4
  124. package/dist/src/utils/html.d.ts +5 -0
  125. package/dist/src/utils/html.js +71 -0
  126. package/dist/src/utils/markdown-files.d.ts +10 -0
  127. package/dist/src/utils/markdown-files.js +21 -0
  128. package/dist/src/utils/markdown-query.d.ts +6 -0
  129. package/dist/src/utils/markdown-query.js +14 -1
  130. package/dist/src/utils/page-readiness.d.ts +1 -0
  131. package/dist/src/utils/page-readiness.js +1 -1
  132. package/dist/src/utils/secrets.js +2 -2
  133. package/dist/src/utils/strings.d.ts +2 -0
  134. package/dist/src/utils/strings.js +10 -0
  135. package/dist/src/utils/test-files.js +1 -1
  136. package/dist/src/utils/web-annotate.d.ts +5 -0
  137. package/dist/src/utils/web-annotate.js +58 -0
  138. package/dist/src/utils/web-eidx.d.ts +2 -0
  139. package/dist/src/utils/web-eidx.js +20 -0
  140. package/dist/src/utils/web-element.d.ts +3 -1
  141. package/dist/src/utils/web-element.js +32 -0
  142. package/dist/src/utils/web-sandbox.d.ts +4 -0
  143. package/dist/src/utils/web-sandbox.js +37 -0
  144. package/models.json +30 -0
  145. package/package.json +23 -3
  146. package/src/action-result.ts +70 -173
  147. package/src/action.ts +38 -132
  148. package/src/ai/agent.ts +20 -0
  149. package/src/ai/captain/idle-mode.ts +1 -1
  150. package/src/ai/captain/test-mode.ts +1 -1
  151. package/src/ai/captain/web-mode.ts +18 -30
  152. package/src/ai/captain.ts +17 -42
  153. package/src/ai/conversation.ts +9 -32
  154. package/src/ai/driller.ts +53 -83
  155. package/src/ai/experience-compactor.ts +16 -35
  156. package/src/ai/fisherman-tools.ts +1 -1
  157. package/src/ai/fisherman.ts +0 -4
  158. package/src/ai/historian/codeceptjs.ts +5 -3
  159. package/src/ai/historian/playwright.ts +7 -4
  160. package/src/ai/historian/screencast.ts +4 -3
  161. package/src/ai/historian/utils.ts +0 -1
  162. package/src/ai/historian.ts +6 -8
  163. package/src/ai/navigator.ts +39 -70
  164. package/src/ai/pilot.ts +59 -61
  165. package/src/ai/planner.ts +76 -62
  166. package/src/ai/provider.ts +85 -146
  167. package/src/ai/quartermaster.ts +39 -45
  168. package/src/ai/rerunner.ts +27 -50
  169. package/src/ai/researcher/cache.ts +10 -16
  170. package/src/ai/researcher/coordinates.ts +4 -3
  171. package/src/ai/researcher/deep-analysis.ts +10 -11
  172. package/src/ai/researcher/locators.ts +66 -9
  173. package/src/ai/researcher/parser.ts +1 -1
  174. package/src/ai/researcher/research-result.ts +0 -18
  175. package/src/ai/researcher/sections.ts +9 -3
  176. package/src/ai/researcher.ts +42 -119
  177. package/src/ai/rules.ts +24 -13
  178. package/src/ai/session-analyst.ts +8 -5
  179. package/src/ai/task-agent.ts +45 -38
  180. package/src/ai/tester.ts +51 -129
  181. package/src/ai/tools.ts +145 -153
  182. package/src/command-handler.ts +1 -1
  183. package/src/commands/compact-command.ts +7 -8
  184. package/src/commands/context-aria-command.ts +1 -1
  185. package/src/commands/context-command.ts +8 -6
  186. package/src/commands/context-data-command.ts +2 -2
  187. package/src/commands/context-experience-command.ts +2 -2
  188. package/src/commands/context-html-command.ts +2 -2
  189. package/src/commands/context-knowledge-command.ts +2 -2
  190. package/src/commands/drill-command.ts +1 -1
  191. package/src/commands/experience-command.ts +1 -1
  192. package/src/commands/explore-command.ts +4 -3
  193. package/src/commands/freesail-command.ts +2 -2
  194. package/src/commands/knows-command.ts +1 -1
  195. package/src/commands/learn-command.ts +5 -3
  196. package/src/commands/path-command.ts +1 -1
  197. package/src/commands/research-command.ts +1 -1
  198. package/src/commands/test-command.ts +1 -1
  199. package/src/components/AddKnowledge.tsx +5 -7
  200. package/src/components/App.tsx +3 -3
  201. package/src/config.ts +185 -11
  202. package/src/experience-tracker.ts +89 -152
  203. package/src/explorbot.ts +88 -83
  204. package/src/explorer.ts +402 -693
  205. package/src/knowledge-tracker.ts +42 -49
  206. package/src/state-manager.ts +20 -155
  207. package/src/utils/aria.ts +11 -2
  208. package/src/utils/cache.ts +40 -0
  209. package/src/utils/context-formatter.ts +12 -15
  210. package/src/utils/hooks-runner.ts +2 -4
  211. package/src/utils/html.ts +79 -0
  212. package/src/utils/markdown-files.ts +30 -0
  213. package/src/utils/markdown-query.ts +15 -1
  214. package/src/utils/page-readiness.ts +1 -1
  215. package/src/utils/secrets.ts +2 -3
  216. package/src/utils/strings.ts +12 -0
  217. package/src/utils/test-files.ts +1 -1
  218. package/src/utils/web-annotate.ts +64 -0
  219. package/src/utils/web-eidx.ts +21 -0
  220. package/src/utils/web-element.ts +34 -0
  221. package/src/utils/web-sandbox.ts +43 -0
  222. package/dist/boat/api-tester/example/apibot.config.js +0 -30
@@ -1,10 +1,11 @@
1
- import dedent from 'dedent';
2
1
  import type { ActionResult } from '../action-result.js';
3
- import { type ExperienceTracker, renderExperienceToc } from '../experience-tracker.js';
2
+ import type { ExplorbotConfig } from '../config.ts';
3
+ import type { ExperienceTracker } from '../experience-tracker.js';
4
+ import type Explorer from '../explorer.ts';
4
5
  import type { KnowledgeTracker } from '../knowledge-tracker.js';
5
- import { createDebug, pluralize, tag } from '../utils/logger.js';
6
-
7
- const debugLog = createDebug('explorbot:task-agent');
6
+ import type { StateManager } from '../state-manager.ts';
7
+ import { HooksRunner } from '../utils/hooks-runner.ts';
8
+ import type { AgentDeps, ToolDeps } from './agent.ts';
8
9
  import { Historian } from './historian.js';
9
10
  import type { Navigator } from './navigator.js';
10
11
  import type { Provider } from './provider.js';
@@ -21,6 +22,12 @@ function createNullProxy<T extends object>(): T {
21
22
  }
22
23
 
23
24
  export abstract class TaskAgent {
25
+ explorer!: Explorer;
26
+ provider!: Provider;
27
+ config!: ExplorbotConfig;
28
+ stateManager!: StateManager;
29
+ knowledgeTracker!: KnowledgeTracker;
30
+ protected hooksRunner!: HooksRunner;
24
31
  protected consecutiveFailures = 0;
25
32
  protected consecutiveEmptyResults = 0;
26
33
  protected recentToolCalls: any[] = [];
@@ -29,44 +36,48 @@ export abstract class TaskAgent {
29
36
  private _historian: Historian | null = null;
30
37
  private _quartermaster: Quartermaster | null = null;
31
38
 
32
- protected abstract getNavigator(): Navigator;
33
- protected abstract getExperienceTracker(): ExperienceTracker;
34
- protected abstract getKnowledgeTracker(): KnowledgeTracker;
35
- protected abstract getProvider(): Provider;
39
+ constructor(deps?: AgentDeps) {
40
+ if (!deps) return;
41
+ this.explorer = deps.explorer;
42
+ this.provider = deps.ai;
43
+ this.config = deps.config;
44
+ this.stateManager = deps.stateManager;
45
+ this.knowledgeTracker = deps.knowledgeTracker;
46
+ this.hooksRunner = new HooksRunner(deps.explorer, deps.config);
47
+ }
36
48
 
37
- protected getKnowledge(actionResult: ActionResult): string {
38
- const knowledgeFiles = this.getKnowledgeTracker().getRelevantKnowledge(actionResult);
49
+ setHistorian(historian: Historian): void {
50
+ this._historian = historian;
51
+ }
39
52
 
40
- if (knowledgeFiles.length === 0) return '';
53
+ setQuartermaster(quartermaster: Quartermaster): void {
54
+ this._quartermaster = quartermaster;
55
+ }
41
56
 
42
- const knowledgeContent = knowledgeFiles
43
- .map((k) => k.content)
44
- .filter((k) => !!k)
45
- .join('\n\n');
57
+ protected abstract getNavigator(): Navigator;
46
58
 
47
- tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
48
- return dedent`
49
- <knowledge>
50
- Here is relevant knowledge for this page:
59
+ protected get toolDeps(): ToolDeps {
60
+ return { explorer: this.explorer, stateManager: this.stateManager, ai: this.provider };
61
+ }
51
62
 
52
- ${knowledgeContent}
53
- </knowledge>
54
- `;
63
+ protected getExperienceTracker(): ExperienceTracker {
64
+ return this.stateManager.getExperienceTracker();
55
65
  }
56
66
 
57
- protected getExperience(actionResult: ActionResult): string {
58
- const tracker = this.getExperienceTracker();
59
- const toc = tracker.getExperienceTableOfContents(actionResult);
60
- if (toc.length === 0) return '';
61
-
62
- const totalSections = toc.reduce((sum, entry) => sum + entry.sections.length, 0);
63
- debugLog(`injecting experience TOC (${toc.length} files, ${totalSections} sections)`);
64
- tag('operation').log(`Found ${toc.length} experience ${pluralize(toc.length, 'file')} (${totalSections} sections)`);
65
- return renderExperienceToc(toc);
67
+ protected getKnowledgeTracker(): KnowledgeTracker {
68
+ return this.knowledgeTracker;
66
69
  }
67
70
 
68
- setHistorian(historian: Historian): void {
69
- this._historian = historian;
71
+ protected getProvider(): Provider {
72
+ return this.provider;
73
+ }
74
+
75
+ protected getKnowledge(actionResult: ActionResult): string {
76
+ return this.getKnowledgeTracker().renderRelevantKnowledge(actionResult);
77
+ }
78
+
79
+ protected getExperience(actionResult: ActionResult): string {
80
+ return this.getExperienceTracker().renderExperienceTocFor(actionResult);
70
81
  }
71
82
 
72
83
  protected getHistorian(): Historian {
@@ -74,10 +85,6 @@ export abstract class TaskAgent {
74
85
  return createNullProxy<Historian>();
75
86
  }
76
87
 
77
- setQuartermaster(quartermaster: Quartermaster): void {
78
- this._quartermaster = quartermaster;
79
- }
80
-
81
88
  protected getQuartermaster(): Quartermaster {
82
89
  if (this._quartermaster) return this._quartermaster;
83
90
  return createNullProxy<Quartermaster>();
package/src/ai/tester.ts CHANGED
@@ -5,20 +5,17 @@ import dedent from 'dedent';
5
5
  import { z } from 'zod';
6
6
  import { ActionResult } from '../action-result.ts';
7
7
  import { clearActivity, setActivity } from '../activity.ts';
8
- import { ConfigParser } from '../config.ts';
9
- import type { ExperienceTracker } from '../experience-tracker.ts';
10
- import type Explorer from '../explorer.ts';
8
+ import type { RequestStore } from '../api/request-store.ts';
9
+ import type { TestRun } from '../explorer.ts';
11
10
  import { Observability } from '../observability.ts';
12
- import type { StateTransition, WebPageState } from '../state-manager.ts';
11
+ import type { StateTransition } from '../state-manager.ts';
13
12
  import { Stats } from '../stats.ts';
14
- import { type Note, type Test, TestResult, type TestResultType } from '../test-plan.ts';
13
+ import { type Test, TestResult, type TestResultType } from '../test-plan.ts';
15
14
  import { detectFocusArea, extractFocusedElement } from '../utils/aria.ts';
16
15
  import { ErrorPageError, isErrorPage } from '../utils/error-page.ts';
17
- import { HooksRunner } from '../utils/hooks-runner.ts';
18
- import { codeToMarkdown } from '../utils/html.ts';
19
16
  import { createDebug, tag } from '../utils/logger.ts';
20
17
  import { loop } from '../utils/loop.ts';
21
- import type { Agent } from './agent.ts';
18
+ import type { Agent, AgentDeps } from './agent.ts';
22
19
  import type { Captain } from './captain.ts';
23
20
  import type { Conversation } from './conversation.ts';
24
21
  import { Navigator } from './navigator.ts';
@@ -27,7 +24,7 @@ import { Provider } from './provider.ts';
27
24
  import { Researcher } from './researcher.ts';
28
25
  import { actionRule, capabilityGroundingRule, dataProtectionRules, focusedElementRule, formRequirementsRule, locatorRule, multipleTabsRule, sectionContextRule } from './rules.ts';
29
26
  import { TaskAgent } from './task-agent.ts';
30
- import { createCodeceptJSTools, createSpecialContextTools } from './tools.ts';
27
+ import { createCodeceptJSTools, createIframeTools } from './tools.ts';
31
28
 
32
29
  const debugLog = createDebug('explorbot:tester');
33
30
 
@@ -46,8 +43,8 @@ export class Tester extends TaskAgent implements Agent {
46
43
  protected readonly ACTION_TOOLS = ['click', 'hover', 'pressKey', 'form'];
47
44
  protected readonly SPECIAL_CONTEXT_ACTION_TOOLS = ['exitIframe'];
48
45
  emoji = '🧪';
49
- private explorer: Explorer;
50
- private provider: Provider;
46
+ private requestStore: RequestStore;
47
+ private testRun: TestRun | null = null;
51
48
  private currentConversation: Conversation | null = null;
52
49
  private pilot: Pilot | null = null;
53
50
  private captain: Captain | null = null;
@@ -58,43 +55,27 @@ export class Tester extends TaskAgent implements Agent {
58
55
  researcher: Researcher;
59
56
  navigator: Navigator;
60
57
  agentTools: any;
61
- executionLogFile: string | null = null;
62
58
  private previousUrl: string | null = null;
63
59
  private previousStateHash: string | null = null;
64
60
  private pageStateHash: string | null = null;
65
61
  private pageActionResult: ActionResult | null = null;
66
- private hooksRunner: HooksRunner;
67
62
  private seenUiMapUrls = new Set<string>();
68
63
  private lastAnalyzedStateHash: string | null = null;
69
64
  private stalledIterations = 0;
70
65
  private readonly MAX_STALLED_ITERATIONS = 3;
71
66
 
72
- constructor(explorer: Explorer, provider: Provider, researcher: Researcher, navigator: Navigator, agentTools?: any) {
73
- super();
74
- this.explorer = explorer;
75
- this.provider = provider;
67
+ constructor(deps: AgentDeps, researcher: Researcher, navigator: Navigator, agentTools?: any) {
68
+ super(deps);
69
+ this.requestStore = deps.requestStore;
76
70
  this.researcher = researcher;
77
71
  this.navigator = navigator;
78
72
  this.agentTools = agentTools;
79
- this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
80
73
  }
81
74
 
82
75
  protected getNavigator(): Navigator {
83
76
  return this.navigator;
84
77
  }
85
78
 
86
- protected getExperienceTracker(): ExperienceTracker {
87
- return this.explorer.getStateManager().getExperienceTracker();
88
- }
89
-
90
- protected getKnowledgeTracker() {
91
- return this.explorer.getKnowledgeTracker();
92
- }
93
-
94
- protected getProvider(): Provider {
95
- return this.provider;
96
- }
97
-
98
79
  setPilot(pilot: Pilot): void {
99
80
  this.pilot = pilot;
100
81
  }
@@ -104,11 +85,11 @@ export class Tester extends TaskAgent implements Agent {
104
85
  }
105
86
 
106
87
  private getCurrentState(): ActionResult {
107
- return ActionResult.fromState(this.explorer.getStateManager().getCurrentState()!);
88
+ return ActionResult.fromState(this.stateManager.getCurrentState()!);
108
89
  }
109
90
 
110
91
  private get progressCheckInterval(): number {
111
- return (this.explorer.getConfig().ai?.agents?.tester as any)?.progressCheckInterval ?? 3;
92
+ return (this.config.ai?.agents?.tester as any)?.progressCheckInterval ?? 3;
112
93
  }
113
94
 
114
95
  getConversation(): Conversation | null {
@@ -117,7 +98,7 @@ export class Tester extends TaskAgent implements Agent {
117
98
 
118
99
  async test(task: Test): Promise<{ success: boolean }> {
119
100
  Stats.tests++;
120
- const state = this.explorer.getStateManager().getCurrentState();
101
+ const state = this.stateManager.getCurrentState();
121
102
  if (!state) throw new Error('No state found');
122
103
 
123
104
  setActivity(`🧪 Testing: ${task.scenario}`, 'action');
@@ -129,20 +110,20 @@ export class Tester extends TaskAgent implements Agent {
129
110
  this.seenUiMapUrls.clear();
130
111
  this.lastAnalyzedStateHash = null;
131
112
  this.stalledIterations = 0;
132
- this.explorer.getStateManager().clearHistory();
113
+ this.stateManager.clearHistory();
133
114
  this.resetFailureCount();
134
115
  this.pilot?.reset();
135
116
 
136
- const requestStore = this.explorer.getRequestStore();
137
- requestStore?.clear();
138
- const offFailedRequest = requestStore?.onFailedRequest((r) => {
117
+ const requestStore = this.requestStore;
118
+ requestStore.clear();
119
+ const offFailedRequest = requestStore.onFailedRequest((r) => {
139
120
  task.addNote(`Network error: ${r.method} ${r.path} → ${r.status}`, TestResult.FAILED);
140
121
  });
141
122
 
142
123
  const initialState = ActionResult.fromState(state);
143
124
  if (isErrorPage(initialState)) {
144
125
  task.start();
145
- await this.explorer.startTest(task);
126
+ this.testRun = await this.explorer.beginTest(task);
146
127
  offFailedRequest?.();
147
128
  return await this.abortStartedTestOnErrorPage(task, initialState);
148
129
  }
@@ -151,10 +132,6 @@ export class Tester extends TaskAgent implements Agent {
151
132
  conversation.markLastMessageCacheable();
152
133
  this.currentConversation = conversation;
153
134
 
154
- const outputDir = ConfigParser.getInstance().getOutputDir();
155
- this.executionLogFile = join(outputDir, `tester_${task.sessionName}.md`);
156
- // Note: Markdown saving functionality removed from Conversation class
157
-
158
135
  const scenarioBlock = this.buildScenarioBlock(task, initialState);
159
136
  conversation.addUserText(scenarioBlock);
160
137
  conversation.markLastMessageCacheable();
@@ -203,7 +180,8 @@ export class Tester extends TaskAgent implements Agent {
203
180
 
204
181
  debugLog('Starting test execution with tools');
205
182
 
206
- if (!(await this.explorer.startTest(task))) {
183
+ this.testRun = await this.explorer.beginTest(task);
184
+ if (!this.testRun.started) {
207
185
  offFailedRequest?.();
208
186
  await this.cleanupStartedTest(task);
209
187
  return { success: task.isSuccessful };
@@ -221,7 +199,7 @@ export class Tester extends TaskAgent implements Agent {
221
199
  }
222
200
  }
223
201
 
224
- const startState = this.explorer.getStateManager().getCurrentState();
202
+ const startState = this.stateManager.getCurrentState();
225
203
  if (startState) {
226
204
  task.addUrlNote(startState);
227
205
  const startActionResult = ActionResult.fromState(startState);
@@ -233,14 +211,14 @@ export class Tester extends TaskAgent implements Agent {
233
211
  const currentUrl = startState?.url || task.startUrl || '';
234
212
  await this.hooksRunner.runBeforeHook('tester', currentUrl);
235
213
 
236
- const offStateChange = this.explorer.getStateManager().onStateChange((event: StateTransition) => {
214
+ const offStateChange = this.stateManager.onStateChange((event: StateTransition) => {
237
215
  if (task.hasFinished) return;
238
216
  if (event.toState?.url === event.fromState?.url) return;
239
217
  if (event.toState) task.addUrlNote(event.toState, event.fromState || undefined);
240
218
  task.states.push(event.toState);
241
219
  });
242
220
 
243
- const codeceptjsTools = createCodeceptJSTools(this.explorer, task);
221
+ const codeceptjsTools = createCodeceptJSTools(this.toolDeps, task);
244
222
  let assertionPerformed = false;
245
223
  let extensions = 0;
246
224
  let shouldContinue = true;
@@ -251,7 +229,7 @@ export class Tester extends TaskAgent implements Agent {
251
229
  await loop(
252
230
  async ({ stop, pause, iteration, userInput }) => {
253
231
  debugLog('iteration', iteration);
254
- if (!(await this.explorer.ensurePageAvailable())) {
232
+ if (!(await this.explorer.recover()).ok) {
255
233
  task.addNote('Browser page is unavailable');
256
234
  task.finish(TestResult.FAILED);
257
235
  stop();
@@ -261,14 +239,16 @@ export class Tester extends TaskAgent implements Agent {
261
239
 
262
240
  const tools = {
263
241
  ...codeceptjsTools,
264
- ...(currentState.isInsideIframe ? createSpecialContextTools(this.explorer, 'iframe') : {}),
265
242
  ...this.createTestFlowTools(task, currentState, conversation),
266
243
  ...this.agentTools,
267
244
  };
245
+ if (currentState.isInsideIframe) {
246
+ Object.assign(tools, createIframeTools(this.toolDeps));
247
+ }
268
248
 
269
249
  debugLog(`Test ${task.scenario} iteration ${iteration}`);
270
250
 
271
- if (this.explorer.getStateManager().isInDeadLoop()) {
251
+ if (this.stateManager.isInDeadLoop()) {
272
252
  task.addNote('Dead loop detected. Stopped');
273
253
  stop();
274
254
  return;
@@ -417,7 +397,7 @@ export class Tester extends TaskAgent implements Agent {
417
397
 
418
398
  if (task.hasFinished) break;
419
399
 
420
- if (!(await this.explorer.ensurePageAvailable())) break;
400
+ if (!(await this.explorer.recover()).ok) break;
421
401
 
422
402
  const finalState = this.getCurrentState();
423
403
  const wantsContinue = await this.pilot!.finalReview(task, finalState, conversation, this.navigator);
@@ -436,7 +416,7 @@ export class Tester extends TaskAgent implements Agent {
436
416
  shouldContinue = true;
437
417
  }
438
418
 
439
- const finalUrl = this.explorer.getStateManager().getCurrentState()?.url || currentUrl;
419
+ const finalUrl = this.stateManager.getCurrentState()?.url || currentUrl;
440
420
  await this.hooksRunner.runAfterHook('tester', finalUrl);
441
421
 
442
422
  await this.getHistorian().saveSession(task, initialState, conversation);
@@ -448,7 +428,7 @@ export class Tester extends TaskAgent implements Agent {
448
428
  offStateChange();
449
429
  offFailedRequest?.();
450
430
  await this.finishTest(task);
451
- await this.explorer.stopTest(task, this.buildStopTestMeta(task));
431
+ await this.testRun?.stop(this.buildStopTestMeta(task));
452
432
 
453
433
  return {
454
434
  success: task.isSuccessful,
@@ -526,7 +506,6 @@ export class Tester extends TaskAgent implements Agent {
526
506
  const currentStateHash = currentState.hash;
527
507
 
528
508
  const isNewUrl = this.previousUrl !== currentUrl;
529
- const isStateChanged = !isNewUrl && this.previousStateHash !== currentStateHash;
530
509
 
531
510
  this.previousUrl = currentUrl;
532
511
  this.previousStateHash = currentStateHash;
@@ -565,7 +544,7 @@ export class Tester extends TaskAgent implements Agent {
565
544
  }
566
545
 
567
546
  if (currentState.isInsideIframe) {
568
- const iframeInfo = currentState.iframeURL || this.explorer.getCurrentIframeInfo() || 'iframe context active';
547
+ const iframeInfo = currentState.iframeURL || 'iframe context active';
569
548
  context += dedent`
570
549
  <iframe_context>
571
550
  INSIDE IFRAME: ${iframeInfo}
@@ -574,10 +553,10 @@ export class Tester extends TaskAgent implements Agent {
574
553
  `;
575
554
  }
576
555
 
577
- if (this.explorer.hasOtherTabs()) {
578
- const otherTabs = this.explorer.getOtherTabsInfo();
556
+ const otherTabs = this.stateManager.otherTabs;
557
+ if (otherTabs.length > 0) {
579
558
  context += multipleTabsRule(otherTabs);
580
- this.explorer.clearOtherTabsInfo();
559
+ this.stateManager.otherTabs = [];
581
560
  }
582
561
 
583
562
  if (isNewUrl) {
@@ -641,27 +620,6 @@ export class Tester extends TaskAgent implements Agent {
641
620
  }
642
621
  }
643
622
 
644
- // if (isStateChanged) {
645
- // const combinedHtml = await currentState.combinedHtml();
646
- // context += dedent`
647
- // Context (state changed):
648
-
649
- // <page>
650
- // CURRENT URL: ${currentState.url}
651
- // CURRENT TITLE: ${currentState.title}
652
- // </page>
653
-
654
- // <page_html>
655
- // ${combinedHtml}
656
- // </page_html>
657
-
658
- // <page_aria>
659
- // ${currentState.ariaSnapshot}
660
- // </page_aria>
661
- // `;
662
- // return context;
663
- // }
664
-
665
623
  if (context) return context;
666
624
 
667
625
  if (iteration % 5) return '';
@@ -680,38 +638,6 @@ export class Tester extends TaskAgent implements Agent {
680
638
  `;
681
639
  }
682
640
 
683
- private async promptLogStep(task: Test): Promise<string> {
684
- let logPrompt = dedent`
685
- <task>
686
- Add a note explaining what you achieved with previous action.
687
- Use tools to interact with the page to achieve the scenario goal or expected outcomes.
688
- Call record tool to explain the last action
689
- Format: record([<action performed>, <what has changed>, <what you expect to do next>])
690
- </task>
691
- `;
692
-
693
- if (task.getPrintableNotes()) {
694
- logPrompt = dedent`
695
- Your interaction log notes:
696
- <notes>
697
- ${task.getPrintableNotes()}
698
- </notes>
699
-
700
- <rules>
701
- Use your previous interaction notes to guide your next actions.
702
- Do not perform the same checks.
703
- </rules>
704
- `;
705
- }
706
-
707
- const remaining = task.getRemainingExpectations();
708
- if (remaining.length > 0) {
709
- logPrompt += `\nExpected steps to check: ${remaining.join(', ')}`;
710
- }
711
-
712
- return logPrompt;
713
- }
714
-
715
641
  private finishTest(task: Test): void {
716
642
  if (!task.hasFinished) {
717
643
  task.finish(TestResult.FAILED);
@@ -734,7 +660,7 @@ export class Tester extends TaskAgent implements Agent {
734
660
  task.addNote(error.message, TestResult.FAILED, actionResult.screenshotFile, actionResult.fullUrl || actionResult.url);
735
661
  task.finish(TestResult.FAILED);
736
662
  this.finishTest(task);
737
- await this.explorer.stopTest(task, this.buildStopTestMeta(task));
663
+ await this.testRun?.stop(this.buildStopTestMeta(task));
738
664
  clearActivity(true);
739
665
  return { success: false };
740
666
  }
@@ -810,11 +736,13 @@ export class Tester extends TaskAgent implements Agent {
810
736
  - Before retrying your actions check maybe they already achived expected results. Use see() tool for that
811
737
  - If the current URL is already a create/edit/new form and the scenario is about creating/editing that entity, fill and submit that form. Do not click the list-page "New" button again from inside the form.
812
738
  - If the scenario is about search/filter/sort/tabs/list inspection and the current URL is a create/edit/new form, go back or reset to the stable list page before interacting with list controls.
813
- - When selecting related entities from a list, do not choose rows/options/cards marked as "0 items", "0 tests", or otherwise empty if the scenario requires selecting real content.
814
- - In selection pickers, counters such as "Selected 0", "Matched tests 0", or disabled Save/Apply mean the selection did not register. Choose a non-empty item or change filters before submitting.
739
+ - When selecting related entities from a list, do not choose rows/options/cards marked as "0 items", "0 results", or otherwise empty if the scenario requires selecting real content.
740
+ - In selection pickers, counters such as "Selected 0", "Matched 0", or disabled Save/Apply mean the selection did not register. Choose a non-empty item or change filters before submitting.
815
741
  - A passed form/click command only means the command executed. If a required field remains empty, submit stays disabled, or the expected text is not visible, treat the action as not completed and correct the missing field/state.
816
- - For filter/tab scenarios, success requires BOTH: the requested filter/tab is visibly active/selected AND the list content matches that filter. Do not finish from only one of these signals.
817
- - Empty-state text such as "No matched items" only proves a filter when the requested filter/tab is active and the empty state belongs to the filtered list.
742
+ - For filter/tab scenarios, success requires BOTH: the requested state is evidenced by a selected control, URL/query, or another explicit state indicator AND the list content matches that state. Do not finish from only one of these signals.
743
+ - Once the requested control state and matching content are both visible, finish the scenario instead of repeating the interaction. Do not require an aggregate count change unless a baseline was observed immediately before the action.
744
+ - Empty-state text such as "No matched items" only proves a filter when the requested filter state is explicit and the empty state belongs to the filtered list.
745
+ - Associate validation feedback with a field only through explicit evidence such as the field label in the message, an accessibility relationship, focus on the invalid control, or visual confirmation. Do not infer the affected field from DOM order or proximity alone.
818
746
  - When filling complex form with lot of actions performed, use see() to look which fields were filled and which are not
819
747
  - When verify() fails, use see() to visually confirm the result — visual confirmation is equally valid evidence
820
748
  - For visual state verification (active tabs, selected items, counts, colors), prefer see() over DOM-based verify()
@@ -847,7 +775,7 @@ export class Tester extends TaskAgent implements Agent {
847
775
 
848
776
  ${dataProtectionRules}
849
777
 
850
- ${this.provider.getSystemPromptForAgent('tester', this.explorer.getStateManager().getCurrentState()?.url) || ''}
778
+ ${this.provider.getSystemPromptForAgent('tester', this.stateManager.getCurrentState()?.url) || ''}
851
779
  `;
852
780
  }
853
781
 
@@ -875,10 +803,6 @@ export class Tester extends TaskAgent implements Agent {
875
803
  When creating or editing items via form() or type() you should include ${task.sessionName} in the value (if it is not restricted by the application logic)
876
804
  Initial page URL: ${actionResult.url}
877
805
 
878
- ${capabilityGroundingRule}
879
-
880
- ${dataProtectionRules}
881
-
882
806
  ${this.buildDeletionScope(task)}
883
807
 
884
808
  ${this.buildAvailableFiles()}
@@ -898,7 +822,7 @@ export class Tester extends TaskAgent implements Agent {
898
822
  }
899
823
 
900
824
  private buildAvailableFiles(): string {
901
- const userFiles = this.explorer.getConfig().files || {};
825
+ const userFiles = this.config.files || {};
902
826
  const codeceptDir = (global as any).codecept_dir || process.cwd();
903
827
  const lines: string[] = [];
904
828
 
@@ -956,11 +880,9 @@ export class Tester extends TaskAgent implements Agent {
956
880
  reason: z.string().optional().describe('Explanation why reset is the only option'),
957
881
  }),
958
882
  execute: async ({ reason }) => {
959
- if (this.getCurrentState().isInsideIframe) {
960
- await this.explorer.switchToMainFrame();
961
- }
883
+ await this.explorer.action().exitIframe();
962
884
 
963
- const currentState = this.explorer.getStateManager().getCurrentState();
885
+ const currentState = this.stateManager.getCurrentState();
964
886
  const currentUrl = currentState?.fullUrl || currentState?.url;
965
887
  if (currentUrl === resetUrl!) {
966
888
  return {
@@ -988,7 +910,7 @@ export class Tester extends TaskAgent implements Agent {
988
910
  const explanation = reason ? `${reason} (RESET)` : 'Resetting to initial page';
989
911
  const targetUrl = resetUrl!;
990
912
  task.addNote(explanation);
991
- const resetAction = this.explorer.createAction();
913
+ const resetAction = this.explorer.action();
992
914
  const success = await resetAction.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, explanation);
993
915
 
994
916
  if (success) {
@@ -1137,7 +1059,7 @@ export class Tester extends TaskAgent implements Agent {
1137
1059
  mappedStatus = TestResult.FAILED;
1138
1060
  }
1139
1061
 
1140
- const screenshotFile = this.explorer.getStateManager().getCurrentState()?.screenshotFile;
1062
+ const screenshotFile = this.stateManager.getCurrentState()?.screenshotFile;
1141
1063
 
1142
1064
  for (const noteText of input.notes) {
1143
1065
  task.addNote(noteText, mappedStatus, screenshotFile);
@@ -1178,7 +1100,7 @@ export class Tester extends TaskAgent implements Agent {
1178
1100
  const message = error instanceof Error ? error.message : String(error);
1179
1101
  if (!task.hasFinished) task.addNote(`Execution error: ${message}`);
1180
1102
 
1181
- const result = await this.explorer.handleExecutionError(error);
1103
+ const result = await this.explorer.recover(error);
1182
1104
  tag('info').log(`Browser supervisor: ${result.action} - ${result.message}`);
1183
1105
  task.addNote(result.message);
1184
1106
 
@@ -1213,7 +1135,7 @@ export class Tester extends TaskAgent implements Agent {
1213
1135
 
1214
1136
  private async cleanupStartedTest(task: Test): Promise<void> {
1215
1137
  await this.finishTest(task);
1216
- await this.explorer.stopTest(task, {
1138
+ await this.testRun?.stop({
1217
1139
  startUrl: task.startUrl,
1218
1140
  style: task.style,
1219
1141
  sessionName: task.sessionName,