explorbot 0.1.31 → 0.2.0

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 (288) hide show
  1. package/README.md +14 -14
  2. package/bin/explorbot-cli.ts +4 -3
  3. package/boat/doc-collector/src/ai/documentarian.ts +22 -27
  4. package/boat/doc-collector/src/ai/tools.ts +67 -10
  5. package/boat/doc-collector/src/docbot.ts +28 -7
  6. package/boat/doc-collector/src/docs-renderer.ts +19 -24
  7. package/boat/doc-collector/src/screenshots.ts +33 -1
  8. package/boat/doc-collector/src/state-diagram.ts +281 -0
  9. package/dist/bin/explorbot-cli.js +3 -3
  10. package/dist/boat/doc-collector/src/ai/documentarian.js +16 -6
  11. package/dist/boat/doc-collector/src/ai/tools.js +47 -10
  12. package/dist/boat/doc-collector/src/docbot.js +25 -6
  13. package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
  14. package/dist/boat/doc-collector/src/screenshots.js +29 -0
  15. package/dist/boat/doc-collector/src/state-diagram.js +200 -0
  16. package/dist/package.json +8 -6
  17. package/dist/src/action-result.d.ts +131 -0
  18. package/dist/src/action-result.js +82 -159
  19. package/dist/src/action.d.ts +43 -0
  20. package/dist/src/action.js +18 -106
  21. package/dist/src/activity.d.ts +26 -0
  22. package/dist/src/ai/agent.d.ts +3 -0
  23. package/dist/src/ai/captain/file-tools.d.ts +28 -0
  24. package/dist/src/ai/captain/idle-mode.d.ts +11 -0
  25. package/dist/src/ai/captain/mixin.d.ts +10 -0
  26. package/dist/src/ai/captain/test-mode.d.ts +13 -0
  27. package/dist/src/ai/captain/web-mode.d.ts +13 -0
  28. package/dist/src/ai/captain/web-mode.js +0 -7
  29. package/dist/src/ai/captain.d.ts +141 -0
  30. package/dist/src/ai/captain.js +4 -19
  31. package/dist/src/ai/conversation.d.ts +28 -0
  32. package/dist/src/ai/conversation.js +9 -28
  33. package/dist/src/ai/driller.d.ts +157 -0
  34. package/dist/src/ai/driller.js +3 -12
  35. package/dist/src/ai/experience-compactor.d.ts +53 -0
  36. package/dist/src/ai/experience-compactor.js +16 -33
  37. package/dist/src/ai/fisherman-tools.d.ts +90 -0
  38. package/dist/src/ai/fisherman-tools.js +1 -1
  39. package/dist/src/ai/fisherman.d.ts +29 -0
  40. package/dist/src/ai/fisherman.js +0 -3
  41. package/dist/src/ai/historian/codeceptjs.d.ts +17 -0
  42. package/dist/src/ai/historian/codeceptjs.js +3 -2
  43. package/dist/src/ai/historian/experience.d.ts +40 -0
  44. package/dist/src/ai/historian/mixin.d.ts +2 -0
  45. package/dist/src/ai/historian/playwright.d.ts +22 -0
  46. package/dist/src/ai/historian/playwright.js +3 -2
  47. package/dist/src/ai/historian/screencast.d.ts +32 -0
  48. package/dist/src/ai/historian/utils.d.ts +3 -0
  49. package/dist/src/ai/historian/utils.js +0 -1
  50. package/dist/src/ai/historian.d.ts +109 -0
  51. package/dist/src/ai/historian.js +1 -5
  52. package/dist/src/ai/navigator.d.ts +54 -0
  53. package/dist/src/ai/navigator.js +11 -46
  54. package/dist/src/ai/pilot.d.ts +81 -0
  55. package/dist/src/ai/pilot.js +6 -17
  56. package/dist/src/ai/planner/session-dedup.d.ts +12 -0
  57. package/dist/src/ai/planner/styles.d.ts +6 -0
  58. package/dist/src/ai/planner/subpages.d.ts +35 -0
  59. package/dist/src/ai/planner.d.ts +79 -0
  60. package/dist/src/ai/planner.js +49 -37
  61. package/dist/src/ai/provider.d.ts +53 -0
  62. package/dist/src/ai/provider.js +78 -130
  63. package/dist/src/ai/quartermaster.d.ts +56 -0
  64. package/dist/src/ai/quartermaster.js +4 -10
  65. package/dist/src/ai/rerunner.d.ts +48 -0
  66. package/dist/src/ai/rerunner.js +14 -22
  67. package/dist/src/ai/researcher/cache.d.ts +6 -0
  68. package/dist/src/ai/researcher/cache.js +11 -18
  69. package/dist/src/ai/researcher/coordinates.d.ts +56 -0
  70. package/dist/src/ai/researcher/deep-analysis.d.ts +74 -0
  71. package/dist/src/ai/researcher/deep-analysis.js +3 -6
  72. package/dist/src/ai/researcher/fingerprint-worker.d.ts +1 -0
  73. package/dist/src/ai/researcher/focus.d.ts +8 -0
  74. package/dist/src/ai/researcher/locators.d.ts +36 -0
  75. package/dist/src/ai/researcher/mixin.d.ts +2 -0
  76. package/dist/src/ai/researcher/parser.d.ts +35 -0
  77. package/dist/src/ai/researcher/parser.js +1 -1
  78. package/dist/src/ai/researcher/research-result.d.ts +15 -0
  79. package/dist/src/ai/researcher/research-result.js +0 -19
  80. package/dist/src/ai/researcher/sections.d.ts +19 -0
  81. package/dist/src/ai/researcher/sections.js +5 -1
  82. package/dist/src/ai/researcher.js +12 -53
  83. package/dist/src/ai/rules.d.ts +16 -0
  84. package/dist/src/ai/rules.js +22 -15
  85. package/dist/src/ai/session-analyst.d.ts +11 -0
  86. package/dist/src/ai/session-analyst.js +8 -5
  87. package/dist/src/ai/task-agent.d.ts +28 -0
  88. package/dist/src/ai/task-agent.js +2 -27
  89. package/dist/src/ai/tester.d.ts +101 -0
  90. package/dist/src/ai/tester.js +10 -61
  91. package/dist/src/ai/tools.d.ts +52 -0
  92. package/dist/src/ai/tools.js +119 -101
  93. package/dist/src/api/api-client.d.ts +17 -0
  94. package/dist/src/api/request-result.d.ts +44 -0
  95. package/dist/src/api/request-store.d.ts +29 -0
  96. package/dist/src/api/spec-reader.d.ts +5 -0
  97. package/dist/src/api/xhr-capture.d.ts +10 -0
  98. package/dist/src/browser-server.d.ts +10 -0
  99. package/dist/src/command-handler.d.ts +68 -0
  100. package/dist/src/commands/add-rule-command.d.ts +11 -0
  101. package/dist/src/commands/base-command.d.ts +26 -0
  102. package/dist/src/commands/clean-command.d.ts +12 -0
  103. package/dist/src/commands/compact-command.d.ts +20 -0
  104. package/dist/src/commands/compact-command.js +6 -6
  105. package/dist/src/commands/context-aria-command.d.ts +6 -0
  106. package/dist/src/commands/context-command.d.ts +11 -0
  107. package/dist/src/commands/context-command.js +1 -1
  108. package/dist/src/commands/context-data-command.d.ts +6 -0
  109. package/dist/src/commands/context-experience-command.d.ts +6 -0
  110. package/dist/src/commands/context-html-command.d.ts +6 -0
  111. package/dist/src/commands/context-knowledge-command.d.ts +6 -0
  112. package/dist/src/commands/context-knowledge-command.js +1 -1
  113. package/dist/src/commands/debug-command.d.ts +6 -0
  114. package/dist/src/commands/drill-command.d.ts +10 -0
  115. package/dist/src/commands/exit-command.d.ts +7 -0
  116. package/dist/src/commands/experience-command.d.ts +15 -0
  117. package/dist/src/commands/experience-command.js +1 -1
  118. package/dist/src/commands/explore-command.d.ts +51 -0
  119. package/dist/src/commands/freesail-command.d.ts +12 -0
  120. package/dist/src/commands/help-command.d.ts +6 -0
  121. package/dist/src/commands/index.d.ts +4 -0
  122. package/dist/src/commands/init-command.d.ts +7 -0
  123. package/dist/src/commands/knows-command.d.ts +10 -0
  124. package/dist/src/commands/knows-command.js +1 -1
  125. package/dist/src/commands/learn-command.d.ts +7 -0
  126. package/dist/src/commands/learn-command.js +3 -1
  127. package/dist/src/commands/navigate-command.d.ts +7 -0
  128. package/dist/src/commands/path-command.d.ts +10 -0
  129. package/dist/src/commands/plan-clear-command.d.ts +7 -0
  130. package/dist/src/commands/plan-command.d.ts +14 -0
  131. package/dist/src/commands/plan-edit-command.d.ts +7 -0
  132. package/dist/src/commands/plan-load-command.d.ts +7 -0
  133. package/dist/src/commands/plan-reload-command.d.ts +7 -0
  134. package/dist/src/commands/plan-save-command.d.ts +7 -0
  135. package/dist/src/commands/plans-command.d.ts +21 -0
  136. package/dist/src/commands/rerun-command.d.ts +7 -0
  137. package/dist/src/commands/research-command.d.ts +11 -0
  138. package/dist/src/commands/runs-command.d.ts +7 -0
  139. package/dist/src/commands/start-command.d.ts +7 -0
  140. package/dist/src/commands/status-command.d.ts +6 -0
  141. package/dist/src/commands/test-command.d.ts +11 -0
  142. package/dist/src/components/ActivityPane.d.ts +6 -0
  143. package/dist/src/components/AddKnowledge.d.ts +10 -0
  144. package/dist/src/components/AddKnowledge.js +3 -7
  145. package/dist/src/components/AddRule.d.ts +9 -0
  146. package/dist/src/components/App.d.ts +9 -0
  147. package/dist/src/components/App.js +2 -2
  148. package/dist/src/components/Autocomplete.d.ts +3 -0
  149. package/dist/src/components/InputPane.d.ts +13 -0
  150. package/dist/src/components/InputReadline.d.ts +17 -0
  151. package/dist/src/components/LogPane.d.ts +6 -0
  152. package/dist/src/components/PlanEditor.d.ts +9 -0
  153. package/dist/src/components/PlanPane.d.ts +13 -0
  154. package/dist/src/components/SessionTimer.d.ts +6 -0
  155. package/dist/src/components/StateTransitionPane.d.ts +8 -0
  156. package/dist/src/components/StatusPane.d.ts +4 -0
  157. package/dist/src/components/TaskPane.d.ts +9 -0
  158. package/dist/src/components/Welcome.d.ts +3 -0
  159. package/dist/src/components/WelcomeChecklist.d.ts +9 -0
  160. package/dist/src/components/WelcomeCommands.d.ts +6 -0
  161. package/dist/src/components/autocomplete-store.d.ts +10 -0
  162. package/dist/src/components/parse-keypress.d.ts +12 -0
  163. package/dist/src/config.d.ts +253 -0
  164. package/dist/src/config.js +12 -0
  165. package/dist/src/execution-controller.d.ts +25 -0
  166. package/dist/src/experience-tracker.d.ts +112 -0
  167. package/dist/src/experience-tracker.js +83 -155
  168. package/dist/src/explorbot.d.ts +106 -0
  169. package/dist/src/explorbot.js +17 -51
  170. package/dist/src/explorer.d.ts +129 -0
  171. package/dist/src/explorer.js +31 -77
  172. package/dist/src/index.d.ts +6 -0
  173. package/dist/src/index.js +2 -55
  174. package/dist/src/knowledge-tracker.d.ts +33 -0
  175. package/dist/src/knowledge-tracker.js +47 -47
  176. package/dist/src/observability.d.ts +13 -0
  177. package/dist/src/observability.js +2 -1
  178. package/dist/src/playwright-recorder.d.ts +31 -0
  179. package/dist/src/reporter.d.ts +34 -0
  180. package/dist/src/state-manager.d.ts +160 -0
  181. package/dist/src/state-manager.js +10 -136
  182. package/dist/src/stats.d.ts +24 -0
  183. package/dist/src/suite.d.ts +24 -0
  184. package/dist/src/test-plan.d.ts +166 -0
  185. package/dist/src/utils/aria.d.ts +25 -0
  186. package/dist/src/utils/aria.js +4 -1
  187. package/dist/src/utils/browser-errors.d.ts +8 -0
  188. package/dist/src/utils/cache.d.ts +15 -0
  189. package/dist/src/utils/cache.js +34 -0
  190. package/dist/src/utils/cli-name.d.ts +1 -0
  191. package/dist/src/utils/code-extractor.d.ts +1 -0
  192. package/dist/src/utils/context-formatter.d.ts +31 -0
  193. package/dist/src/utils/context-formatter.js +15 -27
  194. package/dist/src/utils/error-page.d.ts +12 -0
  195. package/dist/src/utils/expandable.d.ts +4 -0
  196. package/dist/src/utils/hooks-runner.d.ts +15 -0
  197. package/dist/src/utils/html-diff.d.ts +19 -0
  198. package/dist/src/utils/html.d.ts +129 -0
  199. package/dist/src/utils/html.js +71 -0
  200. package/dist/src/utils/log-filters.d.ts +6 -0
  201. package/dist/src/utils/logger.d.ts +35 -0
  202. package/dist/src/utils/loop.d.ts +35 -0
  203. package/dist/src/utils/markdown-files.d.ts +10 -0
  204. package/dist/src/utils/markdown-files.js +21 -0
  205. package/dist/src/utils/markdown-parser.d.ts +17 -0
  206. package/dist/src/utils/markdown-query.d.ts +46 -0
  207. package/dist/src/utils/markdown-query.js +14 -1
  208. package/dist/src/utils/markdown-terminal.d.ts +1 -0
  209. package/dist/src/utils/next-steps.d.ts +11 -0
  210. package/dist/src/utils/page-readiness.d.ts +7 -0
  211. package/dist/src/utils/page-readiness.js +1 -1
  212. package/dist/src/utils/research-parser.d.ts +1 -0
  213. package/dist/src/utils/retry.d.ts +8 -0
  214. package/dist/src/utils/rules-loader.d.ts +12 -0
  215. package/dist/src/utils/secrets.d.ts +4 -0
  216. package/dist/src/utils/secrets.js +28 -0
  217. package/dist/src/utils/step-analyzer.d.ts +11 -0
  218. package/dist/src/utils/strings.d.ts +5 -0
  219. package/dist/src/utils/strings.js +10 -0
  220. package/dist/src/utils/test-files.d.ts +3 -0
  221. package/dist/src/utils/test-plan-markdown.d.ts +9 -0
  222. package/dist/src/utils/throttle.d.ts +2 -0
  223. package/dist/src/utils/unique-names.d.ts +2 -0
  224. package/dist/src/utils/url-matcher.d.ts +6 -0
  225. package/dist/src/utils/web-element.d.ts +45 -0
  226. package/dist/src/utils/web-element.js +3 -0
  227. package/dist/src/utils/web-sandbox.d.ts +4 -0
  228. package/dist/src/utils/web-sandbox.js +37 -0
  229. package/dist/src/utils/xpath.d.ts +17 -0
  230. package/package.json +8 -6
  231. package/src/action-result.ts +80 -168
  232. package/src/action.ts +16 -123
  233. package/src/ai/captain/web-mode.ts +0 -7
  234. package/src/ai/captain.ts +5 -17
  235. package/src/ai/conversation.ts +9 -32
  236. package/src/ai/driller.ts +3 -16
  237. package/src/ai/experience-compactor.ts +16 -35
  238. package/src/ai/fisherman-tools.ts +1 -1
  239. package/src/ai/fisherman.ts +0 -4
  240. package/src/ai/historian/codeceptjs.ts +4 -2
  241. package/src/ai/historian/playwright.ts +4 -2
  242. package/src/ai/historian/utils.ts +0 -1
  243. package/src/ai/historian.ts +4 -7
  244. package/src/ai/navigator.ts +13 -49
  245. package/src/ai/pilot.ts +6 -16
  246. package/src/ai/planner.ts +56 -44
  247. package/src/ai/provider.ts +86 -139
  248. package/src/ai/quartermaster.ts +34 -41
  249. package/src/ai/rerunner.ts +17 -21
  250. package/src/ai/researcher/cache.ts +10 -16
  251. package/src/ai/researcher/deep-analysis.ts +3 -6
  252. package/src/ai/researcher/parser.ts +1 -1
  253. package/src/ai/researcher/research-result.ts +0 -18
  254. package/src/ai/researcher/sections.ts +5 -1
  255. package/src/ai/researcher.ts +11 -59
  256. package/src/ai/rules.ts +24 -13
  257. package/src/ai/session-analyst.ts +8 -5
  258. package/src/ai/task-agent.ts +3 -30
  259. package/src/ai/tester.ts +12 -73
  260. package/src/ai/tools.ts +119 -106
  261. package/src/commands/compact-command.ts +7 -8
  262. package/src/commands/context-command.ts +1 -1
  263. package/src/commands/context-knowledge-command.ts +1 -1
  264. package/src/commands/experience-command.ts +1 -1
  265. package/src/commands/knows-command.ts +1 -1
  266. package/src/commands/learn-command.ts +3 -1
  267. package/src/components/AddKnowledge.tsx +5 -7
  268. package/src/components/App.tsx +2 -2
  269. package/src/config.ts +12 -0
  270. package/src/experience-tracker.ts +90 -152
  271. package/src/explorbot.ts +17 -51
  272. package/src/explorer.ts +34 -81
  273. package/src/index.ts +9 -0
  274. package/src/knowledge-tracker.ts +49 -50
  275. package/src/observability.ts +2 -1
  276. package/src/state-manager.ts +15 -164
  277. package/src/utils/aria.ts +11 -2
  278. package/src/utils/cache.ts +40 -0
  279. package/src/utils/context-formatter.ts +12 -15
  280. package/src/utils/html.ts +79 -0
  281. package/src/utils/markdown-files.ts +30 -0
  282. package/src/utils/markdown-query.ts +15 -1
  283. package/src/utils/page-readiness.ts +1 -1
  284. package/src/utils/secrets.ts +28 -0
  285. package/src/utils/strings.ts +12 -0
  286. package/src/utils/web-element.ts +4 -0
  287. package/src/utils/web-sandbox.ts +43 -0
  288. package/src/index.tsx +0 -62
