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,22 +1,20 @@
1
1
  import { ActionResult } from '../action-result.js';
2
- import type { ExperienceTracker } from '../experience-tracker.js';
3
- import type Explorer from '../explorer.js';
2
+ import type { RequestStore } from '../api/request-store.js';
3
+ import type { TestRun } from '../explorer.js';
4
4
  import { type Test } from '../test-plan.js';
5
- import { HooksRunner } from '../utils/hooks-runner.js';
6
- import type { Agent } from './agent.js';
5
+ import type { Agent, AgentDeps } from './agent.js';
7
6
  import type { Captain } from './captain.js';
8
7
  import type { Conversation } from './conversation.js';
9
8
  import { Navigator } from './navigator.js';
10
9
  import type { Pilot } from './pilot.js';
11
- import { Provider } from './provider.js';
12
10
  import { Researcher } from './researcher.js';
13
11
  import { TaskAgent } from './task-agent.js';
14
12
  export declare class Tester extends TaskAgent implements Agent {
15
13
  readonly ACTION_TOOLS: string[];
16
14
  readonly SPECIAL_CONTEXT_ACTION_TOOLS: string[];
17
15
  emoji: string;
18
- explorer: Explorer;
19
- provider: Provider;
16
+ requestStore: RequestStore;
17
+ testRun: TestRun | null;
20
18
  currentConversation: Conversation | null;
21
19
  pilot: Pilot | null;
22
20
  captain: Captain | null;
@@ -26,21 +24,16 @@ export declare class Tester extends TaskAgent implements Agent {
26
24
  researcher: Researcher;
27
25
  navigator: Navigator;
28
26
  agentTools: any;
29
- executionLogFile: string | null;
30
27
  previousUrl: string | null;
31
28
  previousStateHash: string | null;
32
29
  pageStateHash: string | null;
33
30
  pageActionResult: ActionResult | null;
34
- hooksRunner: HooksRunner;
35
31
  seenUiMapUrls: Set<string>;
36
32
  lastAnalyzedStateHash: string | null;
37
33
  stalledIterations: number;
38
34
  readonly MAX_STALLED_ITERATIONS = 3;
39
- constructor(explorer: Explorer, provider: Provider, researcher: Researcher, navigator: Navigator, agentTools?: any);
35
+ constructor(deps: AgentDeps, researcher: Researcher, navigator: Navigator, agentTools?: any);
40
36
  getNavigator(): Navigator;
41
- getExperienceTracker(): ExperienceTracker;
42
- getKnowledgeTracker(): import("../knowledge-tracker.js").KnowledgeTracker;
43
- getProvider(): Provider;
44
37
  setPilot(pilot: Pilot): void;
45
38
  setCaptain(captain: Captain): void;
46
39
  getCurrentState(): ActionResult;
@@ -56,7 +49,6 @@ export declare class Tester extends TaskAgent implements Agent {
56
49
  shouldStopForStalledExecution(task: Test, previousState: ActionResult, toolExecutions: any[]): boolean;
57
50
  prepareInstructionsForNextStep(task: Test): Promise<string>;
58
51
  reinjectContextIfNeeded(iteration: number, currentState: ActionResult): Promise<string>;
59
- promptLogStep(task: Test): Promise<string>;
60
52
  finishTest(task: Test): void;
61
53
  abortStartedTestOnErrorPage(task: Test, actionResult: ActionResult): Promise<{
62
54
  success: boolean;
@@ -5,18 +5,16 @@ import dedent from 'dedent';
5
5
  import { z } from 'zod';
6
6
  import { ActionResult } from "../action-result.js";
7
7
  import { clearActivity, setActivity } from "../activity.js";
8
- import { ConfigParser } from "../config.js";
9
8
  import { Observability } from "../observability.js";
10
9
  import { Stats } from "../stats.js";
11
10
  import { TestResult } from "../test-plan.js";
12
11
  import { detectFocusArea, extractFocusedElement } from "../utils/aria.js";
13
12
  import { ErrorPageError, isErrorPage } from "../utils/error-page.js";
14
- import { HooksRunner } from "../utils/hooks-runner.js";
15
13
  import { createDebug, tag } from "../utils/logger.js";
16
14
  import { loop } from "../utils/loop.js";
17
15
  import { actionRule, capabilityGroundingRule, dataProtectionRules, focusedElementRule, formRequirementsRule, locatorRule, multipleTabsRule, sectionContextRule } from "./rules.js";
18
16
  import { TaskAgent } from "./task-agent.js";
19
- import { createCodeceptJSTools, createSpecialContextTools } from "./tools.js";
17
+ import { createCodeceptJSTools, createIframeTools } from "./tools.js";
20
18
  const debugLog = createDebug('explorbot:tester');
21
19
  const SAMPLE_FILES_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../../assets/sample-files');
22
20
  const SAMPLE_FILES = {
@@ -32,8 +30,8 @@ export class Tester extends TaskAgent {
32
30
  ACTION_TOOLS = ['click', 'hover', 'pressKey', 'form'];
33
31
  SPECIAL_CONTEXT_ACTION_TOOLS = ['exitIframe'];
34
32
  emoji = '🧪';
35
- explorer;
36
- provider;
33
+ requestStore;
34
+ testRun = null;
37
35
  currentConversation = null;
38
36
  pilot = null;
39
37
  captain = null;
@@ -43,37 +41,24 @@ export class Tester extends TaskAgent {
43
41
  researcher;
44
42
  navigator;
45
43
  agentTools;
46
- executionLogFile = null;
47
44
  previousUrl = null;
48
45
  previousStateHash = null;
49
46
  pageStateHash = null;
50
47
  pageActionResult = null;
51
- hooksRunner;
52
48
  seenUiMapUrls = new Set();
53
49
  lastAnalyzedStateHash = null;
54
50
  stalledIterations = 0;
55
51
  MAX_STALLED_ITERATIONS = 3;
56
- constructor(explorer, provider, researcher, navigator, agentTools) {
57
- super();
58
- this.explorer = explorer;
59
- this.provider = provider;
52
+ constructor(deps, researcher, navigator, agentTools) {
53
+ super(deps);
54
+ this.requestStore = deps.requestStore;
60
55
  this.researcher = researcher;
61
56
  this.navigator = navigator;
62
57
  this.agentTools = agentTools;
63
- this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
64
58
  }
65
59
  getNavigator() {
66
60
  return this.navigator;
67
61
  }
68
- getExperienceTracker() {
69
- return this.explorer.getStateManager().getExperienceTracker();
70
- }
71
- getKnowledgeTracker() {
72
- return this.explorer.getKnowledgeTracker();
73
- }
74
- getProvider() {
75
- return this.provider;
76
- }
77
62
  setPilot(pilot) {
78
63
  this.pilot = pilot;
79
64
  }
@@ -81,17 +66,17 @@ export class Tester extends TaskAgent {
81
66
  this.captain = captain;
82
67
  }
83
68
  getCurrentState() {
84
- return ActionResult.fromState(this.explorer.getStateManager().getCurrentState());
69
+ return ActionResult.fromState(this.stateManager.getCurrentState());
85
70
  }
86
71
  get progressCheckInterval() {
87
- return this.explorer.getConfig().ai?.agents?.tester?.progressCheckInterval ?? 3;
72
+ return this.config.ai?.agents?.tester?.progressCheckInterval ?? 3;
88
73
  }
89
74
  getConversation() {
90
75
  return this.currentConversation;
91
76
  }
92
77
  async test(task) {
93
78
  Stats.tests++;
94
- const state = this.explorer.getStateManager().getCurrentState();
79
+ const state = this.stateManager.getCurrentState();
95
80
  if (!state)
96
81
  throw new Error('No state found');
97
82
  setActivity(`🧪 Testing: ${task.scenario}`, 'action');
@@ -102,27 +87,24 @@ export class Tester extends TaskAgent {
102
87
  this.seenUiMapUrls.clear();
103
88
  this.lastAnalyzedStateHash = null;
104
89
  this.stalledIterations = 0;
105
- this.explorer.getStateManager().clearHistory();
90
+ this.stateManager.clearHistory();
106
91
  this.resetFailureCount();
107
92
  this.pilot?.reset();
108
- const requestStore = this.explorer.getRequestStore();
109
- requestStore?.clear();
110
- const offFailedRequest = requestStore?.onFailedRequest((r) => {
93
+ const requestStore = this.requestStore;
94
+ requestStore.clear();
95
+ const offFailedRequest = requestStore.onFailedRequest((r) => {
111
96
  task.addNote(`Network error: ${r.method} ${r.path} → ${r.status}`, TestResult.FAILED);
112
97
  });
113
98
  const initialState = ActionResult.fromState(state);
114
99
  if (isErrorPage(initialState)) {
115
100
  task.start();
116
- await this.explorer.startTest(task);
101
+ this.testRun = await this.explorer.beginTest(task);
117
102
  offFailedRequest?.();
118
103
  return await this.abortStartedTestOnErrorPage(task, initialState);
119
104
  }
120
105
  const conversation = this.provider.startConversation(this.getSystemMessage(), 'tester');
121
106
  conversation.markLastMessageCacheable();
122
107
  this.currentConversation = conversation;
123
- const outputDir = ConfigParser.getInstance().getOutputDir();
124
- this.executionLogFile = join(outputDir, `tester_${task.sessionName}.md`);
125
- // Note: Markdown saving functionality removed from Conversation class
126
108
  const scenarioBlock = this.buildScenarioBlock(task, initialState);
127
109
  conversation.addUserText(scenarioBlock);
128
110
  conversation.markLastMessageCacheable();
@@ -163,7 +145,8 @@ export class Tester extends TaskAgent {
163
145
  }
164
146
  }
165
147
  debugLog('Starting test execution with tools');
166
- if (!(await this.explorer.startTest(task))) {
148
+ this.testRun = await this.explorer.beginTest(task);
149
+ if (!this.testRun.started) {
167
150
  offFailedRequest?.();
168
151
  await this.cleanupStartedTest(task);
169
152
  return { success: task.isSuccessful };
@@ -180,7 +163,7 @@ export class Tester extends TaskAgent {
180
163
  return { success: task.isSuccessful };
181
164
  }
182
165
  }
183
- const startState = this.explorer.getStateManager().getCurrentState();
166
+ const startState = this.stateManager.getCurrentState();
184
167
  if (startState) {
185
168
  task.addUrlNote(startState);
186
169
  const startActionResult = ActionResult.fromState(startState);
@@ -191,7 +174,7 @@ export class Tester extends TaskAgent {
191
174
  }
192
175
  const currentUrl = startState?.url || task.startUrl || '';
193
176
  await this.hooksRunner.runBeforeHook('tester', currentUrl);
194
- const offStateChange = this.explorer.getStateManager().onStateChange((event) => {
177
+ const offStateChange = this.stateManager.onStateChange((event) => {
195
178
  if (task.hasFinished)
196
179
  return;
197
180
  if (event.toState?.url === event.fromState?.url)
@@ -200,7 +183,7 @@ export class Tester extends TaskAgent {
200
183
  task.addUrlNote(event.toState, event.fromState || undefined);
201
184
  task.states.push(event.toState);
202
185
  });
203
- const codeceptjsTools = createCodeceptJSTools(this.explorer, task);
186
+ const codeceptjsTools = createCodeceptJSTools(this.toolDeps, task);
204
187
  let assertionPerformed = false;
205
188
  let extensions = 0;
206
189
  let shouldContinue = true;
@@ -208,7 +191,7 @@ export class Tester extends TaskAgent {
208
191
  shouldContinue = false;
209
192
  await loop(async ({ stop, pause, iteration, userInput }) => {
210
193
  debugLog('iteration', iteration);
211
- if (!(await this.explorer.ensurePageAvailable())) {
194
+ if (!(await this.explorer.recover()).ok) {
212
195
  task.addNote('Browser page is unavailable');
213
196
  task.finish(TestResult.FAILED);
214
197
  stop();
@@ -217,12 +200,14 @@ export class Tester extends TaskAgent {
217
200
  const currentState = this.getCurrentState();
218
201
  const tools = {
219
202
  ...codeceptjsTools,
220
- ...(currentState.isInsideIframe ? createSpecialContextTools(this.explorer, 'iframe') : {}),
221
203
  ...this.createTestFlowTools(task, currentState, conversation),
222
204
  ...this.agentTools,
223
205
  };
206
+ if (currentState.isInsideIframe) {
207
+ Object.assign(tools, createIframeTools(this.toolDeps));
208
+ }
224
209
  debugLog(`Test ${task.scenario} iteration ${iteration}`);
225
- if (this.explorer.getStateManager().isInDeadLoop()) {
210
+ if (this.stateManager.isInDeadLoop()) {
226
211
  task.addNote('Dead loop detected. Stopped');
227
212
  stop();
228
213
  return;
@@ -357,7 +342,7 @@ export class Tester extends TaskAgent {
357
342
  });
358
343
  if (task.hasFinished)
359
344
  break;
360
- if (!(await this.explorer.ensurePageAvailable()))
345
+ if (!(await this.explorer.recover()).ok)
361
346
  break;
362
347
  const finalState = this.getCurrentState();
363
348
  const wantsContinue = await this.pilot.finalReview(task, finalState, conversation, this.navigator);
@@ -375,7 +360,7 @@ export class Tester extends TaskAgent {
375
360
  conversation.compactToolResults(1);
376
361
  shouldContinue = true;
377
362
  }
378
- const finalUrl = this.explorer.getStateManager().getCurrentState()?.url || currentUrl;
363
+ const finalUrl = this.stateManager.getCurrentState()?.url || currentUrl;
379
364
  await this.hooksRunner.runAfterHook('tester', finalUrl);
380
365
  await this.getHistorian().saveSession(task, initialState, conversation);
381
366
  if (task.plan) {
@@ -385,7 +370,7 @@ export class Tester extends TaskAgent {
385
370
  offStateChange();
386
371
  offFailedRequest?.();
387
372
  await this.finishTest(task);
388
- await this.explorer.stopTest(task, this.buildStopTestMeta(task));
373
+ await this.testRun?.stop(this.buildStopTestMeta(task));
389
374
  return {
390
375
  success: task.isSuccessful,
391
376
  ...task,
@@ -456,7 +441,6 @@ export class Tester extends TaskAgent {
456
441
  const currentUrl = currentState.url;
457
442
  const currentStateHash = currentState.hash;
458
443
  const isNewUrl = this.previousUrl !== currentUrl;
459
- const isStateChanged = !isNewUrl && this.previousStateHash !== currentStateHash;
460
444
  this.previousUrl = currentUrl;
461
445
  this.previousStateHash = currentStateHash;
462
446
  let context = '';
@@ -490,7 +474,7 @@ export class Tester extends TaskAgent {
490
474
  `;
491
475
  }
492
476
  if (currentState.isInsideIframe) {
493
- const iframeInfo = currentState.iframeURL || this.explorer.getCurrentIframeInfo() || 'iframe context active';
477
+ const iframeInfo = currentState.iframeURL || 'iframe context active';
494
478
  context += dedent `
495
479
  <iframe_context>
496
480
  INSIDE IFRAME: ${iframeInfo}
@@ -498,10 +482,10 @@ export class Tester extends TaskAgent {
498
482
  </iframe_context>
499
483
  `;
500
484
  }
501
- if (this.explorer.hasOtherTabs()) {
502
- const otherTabs = this.explorer.getOtherTabsInfo();
485
+ const otherTabs = this.stateManager.otherTabs;
486
+ if (otherTabs.length > 0) {
503
487
  context += multipleTabsRule(otherTabs);
504
- this.explorer.clearOtherTabsInfo();
488
+ this.stateManager.otherTabs = [];
505
489
  }
506
490
  if (isNewUrl) {
507
491
  const alreadySeenUiMap = this.seenUiMapUrls.has(currentUrl);
@@ -564,23 +548,6 @@ export class Tester extends TaskAgent {
564
548
  `;
565
549
  }
566
550
  }
567
- // if (isStateChanged) {
568
- // const combinedHtml = await currentState.combinedHtml();
569
- // context += dedent`
570
- // Context (state changed):
571
- // <page>
572
- // CURRENT URL: ${currentState.url}
573
- // CURRENT TITLE: ${currentState.title}
574
- // </page>
575
- // <page_html>
576
- // ${combinedHtml}
577
- // </page_html>
578
- // <page_aria>
579
- // ${currentState.ariaSnapshot}
580
- // </page_aria>
581
- // `;
582
- // return context;
583
- // }
584
551
  if (context)
585
552
  return context;
586
553
  if (iteration % 5)
@@ -598,34 +565,6 @@ export class Tester extends TaskAgent {
598
565
  </page_aria>
599
566
  `;
600
567
  }
601
- async promptLogStep(task) {
602
- let logPrompt = dedent `
603
- <task>
604
- Add a note explaining what you achieved with previous action.
605
- Use tools to interact with the page to achieve the scenario goal or expected outcomes.
606
- Call record tool to explain the last action
607
- Format: record([<action performed>, <what has changed>, <what you expect to do next>])
608
- </task>
609
- `;
610
- if (task.getPrintableNotes()) {
611
- logPrompt = dedent `
612
- Your interaction log notes:
613
- <notes>
614
- ${task.getPrintableNotes()}
615
- </notes>
616
-
617
- <rules>
618
- Use your previous interaction notes to guide your next actions.
619
- Do not perform the same checks.
620
- </rules>
621
- `;
622
- }
623
- const remaining = task.getRemainingExpectations();
624
- if (remaining.length > 0) {
625
- logPrompt += `\nExpected steps to check: ${remaining.join(', ')}`;
626
- }
627
- return logPrompt;
628
- }
629
568
  finishTest(task) {
630
569
  if (!task.hasFinished) {
631
570
  task.finish(TestResult.FAILED);
@@ -649,7 +588,7 @@ export class Tester extends TaskAgent {
649
588
  task.addNote(error.message, TestResult.FAILED, actionResult.screenshotFile, actionResult.fullUrl || actionResult.url);
650
589
  task.finish(TestResult.FAILED);
651
590
  this.finishTest(task);
652
- await this.explorer.stopTest(task, this.buildStopTestMeta(task));
591
+ await this.testRun?.stop(this.buildStopTestMeta(task));
653
592
  clearActivity(true);
654
593
  return { success: false };
655
594
  }
@@ -725,11 +664,13 @@ export class Tester extends TaskAgent {
725
664
  - Before retrying your actions check maybe they already achived expected results. Use see() tool for that
726
665
  - 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.
727
666
  - 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.
728
- - 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.
729
- - 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.
667
+ - 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.
668
+ - 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.
730
669
  - 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.
731
- - 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.
732
- - 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.
670
+ - 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.
671
+ - 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.
672
+ - 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.
673
+ - 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.
733
674
  - When filling complex form with lot of actions performed, use see() to look which fields were filled and which are not
734
675
  - When verify() fails, use see() to visually confirm the result — visual confirmation is equally valid evidence
735
676
  - For visual state verification (active tabs, selected items, counts, colors), prefer see() over DOM-based verify()
@@ -762,7 +703,7 @@ export class Tester extends TaskAgent {
762
703
 
763
704
  ${dataProtectionRules}
764
705
 
765
- ${this.provider.getSystemPromptForAgent('tester', this.explorer.getStateManager().getCurrentState()?.url) || ''}
706
+ ${this.provider.getSystemPromptForAgent('tester', this.stateManager.getCurrentState()?.url) || ''}
766
707
  `;
767
708
  }
768
709
  buildScenarioBlock(task, actionResult) {
@@ -788,10 +729,6 @@ export class Tester extends TaskAgent {
788
729
  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)
789
730
  Initial page URL: ${actionResult.url}
790
731
 
791
- ${capabilityGroundingRule}
792
-
793
- ${dataProtectionRules}
794
-
795
732
  ${this.buildDeletionScope(task)}
796
733
 
797
734
  ${this.buildAvailableFiles()}
@@ -810,7 +747,7 @@ export class Tester extends TaskAgent {
810
747
  .map((t) => t.sessionName);
811
748
  }
812
749
  buildAvailableFiles() {
813
- const userFiles = this.explorer.getConfig().files || {};
750
+ const userFiles = this.config.files || {};
814
751
  const codeceptDir = global.codecept_dir || process.cwd();
815
752
  const lines = [];
816
753
  for (const [description, filename] of Object.entries(SAMPLE_FILES)) {
@@ -864,10 +801,8 @@ export class Tester extends TaskAgent {
864
801
  reason: z.string().optional().describe('Explanation why reset is the only option'),
865
802
  }),
866
803
  execute: async ({ reason }) => {
867
- if (this.getCurrentState().isInsideIframe) {
868
- await this.explorer.switchToMainFrame();
869
- }
870
- const currentState = this.explorer.getStateManager().getCurrentState();
804
+ await this.explorer.action().exitIframe();
805
+ const currentState = this.stateManager.getCurrentState();
871
806
  const currentUrl = currentState?.fullUrl || currentState?.url;
872
807
  if (currentUrl === resetUrl) {
873
808
  return {
@@ -892,7 +827,7 @@ export class Tester extends TaskAgent {
892
827
  const explanation = reason ? `${reason} (RESET)` : 'Resetting to initial page';
893
828
  const targetUrl = resetUrl;
894
829
  task.addNote(explanation);
895
- const resetAction = this.explorer.createAction();
830
+ const resetAction = this.explorer.action();
896
831
  const success = await resetAction.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, explanation);
897
832
  if (success) {
898
833
  return {
@@ -1035,7 +970,7 @@ export class Tester extends TaskAgent {
1035
970
  else if (input.status === 'fail') {
1036
971
  mappedStatus = TestResult.FAILED;
1037
972
  }
1038
- const screenshotFile = this.explorer.getStateManager().getCurrentState()?.screenshotFile;
973
+ const screenshotFile = this.stateManager.getCurrentState()?.screenshotFile;
1039
974
  for (const noteText of input.notes) {
1040
975
  task.addNote(noteText, mappedStatus, screenshotFile);
1041
976
  if (input.status === 'success') {
@@ -1072,7 +1007,7 @@ export class Tester extends TaskAgent {
1072
1007
  const message = error instanceof Error ? error.message : String(error);
1073
1008
  if (!task.hasFinished)
1074
1009
  task.addNote(`Execution error: ${message}`);
1075
- const result = await this.explorer.handleExecutionError(error);
1010
+ const result = await this.explorer.recover(error);
1076
1011
  tag('info').log(`Browser supervisor: ${result.action} - ${result.message}`);
1077
1012
  task.addNote(result.message);
1078
1013
  if (result.action === 'stop') {
@@ -1103,7 +1038,7 @@ export class Tester extends TaskAgent {
1103
1038
  }
1104
1039
  async cleanupStartedTest(task) {
1105
1040
  await this.finishTest(task);
1106
- await this.explorer.stopTest(task, {
1041
+ await this.testRun?.stop({
1107
1042
  startUrl: task.startUrl,
1108
1043
  style: task.style,
1109
1044
  sessionName: task.sessionName,
@@ -1,11 +1,17 @@
1
- import { ActionResult } from '../action-result.js';
1
+ import { ActionResult, type PageDiff } from '../action-result.js';
2
2
  import type { ExperienceTracker } from '../experience-tracker.js';
3
- import type Explorer from '../explorer.js';
4
3
  import { type Task } from '../test-plan.js';
4
+ import type { ToolDeps } from './agent.js';
5
5
  import { Navigator } from './navigator.js';
6
6
  import { Researcher } from './researcher.js';
7
+ interface AgentToolDeps extends ToolDeps {
8
+ researcher: Researcher;
9
+ navigator: Navigator;
10
+ supervisor?: boolean;
11
+ withExperience?: boolean;
12
+ }
7
13
  export declare const ASSERTION_TOOLS: readonly ["verify"];
8
- export declare function createCodeceptJSTools(explorer: Explorer, task: Task): {
14
+ export declare function createCodeceptJSTools({ explorer, stateManager, ai }: ToolDeps, task: Task): {
9
15
  click: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
10
16
  commands: any;
11
17
  explanation: any;
@@ -24,18 +30,24 @@ export declare function createCodeceptJSTools(explorer: Explorer, task: Task): {
24
30
  explanation: any;
25
31
  }, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
26
32
  };
27
- export declare function createSpecialContextTools(explorer: Explorer, context: 'iframe'): {
28
- exitIframe?: undefined;
29
- } | {
33
+ export declare function createIframeTools({ explorer, stateManager }: ToolDeps): {
30
34
  exitIframe: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
31
35
  reason?: string;
32
36
  }, Record<string, any>, import("@ai-sdk/provider-utils").Context>>;
33
37
  };
34
- export declare function createAgentTools({ explorer, researcher, navigator, experienceTracker, getState, supervisor, }: {
35
- explorer: Explorer;
36
- researcher: Researcher;
37
- navigator: Navigator;
38
- experienceTracker?: ExperienceTracker;
39
- getState?: () => ActionResult | null;
40
- supervisor?: boolean;
41
- }): any;
38
+ export declare function createLearnExperienceTool({ getExperienceTracker, getState }: {
39
+ getExperienceTracker: () => ExperienceTracker;
40
+ getState: () => ActionResult | null;
41
+ }): import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
42
+ fileTag: any;
43
+ sectionIndex: any;
44
+ }, {
45
+ title: string;
46
+ url: string;
47
+ content: string;
48
+ } | {
49
+ error: string;
50
+ }, import("@ai-sdk/provider-utils").Context>>;
51
+ export declare function createAgentTools({ explorer, stateManager, ai, researcher, navigator, supervisor, withExperience }: AgentToolDeps): any;
52
+ export declare function isMajorPageChange(pageDiff: PageDiff): boolean;
53
+ export {};