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
@@ -142,7 +142,7 @@ export function App({ explorBot, initialShowInput = false, exitOnEmptyInput = fa
142
142
  if (!mounted) return;
143
143
  setChecklistData({
144
144
  config: explorBot.getConfig(),
145
- knowledgeTracker: explorBot.getKnowledgeTracker(),
145
+ knowledgeTracker: explorBot.knowledgeTracker(),
146
146
  });
147
147
  setShowWelcome(true);
148
148
  setShowInput(true);
@@ -334,7 +334,7 @@ export function App({ explorBot, initialShowInput = false, exitOnEmptyInput = fa
334
334
  {showSessionTimer && <SessionTimer startedAt={sessionStartedAtRef.current} />}
335
335
  </Box>
336
336
 
337
- {showWelcome && <WelcomeCommands hasKnowledge={explorBot.getKnowledgeTracker().listAllKnowledge().length > 0} />}
337
+ {showWelcome && <WelcomeCommands hasKnowledge={explorBot.knowledgeTracker().listAllKnowledge().length > 0} />}
338
338
  {showInput && <Box height={1} />}
339
339
  {interruptPrompt && showInput && (
340
340
  <Box paddingX={1}>
package/src/config.ts CHANGED
@@ -362,6 +362,18 @@ export class ConfigParser {
362
362
  return path.join(path.dirname(configPath), config.dirs?.output || 'output');
363
363
  }
364
364
 
365
+ public getProjectRoot(): string {
366
+ const configPath = this.getConfigPath();
367
+ if (configPath) return path.dirname(configPath);
368
+ return process.cwd();
369
+ }
370
+
371
+ public resolveProjectDir(relativeDir: string): string {
372
+ const configPath = this.getConfigPath();
373
+ if (!configPath) return relativeDir;
374
+ return path.join(path.dirname(configPath), relativeDir);
375
+ }
376
+
365
377
  public getStatesDir(): string {
366
378
  return outputPath('states');
367
379
  }
@@ -1,13 +1,14 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
2
- import { basename, dirname, join } from 'node:path';
1
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
3
  import matter from 'gray-matter';
4
- import { type Tokens, marked } from 'marked';
5
4
  import type { ActionResult } from './action-result.js';
6
5
  import { ConfigParser } from './config.js';
7
6
  import { KnowledgeTracker } from './knowledge-tracker.js';
8
7
  import type { WebPageState } from './state-manager.js';
9
- import { createDebug, tag } from './utils/logger.js';
8
+ import { createDebug, pluralize, tag } from './utils/logger.js';
9
+ import { loadMarkdownFiles } from './utils/markdown-files.js';
10
10
  import { mdq } from './utils/markdown-query.js';
11
+ import { redactSecrets } from './utils/secrets.js';
11
12
  import { isNonReusableCode } from './utils/step-analyzer.ts';
12
13
  import { extractStatePath } from './utils/url-matcher.js';
13
14
 
@@ -38,20 +39,14 @@ export class ExperienceTracker {
38
39
  private disabled: boolean;
39
40
  private knowledgeTracker: KnowledgeTracker;
40
41
 
41
- constructor(options: { disabled?: boolean } = {}) {
42
+ constructor(knowledgeTracker: KnowledgeTracker, options: { disabled?: boolean } = {}) {
42
43
  const configParser = ConfigParser.getInstance();
43
44
  const config = configParser.getConfig();
44
- const configPath = configParser.getConfigPath();
45
45
  this.disabled = options.disabled ?? false;
46
- this.knowledgeTracker = new KnowledgeTracker();
46
+ this.knowledgeTracker = knowledgeTracker;
47
47
 
48
48
  // Resolve experience directory relative to the config file location (project root)
49
- if (configPath) {
50
- const projectRoot = dirname(configPath);
51
- this.experienceDir = join(projectRoot, config.dirs?.experience || 'experience');
52
- } else {
53
- this.experienceDir = config.dirs?.experience || 'experience';
54
- }
49
+ this.experienceDir = configParser.resolveProjectDir(config.dirs?.experience || 'experience');
55
50
 
56
51
  if (!this.disabled) {
57
52
  this.ensureDirectory(this.experienceDir);
@@ -98,7 +93,10 @@ export class ExperienceTracker {
98
93
  }
99
94
 
100
95
  readExperienceFile(stateHash: string): { content: string; data: any } {
101
- const filePath = this.getExperienceFilePath(stateHash);
96
+ return this.readExperienceFileAt(this.getExperienceFilePath(stateHash));
97
+ }
98
+
99
+ private readExperienceFileAt(filePath: string): { content: string; data: any } {
102
100
  const fileContent = readFileSync(filePath, 'utf8');
103
101
  const { content, data } = matter(fileContent);
104
102
  return { content, data };
@@ -109,25 +107,10 @@ export class ExperienceTracker {
109
107
  return;
110
108
  }
111
109
  const filePath = this.getExperienceFilePath(stateHash);
112
- const fileContent = matter.stringify(content, frontmatter || {});
110
+ const fileContent = matter.stringify(redactSecrets(content), frontmatter || {});
113
111
  writeFileSync(filePath, fileContent, 'utf8');
114
112
  }
115
113
 
116
- hasRecentExperience(stateHash: string, prefix = ''): boolean {
117
- if (this.disabled) {
118
- return false;
119
- }
120
- if (prefix) {
121
- stateHash = `${prefix}_${stateHash}`;
122
- }
123
- const filePath = this.getExperienceFilePath(stateHash);
124
- if (!existsSync(filePath)) {
125
- return false;
126
- }
127
- const stats = statSync(filePath);
128
- return stats.mtime.getTime() > Date.now() - 1000 * 60 * 60 * 24;
129
- }
130
-
131
114
  private getExperienceFilePath(stateHash: string): string {
132
115
  return join(this.experienceDir, `${stateHash}.md`);
133
116
  }
@@ -175,7 +158,12 @@ export class ExperienceTracker {
175
158
  this.ensureExperienceFile(state);
176
159
  const stateHash = state.getStateHash();
177
160
  const { content, data } = this.readExperienceFile(stateHash);
178
- if (content.includes(action.code)) {
161
+ if (
162
+ mdq(content)
163
+ .query('code')
164
+ .meta()
165
+ .some((m) => m.text.trim() === action.code.trim())
166
+ ) {
179
167
  debugLog('Skipping duplicate action', action.code);
180
168
  return;
181
169
  }
@@ -203,7 +191,12 @@ export class ExperienceTracker {
203
191
  const stateHash = state.getStateHash();
204
192
  const { content, data } = this.readExperienceFile(stateHash);
205
193
 
206
- if (content.includes(body)) {
194
+ if (
195
+ mdq(content)
196
+ .query('section2')
197
+ .each()
198
+ .some((s) => s.text().trim() === body.trim())
199
+ ) {
207
200
  debugLog('Skipping duplicate flow body');
208
201
  return;
209
202
  }
@@ -230,25 +223,11 @@ export class ExperienceTracker {
230
223
  }
231
224
 
232
225
  try {
233
- const files = readdirSync(experienceDir)
234
- .filter((file: string) => file.endsWith('.md'))
235
- .map((file: string) => join(experienceDir, file));
236
-
237
- for (const file of files) {
238
- try {
239
- const content = readFileSync(file, 'utf8');
240
- const parsed = matter(content);
241
- const mtime = statSync(file).mtime;
242
- allFiles.push({
243
- filePath: file,
244
- data: parsed.data,
245
- content: parsed.content,
246
- mtime,
247
- });
248
- } catch (error) {
249
- debugLog(`Failed to read experience file ${file}:`, error);
250
- }
251
- }
226
+ allFiles.push(
227
+ ...loadMarkdownFiles(experienceDir, {
228
+ onError: (filePath, error) => debugLog(`Failed to read experience file ${filePath}:`, error),
229
+ })
230
+ );
252
231
  } catch (error) {
253
232
  debugLog(`Failed to read experience directory ${experienceDir}:`, error);
254
233
  }
@@ -274,20 +253,17 @@ export class ExperienceTracker {
274
253
  });
275
254
  })
276
255
  .map((experience) => {
277
- const lines = experience.content.split('\n');
278
- if (lines.length <= maxLines) return experience;
279
- return { ...experience, content: lines.slice(0, maxLines).join('\n') };
256
+ if (experience.content.split('\n').length <= maxLines) return experience;
257
+ let content = experience.content;
258
+ while (content.split('\n').length > maxLines) {
259
+ const sections = mdq(content).query('section2').each();
260
+ if (sections.length <= 1) break;
261
+ content = sections[sections.length - 1].replace('');
262
+ }
263
+ return { ...experience, content };
280
264
  });
281
265
  }
282
266
 
283
- /**
284
- * Clean up experience tracker (for testing)
285
- */
286
- cleanup(): void {
287
- // Clear any in-memory state if needed
288
- // The actual files will be cleaned up by test cleanup
289
- }
290
-
291
267
  getSuccessfulExperience(state: ActionResult, options?: { includeDescendants?: boolean; stripCode?: boolean }): string[] {
292
268
  const records = this.getRelevantExperience(state, {
293
269
  includeDescendantExperience: options?.includeDescendants,
@@ -325,36 +301,50 @@ export class ExperienceTracker {
325
301
  return aHash.localeCompare(bHash);
326
302
  });
327
303
 
304
+ return this.buildToc(sorted);
305
+ }
306
+
307
+ renderExperienceTocFor(state: ActionResult): string {
308
+ const toc = this.getExperienceTableOfContents(state);
309
+ if (toc.length === 0) return '';
310
+
311
+ const totalSections = toc.reduce((sum, entry) => sum + entry.sections.length, 0);
312
+ debugLog(`injecting experience TOC (${toc.length} files, ${totalSections} sections)`);
313
+ tag('operation').log(`Found ${toc.length} experience ${pluralize(toc.length, 'file')} (${totalSections} sections)`);
314
+ return renderExperienceToc(toc);
315
+ }
316
+
317
+ getExperienceSection(fileTag: string, sectionIndex: number, state: ActionResult, options?: { includeDescendantExperience?: boolean }): { title: string; url: string; content: string } | null {
318
+ const toc = this.getExperienceTableOfContents(state, options);
319
+ const entry = toc.find((e) => e.fileTag === fileTag);
320
+ if (!entry) return null;
321
+
322
+ const extracted = this.readSectionByHash(entry.fileHash, sectionIndex);
323
+ if (!extracted) return null;
324
+
325
+ return { title: extracted.title, url: entry.url, content: extracted.body };
326
+ }
327
+
328
+ private buildToc(records: ExperienceFile[]): ExperienceTocEntry[] {
328
329
  const toc: ExperienceTocEntry[] = [];
329
- for (let i = 0; i < sorted.length; i++) {
330
- const record = sorted[i];
331
- const fileHash = basename(record.filePath, '.md');
332
- const url = (record.data as WebPageState)?.url || '';
330
+ for (const record of records) {
333
331
  const sections = listTocHeadings(record.content);
334
332
  if (sections.length === 0) continue;
335
333
  toc.push({
336
- fileTag: indexToLetters(i),
337
- fileHash,
338
- url,
334
+ fileTag: indexToLetters(toc.length),
335
+ fileHash: basename(record.filePath, '.md'),
336
+ url: (record.data as WebPageState)?.url || '',
339
337
  sections,
340
338
  });
341
339
  }
342
340
  return toc;
343
341
  }
344
342
 
345
- getExperienceSection(fileTag: string, sectionIndex: number, state: ActionResult, options?: { includeDescendantExperience?: boolean }): { title: string; url: string; content: string } | null {
346
- const toc = this.getExperienceTableOfContents(state, options);
347
- const entry = toc.find((e) => e.fileTag === fileTag);
348
- if (!entry) return null;
349
-
350
- const filePath = this.findExperienceFileByHash(entry.fileHash);
343
+ private readSectionByHash(fileHash: string, sectionIndex: number): { title: string; body: string } | null {
344
+ const filePath = this.findExperienceFileByHash(fileHash);
351
345
  if (!filePath) return null;
352
-
353
- const { content } = this.readExperienceFile(entry.fileHash);
354
- const extracted = extractHeadingSection(content, sectionIndex);
355
- if (!extracted) return null;
356
-
357
- return { title: extracted.title, url: entry.url, content: extracted.body };
346
+ const { content } = this.readExperienceFileAt(filePath);
347
+ return extractHeadingSection(content, sectionIndex);
358
348
  }
359
349
 
360
350
  private findExperienceFileByHash(fileHash: string): string | null {
@@ -401,19 +391,7 @@ export class ExperienceTracker {
401
391
  return aUrl.localeCompare(bUrl);
402
392
  });
403
393
 
404
- const toc: ExperienceTocEntry[] = [];
405
- for (let i = 0; i < sorted.length; i++) {
406
- const record = sorted[i];
407
- const sections = listTocHeadings(record.content);
408
- if (sections.length === 0) continue;
409
- toc.push({
410
- fileTag: indexToLetters(toc.length),
411
- fileHash: basename(record.filePath, '.md'),
412
- url: (record.data as WebPageState)?.url || '',
413
- sections,
414
- });
415
- }
416
- return toc;
394
+ return this.buildToc(sorted);
417
395
  }
418
396
 
419
397
  getExperienceSectionByTag(fileTag: string, sectionIndex: number, filter?: string): { title: string; url: string; content: string; fileHash: string } | null {
@@ -421,11 +399,7 @@ export class ExperienceTracker {
421
399
  const entry = toc.find((e) => e.fileTag === fileTag);
422
400
  if (!entry) return null;
423
401
 
424
- const filePath = this.findExperienceFileByHash(entry.fileHash);
425
- if (!filePath) return null;
426
-
427
- const { content } = this.readExperienceFile(entry.fileHash);
428
- const extracted = extractHeadingSection(content, sectionIndex);
402
+ const extracted = this.readSectionByHash(entry.fileHash, sectionIndex);
429
403
  if (!extracted) return null;
430
404
 
431
405
  return { title: extracted.title, url: entry.url, content: extracted.body, fileHash: entry.fileHash };
@@ -433,49 +407,22 @@ export class ExperienceTracker {
433
407
  }
434
408
 
435
409
  function listTocHeadings(content: string): { index: number; level: 2 | 3; title: string }[] {
436
- const tokens = marked.lexer(content);
437
- const result: { index: number; level: 2 | 3; title: string }[] = [];
438
- let index = 0;
439
- for (const token of tokens) {
440
- if (token.type !== 'heading') continue;
441
- const heading = token as Tokens.Heading;
442
- if (heading.depth !== 2 && heading.depth !== 3) continue;
443
- index++;
444
- result.push({ index, level: heading.depth as 2 | 3, title: heading.text });
445
- }
446
- return result;
410
+ return mdq(content)
411
+ .query('heading')
412
+ .meta()
413
+ .filter((m) => m.depth === 2 || m.depth === 3)
414
+ .map((m, i) => ({ index: i + 1, level: m.depth as 2 | 3, title: m.text }));
447
415
  }
448
416
 
449
417
  function extractHeadingSection(content: string, sectionIndex: number): { title: string; body: string } | null {
450
- const tokens = marked.lexer(content);
451
- const matching: { tokenIdx: number; depth: number; text: string }[] = [];
452
-
453
- for (let i = 0; i < tokens.length; i++) {
454
- const token = tokens[i];
455
- if (token.type !== 'heading') continue;
456
- const heading = token as Tokens.Heading;
457
- if (heading.depth !== 2 && heading.depth !== 3) continue;
458
- matching.push({ tokenIdx: i, depth: heading.depth, text: heading.text });
459
- }
418
+ const sections = mdq(content).query('section').each();
419
+ const metas = mdq(content).query('section').meta();
420
+ const matching = metas.map((meta, i) => ({ meta, section: sections[i] })).filter((entry) => entry.meta.depth === 2 || entry.meta.depth === 3);
460
421
 
461
422
  if (sectionIndex < 1 || sectionIndex > matching.length) return null;
462
423
 
463
424
  const target = matching[sectionIndex - 1];
464
- let endTokenIdx = tokens.length;
465
- for (let j = target.tokenIdx + 1; j < tokens.length; j++) {
466
- const token = tokens[j];
467
- if (token.type !== 'heading') continue;
468
- if ((token as Tokens.Heading).depth <= target.depth) {
469
- endTokenIdx = j;
470
- break;
471
- }
472
- }
473
-
474
- const body = tokens
475
- .slice(target.tokenIdx, endTokenIdx)
476
- .map((t) => (t as any).raw || '')
477
- .join('');
478
- return { title: target.text, body };
425
+ return { title: target.meta.text, body: target.section.text() };
479
426
  }
480
427
 
481
428
  function indexToLetters(index: number): string {
@@ -544,23 +491,14 @@ function generateActionContent(title: string, code: string, explanation?: string
544
491
  }
545
492
 
546
493
  function renderAsHowTo(content: string): string {
547
- const tokens = marked.lexer(content);
548
- let result = '';
549
- for (const token of tokens) {
550
- if (token.type === 'heading' && (token as Tokens.Heading).depth === 2) {
551
- const text = (token as Tokens.Heading).text.trim();
552
- if (text.startsWith('FLOW:')) {
553
- result += `## HOW to ${text.slice(5).trim()} (multi-step)\n\n`;
554
- continue;
555
- }
556
- if (text.startsWith('ACTION:')) {
557
- result += `## HOW to ${text.slice(7).trim()} (single-step)\n\n`;
558
- continue;
559
- }
560
- }
561
- result += (token as any).raw || '';
562
- }
563
- return result;
494
+ return mdq(content)
495
+ .query('h2')
496
+ .replaceEach((heading) => {
497
+ const text = heading.meta()[0]?.text.trim() || '';
498
+ if (text.startsWith('FLOW:')) return `## HOW to ${text.slice(5).trim()} (multi-step)\n\n`;
499
+ if (text.startsWith('ACTION:')) return `## HOW to ${text.slice(7).trim()} (single-step)\n\n`;
500
+ return heading.text();
501
+ });
564
502
  }
565
503
 
566
504
  export interface ExperienceFile {
package/src/explorbot.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { existsSync, mkdirSync } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { ActionResult } from './action-result.ts';
4
3
  import { Captain } from './ai/captain.ts';
5
4
  import { Driller } from './ai/driller.ts';
6
5
  import { ExperienceCompactor } from './ai/experience-compactor.ts';
@@ -28,6 +27,7 @@ import { WebPageState } from './state-manager.ts';
28
27
  import { Stats } from './stats.ts';
29
28
  import type { Suite } from './suite.ts';
30
29
  import { Plan, type Test } from './test-plan.ts';
30
+ import { browserErrorMessage } from './utils/browser-errors.ts';
31
31
  import { setVerboseMode, tag } from './utils/logger.ts';
32
32
  import { relativeToCwd } from './utils/next-steps.ts';
33
33
  import { sanitizeFilename } from './utils/strings.ts';
@@ -54,7 +54,6 @@ export class ExplorBot {
54
54
  private config!: ExplorbotConfig;
55
55
  private options: ExplorBotOptions;
56
56
  private userResolveFn: UserResolveFunction | null = null;
57
- public needsInput = false;
58
57
  private currentPlan?: Plan;
59
58
  private planFeature?: string;
60
59
  lastPlanError: Error | null = null;
@@ -62,6 +61,8 @@ export class ExplorBot {
62
61
  private agents: Record<string, any> = {};
63
62
  private sessionPlans: Plan[] = [];
64
63
  private lastReportedTestCount = 0;
64
+ private _experienceTracker?: ExperienceTracker;
65
+ private _knowledgeTracker?: KnowledgeTracker;
65
66
 
66
67
  constructor(options: ExplorBotOptions = {}) {
67
68
  this.options = options;
@@ -87,14 +88,13 @@ export class ExplorBot {
87
88
 
88
89
  try {
89
90
  await this.startProviderOnly();
90
- this.explorer = new Explorer(this.config, this.provider, this.options);
91
+ this.explorer = new Explorer(this.config, this.provider, this.options, this.experienceTracker(), this.knowledgeTracker());
91
92
  await this.explorer.start();
92
93
  if (!this.options.incognito) {
93
94
  await this.agentExperienceCompactor().autocompact();
94
95
  }
95
- if (this.userResolveFn) this.explorer.setUserResolve(this.userResolveFn);
96
96
  } catch (error) {
97
- const message = error instanceof Error ? error.message : String(error);
97
+ const message = browserErrorMessage(error);
98
98
  console.error('\nFailed to start:', message);
99
99
  process.exit(1);
100
100
  }
@@ -133,18 +133,12 @@ export class ExplorBot {
133
133
  return this.explorer;
134
134
  }
135
135
 
136
- getKnowledgeTracker(): KnowledgeTracker {
137
- if (this.explorer) {
138
- return this.explorer.getKnowledgeTracker();
139
- }
140
- return new KnowledgeTracker();
136
+ knowledgeTracker(): KnowledgeTracker {
137
+ return (this._knowledgeTracker ||= new KnowledgeTracker());
141
138
  }
142
139
 
143
- getExperienceTracker(): ExperienceTracker {
144
- if (this.explorer) {
145
- return this.explorer.getStateManager().getExperienceTracker();
146
- }
147
- return new ExperienceTracker();
140
+ experienceTracker(): ExperienceTracker {
141
+ return (this._experienceTracker ||= new ExperienceTracker(this.knowledgeTracker()));
148
142
  }
149
143
 
150
144
  getConfig(): ExplorbotConfig {
@@ -154,13 +148,6 @@ export class ExplorBot {
154
148
  getOptions(): ExplorBotOptions {
155
149
  return this.options;
156
150
  }
157
- isReady(): boolean {
158
- return this.explorer?.isStarted;
159
- }
160
-
161
- getConfigParser(): ConfigParser {
162
- return this.configParser;
163
- }
164
151
 
165
152
  getProvider(): AIProvider {
166
153
  return this.provider;
@@ -173,12 +160,9 @@ export class ExplorBot {
173
160
  config: this.config,
174
161
  });
175
162
 
176
- const agentEmoji = (agent as any).emoji || '';
177
163
  const agentName = (agent as any).constructor.name.toLowerCase();
178
164
  tag('debug').log(`Created ${agentName} agent`);
179
165
 
180
- // Agent is stored by the calling method using a string key
181
-
182
166
  return agent;
183
167
  }
184
168
 
@@ -188,13 +172,13 @@ export class ExplorBot {
188
172
 
189
173
  agentNavigator(): Navigator {
190
174
  return (this.agents.navigator ||= this.createAgent(({ ai, explorer }) => {
191
- return new Navigator(explorer, ai, this.agentExperienceCompactor(), explorer.getStateManager().getExperienceTracker());
175
+ return new Navigator(explorer, ai);
192
176
  }));
193
177
  }
194
178
 
195
179
  agentPlanner(): Planner {
196
180
  if (!this.agents.planner) {
197
- this.agents.planner = this.createAgent(({ ai, explorer }) => new Planner(explorer, ai));
181
+ this.agents.planner = this.createAgent(({ ai, explorer }) => new Planner(explorer, ai, this.agentResearcher()));
198
182
  const fisherman = this.agentFisherman();
199
183
  if (fisherman) this.agents.planner.setFisherman(fisherman);
200
184
  }
@@ -205,14 +189,8 @@ export class ExplorBot {
205
189
  return (this.agents.pilot ||= this.createAgent(({ ai, explorer }) => {
206
190
  const researcher = this.agentResearcher();
207
191
  const navigator = this.agentNavigator();
208
- const stateManager = explorer.getStateManager();
209
- const experienceTracker = stateManager.getExperienceTracker();
210
- const getState = () => {
211
- const state = stateManager.getCurrentState();
212
- return state ? ActionResult.fromState(state) : null;
213
- };
214
- const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState, supervisor: true });
215
- return new Pilot(ai, tools, researcher, explorer, experienceTracker);
192
+ const tools = createAgentTools({ explorer, researcher, navigator, supervisor: true });
193
+ return new Pilot(ai, tools, researcher, explorer);
216
194
  }));
217
195
  }
218
196
 
@@ -221,13 +199,7 @@ export class ExplorBot {
221
199
  this.agents.tester = this.createAgent(({ ai, explorer }) => {
222
200
  const researcher = this.agentResearcher();
223
201
  const navigator = this.agentNavigator();
224
- const stateManager = explorer.getStateManager();
225
- const experienceTracker = stateManager.getExperienceTracker();
226
- const getState = () => {
227
- const state = stateManager.getCurrentState();
228
- return state ? ActionResult.fromState(state) : null;
229
- };
230
- const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState });
202
+ const tools = createAgentTools({ explorer, researcher, navigator });
231
203
  return new Tester(explorer, ai, researcher, navigator, tools);
232
204
  });
233
205
 
@@ -246,16 +218,12 @@ export class ExplorBot {
246
218
  agentCaptain(): Captain {
247
219
  if (!this.agents.captain) {
248
220
  this.agents.captain = new Captain(this);
249
-
250
- const qm = this.agentQuartermaster();
251
- if (qm) this.agents.captain.setQuartermaster(qm);
252
- this.agents.captain.setHistorian(this.agentHistorian());
253
221
  }
254
222
  return this.agents.captain;
255
223
  }
256
224
 
257
225
  agentExperienceCompactor(): ExperienceCompactor {
258
- return (this.agents.experienceCompactor ||= new ExperienceCompactor(this.provider, this.getExperienceTracker()));
226
+ return (this.agents.experienceCompactor ||= new ExperienceCompactor(this.provider, this.experienceTracker()));
259
227
  }
260
228
 
261
229
  agentQuartermaster(): Quartermaster | null {
@@ -287,11 +255,9 @@ export class ExplorBot {
287
255
  this.agents.rerunner = this.createAgent(({ ai, explorer }) => {
288
256
  const researcher = this.agentResearcher();
289
257
  const navigator = this.agentNavigator();
290
- const tools = createAgentTools({ explorer, researcher, navigator });
258
+ const tools = createAgentTools({ explorer, researcher, navigator, withExperience: false });
291
259
  return new Rerunner(explorer, ai, tools);
292
260
  });
293
- const qm = this.agentQuartermaster();
294
- if (qm) this.agents.rerunner.setQuartermaster(qm);
295
261
  this.agents.rerunner.setHistorian(this.agentHistorian());
296
262
  }
297
263
  return this.agents.rerunner;
@@ -509,7 +475,7 @@ export class ExplorBot {
509
475
 
510
476
  this.lastReportedTestCount = tests.length;
511
477
  } catch (error) {
512
- const message = error instanceof Error ? error.message : String(error);
478
+ const message = browserErrorMessage(error);
513
479
  tag('warning').log(`Session analysis failed: ${message}`);
514
480
  }
515
481
  }