package/src/explorer.ts CHANGED
@@ -14,17 +14,17 @@ import { visuallyAnnotateContainers } from './ai/researcher/coordinates.ts';
14
14
  import { RequestStore } from './api/request-store.ts';
15
15
  import { XhrCapture } from './api/xhr-capture.ts';
16
16
  import type { ExplorbotConfig } from './config.js';
17
- import { ConfigParser, outputPath } from './config.js';
18
- import type { UserResolveFunction } from './explorbot.js';
17
+ import { ConfigParser } from './config.js';
18
+ import type { ExperienceTracker } from './experience-tracker.js';
19
19
  import { KnowledgeTracker } from './knowledge-tracker.js';
20
20
  import { PlaywrightRecorder } from './playwright-recorder.ts';
21
21
  import { Reporter } from './reporter.ts';
22
22
  import { StateManager } from './state-manager.js';
23
23
  import { Test, TestResult } from './test-plan.ts';
24
- import { BrowserRecoveryError, isFatalBrowserError, isNavigationTransitionError } from './utils/browser-errors.ts';
24
+ import { BrowserRecoveryError, browserErrorMessage, isFatalBrowserError, isNavigationTransitionError } from './utils/browser-errors.ts';
25
25
  import { ELEMENT_EXTRACTION_CONFIG, getElementDataExtractorSource } from './utils/html.ts';
26
26
  import { createDebug, log, tag } from './utils/logger.js';
27
- import { waitForPageReadiness } from './utils/page-readiness.ts';
27
+ import { sleep, waitForPageReadiness } from './utils/page-readiness.ts';
28
28
  import { WebElement } from './utils/web-element.ts';
29
29
 
30
30
  declare global {
@@ -64,7 +64,6 @@ class Explorer {
64
64
  private stateManager!: StateManager;
65
65
  private knowledgeTracker!: KnowledgeTracker;
66
66
  config: ExplorbotConfig;
67
- private userResolveFn: UserResolveFunction | null = null;
68
67
  private options?: { show?: boolean; headless?: boolean; incognito?: boolean; session?: string };
69
68
  private reporter!: Reporter;
70
69
  private otherTabs: TabInfo[] = [];
@@ -77,13 +76,13 @@ class Explorer {
77
76
  private testConsoleHandler: ((message: any) => void) | null = null;
78
77
  private testDialogHandler: ((dialog: any) => void) | null = null;
79
78
 
80
- constructor(config: ExplorbotConfig, aiProvider: AIProvider, options?: { show?: boolean; headless?: boolean; incognito?: boolean; session?: string }) {
79
+ constructor(config: ExplorbotConfig, aiProvider: AIProvider, options: { show?: boolean; headless?: boolean; incognito?: boolean; session?: string } | undefined, experienceTracker: ExperienceTracker, knowledgeTracker: KnowledgeTracker) {
81
80
  this.config = config;
82
81
  this.aiProvider = aiProvider;
83
82
  this.options = options;
84
83
  this.initializeContainer();
85
- this.stateManager = new StateManager({ incognito: this.options?.incognito });
86
- this.knowledgeTracker = new KnowledgeTracker();
84
+ this.knowledgeTracker = knowledgeTracker;
85
+ this.stateManager = new StateManager(experienceTracker, knowledgeTracker);
87
86
  this.reporter = new Reporter(config.reporter, this.stateManager);
88
87
  }
89
88
 
@@ -91,8 +90,7 @@ class Explorer {
91
90
  try {
92
91
  // Use project root for output directory, not current working directory
93
92
  const configParser = ConfigParser.getInstance();
94
- const configPath = configParser.getConfigPath();
95
- const projectRoot = configPath ? path.dirname(configPath) : process.cwd();
93
+ const projectRoot = configParser.getProjectRoot();
96
94
  (global as any).output_dir = path.join(projectRoot, 'output', 'states');
97
95
  (global as any).codecept_dir = projectRoot;
98
96
 
@@ -155,8 +153,7 @@ class Explorer {
155
153
  };
156
154
 
157
155
  if (this.config.stepsFile) {
158
- const configPath = ConfigParser.getInstance().getConfigPath();
159
- const projectRoot = configPath ? path.dirname(configPath) : process.cwd();
156
+ const projectRoot = ConfigParser.getInstance().getProjectRoot();
160
157
  codeceptConfig.include = { I: path.resolve(projectRoot, this.config.stepsFile) };
161
158
  }
162
159
 
@@ -164,17 +161,9 @@ class Explorer {
164
161
  }
165
162
 
166
163
  public getConfig(): ExplorbotConfig {
167
- if (!this.config) {
168
- throw new Error('Configuration not loaded. Call run() first.');
169
- }
170
164
  return this.config;
171
165
  }
172
166
 
173
- public getConfigPath(): string | null {
174
- const configParser = ConfigParser.getInstance();
175
- return configParser.getConfigPath();
176
- }
177
-
178
167
  public getAIProvider(): AIProvider {
179
168
  return this.aiProvider;
180
169
  }
@@ -183,10 +172,6 @@ class Explorer {
183
172
  return this.stateManager;
184
173
  }
185
174
 
186
- public getCurrentUrl(): string {
187
- return this.stateManager.getCurrentState()!.url || '?';
188
- }
189
-
190
175
  public getKnowledgeTracker(): KnowledgeTracker {
191
176
  return this.knowledgeTracker;
192
177
  }
@@ -227,10 +212,6 @@ class Explorer {
227
212
  return;
228
213
  }
229
214
 
230
- if (!this.config) {
231
- await this.initializeContainer();
232
- }
233
-
234
215
  await codeceptjs.recorder.start();
235
216
  await codeceptjs.container.started(null);
236
217
  storeListener();
@@ -359,8 +340,8 @@ class Explorer {
359
340
  return this.runWithBrowserRecovery('executeAction', () => this.createAction().execute(code));
360
341
  }
361
342
 
362
- async attemptAction(code: string, originalMessage?: string, experience = true): Promise<boolean> {
363
- return this.runWithBrowserRecovery('attemptAction', () => this.createAction().attempt(code, originalMessage, experience));
343
+ async attemptAction(code: string, originalMessage?: string): Promise<boolean> {
344
+ return this.runWithBrowserRecovery('attemptAction', () => this.createAction().attempt(code, originalMessage));
364
345
  }
365
346
 
366
347
  getPlaywrightRecorder(): PlaywrightRecorder {
@@ -388,7 +369,7 @@ class Explorer {
388
369
  try {
389
370
  await action.execute(`I.amOnPage(${serializedUrl})`);
390
371
  } catch (err) {
391
- const msg = err instanceof Error ? err.message : String(err);
372
+ const msg = browserErrorMessage(err);
392
373
  if (!RECOVERABLE_NAVIGATION_ERRORS.test(msg)) throw err;
393
374
  tag('warning').log(`Navigation warning (continuing after load): ${msg.split('\n')[0]}`);
394
375
  await this.waitForPageReadiness();
@@ -437,7 +418,7 @@ class Explorer {
437
418
  return result;
438
419
  } catch (error) {
439
420
  if (this.isFatalBrowserError(error)) {
440
- tag('warning').log(`getEidxInContainer: ${error instanceof Error ? error.message : error}`);
421
+ tag('warning').log(`getEidxInContainer: ${browserErrorMessage(error)}`);
441
422
  await this.recoverFromBrowserError();
442
423
  }
443
424
  return [];
@@ -452,18 +433,13 @@ class Explorer {
452
433
  return await el.first().getAttribute(ELEMENT_EXTRACTION_CONFIG.attrs.eidx);
453
434
  } catch (error) {
454
435
  if (this.isFatalBrowserError(error)) {
455
- tag('warning').log(`getEidxByLocator: ${error instanceof Error ? error.message : error}`);
436
+ tag('warning').log(`getEidxByLocator: ${browserErrorMessage(error)}`);
456
437
  await this.recoverFromBrowserError();
457
438
  }
458
439
  return null;
459
440
  }
460
441
  }
461
442
 
462
- async reload() {
463
- await this.closeOtherTabs();
464
- await this.playwrightHelper.page.reload();
465
- }
466
-
467
443
  private resolveBrowserUrl(url?: string): string | null {
468
444
  if (!url) return null;
469
445
  try {
@@ -508,7 +484,7 @@ class Explorer {
508
484
  await this.playwrightHelper.page.reload({ waitUntil: 'domcontentloaded', timeout: 10000 });
509
485
  return this.waitForPageReadiness();
510
486
  } catch (err) {
511
- tag('error').log(`Browser recovery failed: ${err instanceof Error ? err.message : err}`);
487
+ tag('error').log(`Browser recovery failed: ${browserErrorMessage(err)}`);
512
488
  return false;
513
489
  }
514
490
  }
@@ -552,7 +528,7 @@ class Explorer {
552
528
  tag('success').log('Browser restarted');
553
529
  return true;
554
530
  } catch (err) {
555
- tag('error').log(`Browser restart failed: ${err instanceof Error ? err.message : err}`);
531
+ tag('error').log(`Browser restart failed: ${browserErrorMessage(err)}`);
556
532
  return false;
557
533
  }
558
534
  }
@@ -584,7 +560,7 @@ class Explorer {
584
560
  return await page.evaluate(() => window.top !== window.self);
585
561
  } catch (error) {
586
562
  if (this.isFatalBrowserError(error)) {
587
- tag('warning').log(`isInsideIframe: ${error instanceof Error ? error.message : error}`);
563
+ tag('warning').log(`isInsideIframe: ${browserErrorMessage(error)}`);
588
564
  await this.recoverFromBrowserError();
589
565
  }
590
566
  return false;
@@ -608,10 +584,6 @@ class Explorer {
608
584
  this.otherTabs = [];
609
585
  }
610
586
 
611
- setUserResolve(userResolveFn: UserResolveFunction): void {
612
- this.userResolveFn = userResolveFn;
613
- }
614
-
615
587
  private listenToStateChanged(): void {
616
588
  if (!this.playwrightHelper) {
617
589
  debugLog('Playwright helper not available for state monitoring');
@@ -727,17 +699,22 @@ class Explorer {
727
699
 
728
700
  this.watchActiveTestPage();
729
701
 
730
- codeceptjs.event.dispatcher.emit('test.before', codeceptjsTest);
731
- codeceptjs.event.dispatcher.emit('test.start', codeceptjsTest);
732
- codeceptjs.event.dispatcher.on('step.passed', (step: any) => stepHandler(step, 'passed'));
733
- codeceptjs.event.dispatcher.on('step.failed', (step: any, error: any) => {
702
+ const onStepPassed = (step: any) => stepHandler(step, 'passed');
703
+ const onStepFailed = (step: any, error: any) => {
734
704
  stepHandler(step, 'failed', error?.message || String(error), error?.stack);
735
- });
736
- codeceptjs.event.dispatcher.on('test.after', () => {
737
- codeceptjs.event.dispatcher.off('step.passed', stepHandler);
738
- codeceptjs.event.dispatcher.off('step.failed', stepHandler);
705
+ };
706
+ const onTestAfter = () => {
707
+ codeceptjs.event.dispatcher.off('step.passed', onStepPassed);
708
+ codeceptjs.event.dispatcher.off('step.failed', onStepFailed);
709
+ codeceptjs.event.dispatcher.off('test.after', onTestAfter);
739
710
  this.unwatchActiveTestPages();
740
- });
711
+ };
712
+
713
+ codeceptjs.event.dispatcher.emit('test.before', codeceptjsTest);
714
+ codeceptjs.event.dispatcher.emit('test.start', codeceptjsTest);
715
+ codeceptjs.event.dispatcher.on('step.passed', onStepPassed);
716
+ codeceptjs.event.dispatcher.on('step.failed', onStepFailed);
717
+ codeceptjs.event.dispatcher.on('test.after', onTestAfter);
741
718
 
742
719
  return true;
743
720
  }
@@ -755,12 +732,8 @@ class Explorer {
755
732
  return true;
756
733
  }
757
734
 
758
- async ensureActiveTestPageAvailable(): Promise<boolean> {
759
- return this.ensurePageAvailable();
760
- }
761
-
762
735
  async handleExecutionError(error: unknown): Promise<BrowserExecutionErrorResult> {
763
- const message = error instanceof Error ? error.message : String(error);
736
+ const message = browserErrorMessage(error);
764
737
  tag('error').log(`Browser execution error: ${message}`);
765
738
 
766
739
  if (error instanceof Error && error.name === 'AbortError') {
@@ -868,33 +841,13 @@ class Explorer {
868
841
  this.observedTestPages.clear();
869
842
  }
870
843
 
871
- async hasPlaywrightLocator(locatorFn: (page: any) => any, opts: { multiple?: boolean; contents?: boolean; success?: (locator: any) => Promise<void> | void } = {}): Promise<boolean> {
872
- try {
873
- const pwLocator = locatorFn(this.playwrightHelper.page);
874
- const count = await pwLocator.count();
875
- if (opts.multiple ? count === 0 : count !== 1) return false;
876
- if (opts.contents) {
877
- const html = await pwLocator.first().innerHTML();
878
- if (!html?.trim()) return false;
879
- }
880
- if (opts.success) await opts.success(pwLocator);
881
- return true;
882
- } catch (error) {
883
- if (this.isFatalBrowserError(error)) {
884
- tag('warning').log(`hasPlaywrightLocator: ${error instanceof Error ? error.message : error}`);
885
- await this.recoverFromBrowserError();
886
- }
887
- return false;
888
- }
889
- }
890
-
891
844
  async playwrightLocatorCount(locatorFn: (page: any) => any): Promise<number> {
892
845
  try {
893
846
  const pwLocator = locatorFn(this.playwrightHelper.page);
894
847
  return await pwLocator.count();
895
848
  } catch (error) {
896
849
  if (this.isFatalBrowserError(error)) {
897
- tag('warning').log(`playwrightLocatorCount: ${error instanceof Error ? error.message : error}`);
850
+ tag('warning').log(`playwrightLocatorCount: ${browserErrorMessage(error)}`);
898
851
  await this.recoverFromBrowserError();
899
852
  }
900
853
  throw error;
@@ -916,7 +869,7 @@ class Explorer {
916
869
 
917
870
  this.stateManager.updateStateFromBasic(newUrl, newTitle, 'navigation');
918
871
 
919
- await new Promise((resolve) => setTimeout(resolve, 500));
872
+ await sleep(500);
920
873
  });
921
874
  }
922
875
 
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { ExplorBot } from './explorbot.ts';
2
+ export type { ExplorBotOptions, UserResolveFunction } from './explorbot.ts';
3
+
4
+ export { Plan, Test, TestResult, TestStatus } from './test-plan.ts';
5
+ export type { Note, StepData, TestResultType, TestStatusType } from './test-plan.ts';
6
+
7
+ export type { Link, StateTransition, WebPageState } from './state-manager.ts';
8
+
9
+ export type { AIConfig, ExplorbotConfig, PlaywrightConfig } from './config.ts';
@@ -1,14 +1,19 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
2
- import { dirname, join, resolve } from 'node:path';
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import dedent from 'dedent';
3
4
  import matter from 'gray-matter';
4
5
  import { ActionResult } from './action-result.js';
5
6
  import { ConfigParser } from './config.js';
6
7
  import { getCliName } from './utils/cli-name.ts';
7
- import { createDebug } from './utils/logger.js';
8
+ import { createDebug, pluralize, tag } from './utils/logger.js';
9
+ import { loadMarkdownFiles } from './utils/markdown-files.js';
10
+ import { mdq } from './utils/markdown-query.js';
11
+ import { isSecretName, registerSecret } from './utils/secrets.js';
12
+ import { slugify } from './utils/strings.js';
8
13
 
9
14
  const debugLog = createDebug('explorbot:knowledge-tracker');
10
15
 
11
- interface Knowledge {
16
+ export interface Knowledge {
12
17
  filePath: string;
13
18
  url: string;
14
19
  content: string;
@@ -23,14 +28,7 @@ export class KnowledgeTracker {
23
28
  constructor() {
24
29
  const configParser = ConfigParser.getInstance();
25
30
  const config = configParser.getConfig();
26
- const configPath = configParser.getConfigPath();
27
-
28
- if (configPath) {
29
- const projectRoot = dirname(configPath);
30
- this.knowledgeDir = join(projectRoot, config.dirs?.knowledge || 'knowledge');
31
- } else {
32
- this.knowledgeDir = config.dirs?.knowledge || 'knowledge';
33
- }
31
+ this.knowledgeDir = configParser.resolveProjectDir(config.dirs?.knowledge || 'knowledge');
34
32
 
35
33
  if (!existsSync(this.knowledgeDir)) {
36
34
  mkdirSync(this.knowledgeDir, { recursive: true });
@@ -42,29 +40,13 @@ export class KnowledgeTracker {
42
40
 
43
41
  this.knowledgeFiles = [];
44
42
 
45
- if (!existsSync(this.knowledgeDir)) {
46
- return;
47
- }
48
-
49
- const files = readdirSync(this.knowledgeDir)
50
- .filter((file) => file.endsWith('.md'))
51
- .map((file) => join(this.knowledgeDir, file));
52
-
53
- for (const filePath of files) {
54
- try {
55
- const fileContent = readFileSync(filePath, 'utf8');
56
- const parsed = matter(fileContent);
57
- const urlPattern = parsed.data.url || parsed.data.path || '*';
58
-
59
- this.knowledgeFiles.push({
60
- filePath,
61
- url: urlPattern,
62
- content: this.interpolateVars(parsed.content),
63
- ...parsed.data,
64
- });
65
- } catch (error) {
66
- // Skip invalid files
67
- }
43
+ for (const entry of loadMarkdownFiles(this.knowledgeDir, { recursive: true })) {
44
+ this.knowledgeFiles.push({
45
+ filePath: entry.filePath,
46
+ url: entry.data.url || entry.data.path || '*',
47
+ content: this.interpolateVars(entry.content),
48
+ ...entry.data,
49
+ });
68
50
  }
69
51
 
70
52
  this.isLoaded = true;
@@ -78,25 +60,40 @@ export class KnowledgeTracker {
78
60
  });
79
61
  }
80
62
 
63
+ renderRelevantKnowledge(state: ActionResult): string {
64
+ const knowledgeFiles = this.getRelevantKnowledge(state);
65
+ if (knowledgeFiles.length === 0) return '';
66
+
67
+ const knowledgeContent = knowledgeFiles
68
+ .map((k) => k.content)
69
+ .filter((k) => !!k)
70
+ .join('\n\n');
71
+
72
+ tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
73
+ return dedent`
74
+ <knowledge>
75
+ Here is relevant knowledge for this page:
76
+
77
+ ${knowledgeContent}
78
+ </knowledge>
79
+ `;
80
+ }
81
+
81
82
  addKnowledge(urlPattern: string, description: string): { filename: string; filePath: string; isNewFile: boolean } {
82
83
  const configParser = ConfigParser.getInstance();
83
- const config = configParser.getConfig();
84
84
  const configPath = configParser.getConfigPath();
85
85
 
86
86
  if (!configPath) {
87
87
  throw new Error(`No explorbot configuration found. Please run "${getCliName()} init" first.`);
88
88
  }
89
89
 
90
- const projectRoot = dirname(configPath);
91
- const knowledgeDir = join(projectRoot, config.dirs?.knowledge || 'knowledge');
92
-
93
- if (!existsSync(knowledgeDir)) {
94
- mkdirSync(knowledgeDir, { recursive: true });
90
+ if (!existsSync(this.knowledgeDir)) {
91
+ mkdirSync(this.knowledgeDir, { recursive: true });
95
92
  }
96
93
 
97
94
  const normalizedUrl = this.normalizeUrl(urlPattern);
98
95
  const filename = this.generateFilename(normalizedUrl);
99
- const filePath = join(knowledgeDir, filename);
96
+ const filePath = join(this.knowledgeDir, filename);
100
97
 
101
98
  const isNewFile = !existsSync(filePath);
102
99
 
@@ -127,6 +124,8 @@ export class KnowledgeTracker {
127
124
  writeFileSync(filePath, fileContent, 'utf8');
128
125
  }
129
126
 
127
+ this.isLoaded = false;
128
+
130
129
  return { filename, filePath, isNewFile };
131
130
  }
132
131
 
@@ -138,9 +137,14 @@ export class KnowledgeTracker {
138
137
  const namespace = expr.slice(0, dotIndex);
139
138
  const key = expr.slice(dotIndex + 1);
140
139
 
141
- if (namespace === 'env') return process.env[key] ?? '';
140
+ if (namespace === 'env') {
141
+ const value = process.env[key] ?? '';
142
+ if (isSecretName(key)) registerSecret(value);
143
+ return value;
144
+ }
142
145
 
143
146
  if (namespace === 'config') {
147
+ if (isSecretName(key)) return '';
144
148
  const config = ConfigParser.getInstance().getConfig();
145
149
  const value = key.split('.').reduce((obj: any, k) => obj?.[k], config);
146
150
  if (value !== undefined && typeof value !== 'object') return String(value);
@@ -152,12 +156,7 @@ export class KnowledgeTracker {
152
156
  }
153
157
 
154
158
  private generateFilename(url: string): string {
155
- let filename = url
156
- .replace(/https?:\/\//g, '')
157
- .replace(/[^a-zA-Z0-9_]/g, '_')
158
- .replace(/_+/g, '_')
159
- .replace(/^_|_$/g, '')
160
- .toLowerCase();
159
+ let filename = slugify(url.replace(/https?:\/\//g, ''));
161
160
 
162
161
  if (!filename || filename === '*') {
163
162
  filename = 'general';
@@ -188,7 +187,7 @@ export class KnowledgeTracker {
188
187
 
189
188
  return this.knowledgeFiles.map((knowledge) => {
190
189
  const content = knowledge.content.trim();
191
- const firstLine = content.split('\n')[0]?.trim() || '';
190
+ const firstLine = mdq(content).meta()[0]?.text.split('\n')[0]?.trim() || '';
192
191
  return {
193
192
  url: knowledge.url,
194
193
  firstLine,
@@ -1,4 +1,5 @@
1
1
  import { type Span, trace } from '@opentelemetry/api';
2
+ import { redactSecrets } from './utils/secrets.js';
2
3
 
3
4
  type TelemetryMetadata = Record<string, unknown>;
4
5
 
@@ -84,7 +85,7 @@ function buildRootSpanAttributes(name: string, metadata: TelemetryMetadata): Rec
84
85
  attributes['langfuse.trace.tags'] = metadata.tags as string[];
85
86
  }
86
87
  if (metadata.input !== undefined) {
87
- attributes['langfuse.trace.input'] = JSON.stringify(metadata.input);
88
+ attributes['langfuse.trace.input'] = redactSecrets(JSON.stringify(metadata.input));
88
89
  }
89
90
 
90
91
  return attributes;