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
@@ -1,11 +1,11 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
2
- import { basename, dirname, join } from 'node:path';
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
3
  import matter from 'gray-matter';
4
- import { marked } from 'marked';
5
4
  import { ConfigParser } from './config.js';
6
- import { KnowledgeTracker } from './knowledge-tracker.js';
7
- import { createDebug, tag } from './utils/logger.js';
5
+ import { createDebug, pluralize, tag } from './utils/logger.js';
6
+ import { loadMarkdownFiles } from './utils/markdown-files.js';
8
7
  import { mdq } from './utils/markdown-query.js';
8
+ import { redactSecrets } from './utils/secrets.js';
9
9
  import { isNonReusableCode } from "./utils/step-analyzer.js";
10
10
  import { extractStatePath } from './utils/url-matcher.js';
11
11
  const debugLog = createDebug('explorbot:experience');
@@ -32,20 +32,13 @@ export class ExperienceTracker {
32
32
  experienceDir;
33
33
  disabled;
34
34
  knowledgeTracker;
35
- constructor(options = {}) {
35
+ constructor(knowledgeTracker, options = {}) {
36
36
  const configParser = ConfigParser.getInstance();
37
37
  const config = configParser.getConfig();
38
- const configPath = configParser.getConfigPath();
39
38
  this.disabled = options.disabled ?? false;
40
- this.knowledgeTracker = new KnowledgeTracker();
39
+ this.knowledgeTracker = knowledgeTracker;
41
40
  // Resolve experience directory relative to the config file location (project root)
42
- if (configPath) {
43
- const projectRoot = dirname(configPath);
44
- this.experienceDir = join(projectRoot, config.dirs?.experience || 'experience');
45
- }
46
- else {
47
- this.experienceDir = config.dirs?.experience || 'experience';
48
- }
41
+ this.experienceDir = configParser.resolveProjectDir(config.dirs?.experience || 'experience');
49
42
  if (!this.disabled) {
50
43
  this.ensureDirectory(this.experienceDir);
51
44
  }
@@ -83,7 +76,9 @@ export class ExperienceTracker {
83
76
  }
84
77
  }
85
78
  readExperienceFile(stateHash) {
86
- const filePath = this.getExperienceFilePath(stateHash);
79
+ return this.readExperienceFileAt(this.getExperienceFilePath(stateHash));
80
+ }
81
+ readExperienceFileAt(filePath) {
87
82
  const fileContent = readFileSync(filePath, 'utf8');
88
83
  const { content, data } = matter(fileContent);
89
84
  return { content, data };
@@ -93,23 +88,9 @@ export class ExperienceTracker {
93
88
  return;
94
89
  }
95
90
  const filePath = this.getExperienceFilePath(stateHash);
96
- const fileContent = matter.stringify(content, frontmatter || {});
91
+ const fileContent = matter.stringify(redactSecrets(content), frontmatter || {});
97
92
  writeFileSync(filePath, fileContent, 'utf8');
98
93
  }
99
- hasRecentExperience(stateHash, prefix = '') {
100
- if (this.disabled) {
101
- return false;
102
- }
103
- if (prefix) {
104
- stateHash = `${prefix}_${stateHash}`;
105
- }
106
- const filePath = this.getExperienceFilePath(stateHash);
107
- if (!existsSync(filePath)) {
108
- return false;
109
- }
110
- const stats = statSync(filePath);
111
- return stats.mtime.getTime() > Date.now() - 1000 * 60 * 60 * 24;
112
- }
113
94
  getExperienceFilePath(stateHash) {
114
95
  return join(this.experienceDir, `${stateHash}.md`);
115
96
  }
@@ -153,7 +134,10 @@ export class ExperienceTracker {
153
134
  this.ensureExperienceFile(state);
154
135
  const stateHash = state.getStateHash();
155
136
  const { content, data } = this.readExperienceFile(stateHash);
156
- if (content.includes(action.code)) {
137
+ if (mdq(content)
138
+ .query('code')
139
+ .meta()
140
+ .some((m) => m.text.trim() === action.code.trim())) {
157
141
  debugLog('Skipping duplicate action', action.code);
158
142
  return;
159
143
  }
@@ -178,7 +162,10 @@ export class ExperienceTracker {
178
162
  this.ensureExperienceFile(state);
179
163
  const stateHash = state.getStateHash();
180
164
  const { content, data } = this.readExperienceFile(stateHash);
181
- if (content.includes(body)) {
165
+ if (mdq(content)
166
+ .query('section2')
167
+ .each()
168
+ .some((s) => s.text().trim() === body.trim())) {
182
169
  debugLog('Skipping duplicate flow body');
183
170
  return;
184
171
  }
@@ -199,25 +186,9 @@ export class ExperienceTracker {
199
186
  continue;
200
187
  }
201
188
  try {
202
- const files = readdirSync(experienceDir)
203
- .filter((file) => file.endsWith('.md'))
204
- .map((file) => join(experienceDir, file));
205
- for (const file of files) {
206
- try {
207
- const content = readFileSync(file, 'utf8');
208
- const parsed = matter(content);
209
- const mtime = statSync(file).mtime;
210
- allFiles.push({
211
- filePath: file,
212
- data: parsed.data,
213
- content: parsed.content,
214
- mtime,
215
- });
216
- }
217
- catch (error) {
218
- debugLog(`Failed to read experience file ${file}:`, error);
219
- }
220
- }
189
+ allFiles.push(...loadMarkdownFiles(experienceDir, {
190
+ onError: (filePath, error) => debugLog(`Failed to read experience file ${filePath}:`, error),
191
+ }));
221
192
  }
222
193
  catch (error) {
223
194
  debugLog(`Failed to read experience directory ${experienceDir}:`, error);
@@ -241,19 +212,18 @@ export class ExperienceTracker {
241
212
  });
242
213
  })
243
214
  .map((experience) => {
244
- const lines = experience.content.split('\n');
245
- if (lines.length <= maxLines)
215
+ if (experience.content.split('\n').length <= maxLines)
246
216
  return experience;
247
- return { ...experience, content: lines.slice(0, maxLines).join('\n') };
217
+ let content = experience.content;
218
+ while (content.split('\n').length > maxLines) {
219
+ const sections = mdq(content).query('section2').each();
220
+ if (sections.length <= 1)
221
+ break;
222
+ content = sections[sections.length - 1].replace('');
223
+ }
224
+ return { ...experience, content };
248
225
  });
249
226
  }
250
- /**
251
- * Clean up experience tracker (for testing)
252
- */
253
- cleanup() {
254
- // Clear any in-memory state if needed
255
- // The actual files will be cleaned up by test cleanup
256
- }
257
227
  getSuccessfulExperience(state, options) {
258
228
  const records = this.getRelevantExperience(state, {
259
229
  includeDescendantExperience: options?.includeDescendants,
@@ -285,36 +255,48 @@ export class ExperienceTracker {
285
255
  const bHash = basename(b.filePath, '.md');
286
256
  return aHash.localeCompare(bHash);
287
257
  });
258
+ return this.buildToc(sorted);
259
+ }
260
+ renderExperienceTocFor(state) {
261
+ const toc = this.getExperienceTableOfContents(state);
262
+ if (toc.length === 0)
263
+ return '';
264
+ const totalSections = toc.reduce((sum, entry) => sum + entry.sections.length, 0);
265
+ debugLog(`injecting experience TOC (${toc.length} files, ${totalSections} sections)`);
266
+ tag('operation').log(`Found ${toc.length} experience ${pluralize(toc.length, 'file')} (${totalSections} sections)`);
267
+ return renderExperienceToc(toc);
268
+ }
269
+ getExperienceSection(fileTag, sectionIndex, state, options) {
270
+ const toc = this.getExperienceTableOfContents(state, options);
271
+ const entry = toc.find((e) => e.fileTag === fileTag);
272
+ if (!entry)
273
+ return null;
274
+ const extracted = this.readSectionByHash(entry.fileHash, sectionIndex);
275
+ if (!extracted)
276
+ return null;
277
+ return { title: extracted.title, url: entry.url, content: extracted.body };
278
+ }
279
+ buildToc(records) {
288
280
  const toc = [];
289
- for (let i = 0; i < sorted.length; i++) {
290
- const record = sorted[i];
291
- const fileHash = basename(record.filePath, '.md');
292
- const url = record.data?.url || '';
281
+ for (const record of records) {
293
282
  const sections = listTocHeadings(record.content);
294
283
  if (sections.length === 0)
295
284
  continue;
296
285
  toc.push({
297
- fileTag: indexToLetters(i),
298
- fileHash,
299
- url,
286
+ fileTag: indexToLetters(toc.length),
287
+ fileHash: basename(record.filePath, '.md'),
288
+ url: record.data?.url || '',
300
289
  sections,
301
290
  });
302
291
  }
303
292
  return toc;
304
293
  }
305
- getExperienceSection(fileTag, sectionIndex, state, options) {
306
- const toc = this.getExperienceTableOfContents(state, options);
307
- const entry = toc.find((e) => e.fileTag === fileTag);
308
- if (!entry)
309
- return null;
310
- const filePath = this.findExperienceFileByHash(entry.fileHash);
294
+ readSectionByHash(fileHash, sectionIndex) {
295
+ const filePath = this.findExperienceFileByHash(fileHash);
311
296
  if (!filePath)
312
297
  return null;
313
- const { content } = this.readExperienceFile(entry.fileHash);
314
- const extracted = extractHeadingSection(content, sectionIndex);
315
- if (!extracted)
316
- return null;
317
- return { title: extracted.title, url: entry.url, content: extracted.body };
298
+ const { content } = this.readExperienceFileAt(filePath);
299
+ return extractHeadingSection(content, sectionIndex);
318
300
  }
319
301
  findExperienceFileByHash(fileHash) {
320
302
  for (const dir of this.getExperienceDirectories()) {
@@ -358,81 +340,34 @@ export class ExperienceTracker {
358
340
  const bUrl = b.data?.url || '';
359
341
  return aUrl.localeCompare(bUrl);
360
342
  });
361
- const toc = [];
362
- for (let i = 0; i < sorted.length; i++) {
363
- const record = sorted[i];
364
- const sections = listTocHeadings(record.content);
365
- if (sections.length === 0)
366
- continue;
367
- toc.push({
368
- fileTag: indexToLetters(toc.length),
369
- fileHash: basename(record.filePath, '.md'),
370
- url: record.data?.url || '',
371
- sections,
372
- });
373
- }
374
- return toc;
343
+ return this.buildToc(sorted);
375
344
  }
376
345
  getExperienceSectionByTag(fileTag, sectionIndex, filter) {
377
346
  const toc = this.listAllExperienceToc(filter);
378
347
  const entry = toc.find((e) => e.fileTag === fileTag);
379
348
  if (!entry)
380
349
  return null;
381
- const filePath = this.findExperienceFileByHash(entry.fileHash);
382
- if (!filePath)
383
- return null;
384
- const { content } = this.readExperienceFile(entry.fileHash);
385
- const extracted = extractHeadingSection(content, sectionIndex);
350
+ const extracted = this.readSectionByHash(entry.fileHash, sectionIndex);
386
351
  if (!extracted)
387
352
  return null;
388
353
  return { title: extracted.title, url: entry.url, content: extracted.body, fileHash: entry.fileHash };
389
354
  }
390
355
  }
391
356
  function listTocHeadings(content) {
392
- const tokens = marked.lexer(content);
393
- const result = [];
394
- let index = 0;
395
- for (const token of tokens) {
396
- if (token.type !== 'heading')
397
- continue;
398
- const heading = token;
399
- if (heading.depth !== 2 && heading.depth !== 3)
400
- continue;
401
- index++;
402
- result.push({ index, level: heading.depth, title: heading.text });
403
- }
404
- return result;
357
+ return mdq(content)
358
+ .query('heading')
359
+ .meta()
360
+ .filter((m) => m.depth === 2 || m.depth === 3)
361
+ .map((m, i) => ({ index: i + 1, level: m.depth, title: m.text }));
405
362
  }
406
363
  function extractHeadingSection(content, sectionIndex) {
407
- const tokens = marked.lexer(content);
408
- const matching = [];
409
- for (let i = 0; i < tokens.length; i++) {
410
- const token = tokens[i];
411
- if (token.type !== 'heading')
412
- continue;
413
- const heading = token;
414
- if (heading.depth !== 2 && heading.depth !== 3)
415
- continue;
416
- matching.push({ tokenIdx: i, depth: heading.depth, text: heading.text });
417
- }
364
+ const sections = mdq(content).query('section').each();
365
+ const metas = mdq(content).query('section').meta();
366
+ const matching = metas.map((meta, i) => ({ meta, section: sections[i] })).filter((entry) => entry.meta.depth === 2 || entry.meta.depth === 3);
418
367
  if (sectionIndex < 1 || sectionIndex > matching.length)
419
368
  return null;
420
369
  const target = matching[sectionIndex - 1];
421
- let endTokenIdx = tokens.length;
422
- for (let j = target.tokenIdx + 1; j < tokens.length; j++) {
423
- const token = tokens[j];
424
- if (token.type !== 'heading')
425
- continue;
426
- if (token.depth <= target.depth) {
427
- endTokenIdx = j;
428
- break;
429
- }
430
- }
431
- const body = tokens
432
- .slice(target.tokenIdx, endTokenIdx)
433
- .map((t) => t.raw || '')
434
- .join('');
435
- return { title: target.text, body };
370
+ return { title: target.meta.text, body: target.section.text() };
436
371
  }
437
372
  function indexToLetters(index) {
438
373
  let n = index;
@@ -498,21 +433,14 @@ function generateActionContent(title, code, explanation) {
498
433
  return lines.join('\n');
499
434
  }
500
435
  function renderAsHowTo(content) {
501
- const tokens = marked.lexer(content);
502
- let result = '';
503
- for (const token of tokens) {
504
- if (token.type === 'heading' && token.depth === 2) {
505
- const text = token.text.trim();
506
- if (text.startsWith('FLOW:')) {
507
- result += `## HOW to ${text.slice(5).trim()} (multi-step)\n\n`;
508
- continue;
509
- }
510
- if (text.startsWith('ACTION:')) {
511
- result += `## HOW to ${text.slice(7).trim()} (single-step)\n\n`;
512
- continue;
513
- }
514
- }
515
- result += token.raw || '';
516
- }
517
- return result;
436
+ return mdq(content)
437
+ .query('h2')
438
+ .replaceEach((heading) => {
439
+ const text = heading.meta()[0]?.text.trim() || '';
440
+ if (text.startsWith('FLOW:'))
441
+ return `## HOW to ${text.slice(5).trim()} (multi-step)\n\n`;
442
+ if (text.startsWith('ACTION:'))
443
+ return `## HOW to ${text.slice(7).trim()} (single-step)\n\n`;
444
+ return heading.text();
445
+ });
518
446
  }
@@ -0,0 +1,106 @@
1
+ import { Captain } from './ai/captain.js';
2
+ import { Driller } from './ai/driller.js';
3
+ import { ExperienceCompactor } from './ai/experience-compactor.js';
4
+ import { Fisherman } from './ai/fisherman.js';
5
+ import { Historian } from './ai/historian.js';
6
+ import { Navigator } from './ai/navigator.js';
7
+ import { Pilot } from './ai/pilot.js';
8
+ import { Planner } from './ai/planner.js';
9
+ import { AIProvider } from './ai/provider.js';
10
+ import { Quartermaster } from './ai/quartermaster.js';
11
+ import { Rerunner } from './ai/rerunner.js';
12
+ import { Researcher } from './ai/researcher.js';
13
+ import { SessionAnalyst } from './ai/session-analyst.js';
14
+ import { Tester } from './ai/tester.js';
15
+ import type { ExplorbotConfig } from './config.js';
16
+ import { ConfigParser } from './config.js';
17
+ import { ExperienceTracker } from './experience-tracker.js';
18
+ import Explorer from './explorer.js';
19
+ import { KnowledgeTracker } from './knowledge-tracker.js';
20
+ import { WebPageState } from './state-manager.js';
21
+ import type { Suite } from './suite.js';
22
+ import { Plan, type Test } from './test-plan.js';
23
+ export interface ExplorBotOptions {
24
+ from?: string;
25
+ baseUrl?: string;
26
+ verbose?: boolean;
27
+ config?: string;
28
+ path?: string;
29
+ show?: boolean;
30
+ headless?: boolean;
31
+ incognito?: boolean;
32
+ session?: string;
33
+ }
34
+ export type UserResolveFunction = (error?: Error, showWelcome?: boolean) => Promise<string | null>;
35
+ export declare class ExplorBot {
36
+ configParser: ConfigParser;
37
+ explorer: Explorer;
38
+ provider: AIProvider;
39
+ config: ExplorbotConfig;
40
+ options: ExplorBotOptions;
41
+ userResolveFn: UserResolveFunction | null;
42
+ currentPlan?: Plan;
43
+ planFeature?: string;
44
+ lastPlanError: Error | null;
45
+ lastSavedPlanPath: string | null;
46
+ agents: Record<string, any>;
47
+ sessionPlans: Plan[];
48
+ lastReportedTestCount: number;
49
+ _experienceTracker?: ExperienceTracker;
50
+ _knowledgeTracker?: KnowledgeTracker;
51
+ constructor(options?: ExplorBotOptions);
52
+ get isExploring(): boolean;
53
+ setUserResolve(fn: UserResolveFunction): void;
54
+ start(): Promise<void>;
55
+ startProviderOnly(): Promise<void>;
56
+ stop(): Promise<void>;
57
+ visitInitialState(): Promise<void>;
58
+ visit(url: string): Promise<void>;
59
+ openFreshTab(): Promise<void>;
60
+ getCurrentState(): WebPageState | null;
61
+ getExplorer(): Explorer;
62
+ knowledgeTracker(): KnowledgeTracker;
63
+ experienceTracker(): ExperienceTracker;
64
+ getConfig(): ExplorbotConfig;
65
+ getOptions(): ExplorBotOptions;
66
+ getProvider(): AIProvider;
67
+ createAgent<T>(factory: (deps: {
68
+ explorer: Explorer;
69
+ ai: AIProvider;
70
+ config: ExplorbotConfig;
71
+ }) => T): T;
72
+ agentResearcher(): Researcher;
73
+ agentNavigator(): Navigator;
74
+ agentPlanner(): Planner;
75
+ agentPilot(): Pilot;
76
+ agentTester(): Tester;
77
+ agentCaptain(): Captain;
78
+ agentExperienceCompactor(): ExperienceCompactor;
79
+ agentQuartermaster(): Quartermaster | null;
80
+ agentHistorian(): Historian;
81
+ agentRerunner(): Rerunner;
82
+ agentDriller(): Driller;
83
+ agentSessionAnalyst(): SessionAnalyst;
84
+ agentFisherman(): Fisherman | null;
85
+ getCurrentPlan(): Plan | undefined;
86
+ getSuite(): Suite | null;
87
+ getPlanFeature(): string | undefined;
88
+ clearPlan(): void;
89
+ plan(feature?: string, opts?: {
90
+ fresh?: boolean;
91
+ style?: string;
92
+ extend?: Plan;
93
+ completedPlans?: Plan[];
94
+ noSave?: boolean;
95
+ }): Promise<Plan>;
96
+ getPlansDir(): string;
97
+ savePlan(filename?: string): string | null;
98
+ savePlans(plans: Plan[], filename?: string): string | null;
99
+ generatePlanFilename(feature?: string): string;
100
+ resolvePlanPath(filename: string): string;
101
+ loadPlan(filename: string): Plan;
102
+ loadPlans(filename: string): Plan[];
103
+ setCurrentPlan(plan?: Plan): void;
104
+ getSessionTests(): Test[];
105
+ printSessionAnalysis(): Promise<void>;
106
+ }
@@ -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.js";
4
3
  import { Captain } from "./ai/captain.js";
5
4
  import { Driller } from "./ai/driller.js";
6
5
  import { ExperienceCompactor } from "./ai/experience-compactor.js";
@@ -25,6 +24,7 @@ import Explorer from "./explorer.js";
25
24
  import { KnowledgeTracker } from "./knowledge-tracker.js";
26
25
  import { Stats } from "./stats.js";
27
26
  import { Plan } from "./test-plan.js";
27
+ import { browserErrorMessage } from "./utils/browser-errors.js";
28
28
  import { setVerboseMode, tag } from "./utils/logger.js";
29
29
  import { relativeToCwd } from "./utils/next-steps.js";
30
30
  import { sanitizeFilename } from "./utils/strings.js";
@@ -36,7 +36,6 @@ export class ExplorBot {
36
36
  config;
37
37
  options;
38
38
  userResolveFn = null;
39
- needsInput = false;
40
39
  currentPlan;
41
40
  planFeature;
42
41
  lastPlanError = null;
@@ -44,6 +43,8 @@ export class ExplorBot {
44
43
  agents = {};
45
44
  sessionPlans = [];
46
45
  lastReportedTestCount = 0;
46
+ _experienceTracker;
47
+ _knowledgeTracker;
47
48
  constructor(options = {}) {
48
49
  this.options = options;
49
50
  this.configParser = ConfigParser.getInstance();
@@ -64,16 +65,14 @@ export class ExplorBot {
64
65
  }
65
66
  try {
66
67
  await this.startProviderOnly();
67
- this.explorer = new Explorer(this.config, this.provider, this.options);
68
+ this.explorer = new Explorer(this.config, this.provider, this.options, this.experienceTracker(), this.knowledgeTracker());
68
69
  await this.explorer.start();
69
70
  if (!this.options.incognito) {
70
71
  await this.agentExperienceCompactor().autocompact();
71
72
  }
72
- if (this.userResolveFn)
73
- this.explorer.setUserResolve(this.userResolveFn);
74
73
  }
75
74
  catch (error) {
76
- const message = error instanceof Error ? error.message : String(error);
75
+ const message = browserErrorMessage(error);
77
76
  console.error('\nFailed to start:', message);
78
77
  process.exit(1);
79
78
  }
@@ -105,17 +104,11 @@ export class ExplorBot {
105
104
  getExplorer() {
106
105
  return this.explorer;
107
106
  }
108
- getKnowledgeTracker() {
109
- if (this.explorer) {
110
- return this.explorer.getKnowledgeTracker();
111
- }
112
- return new KnowledgeTracker();
107
+ knowledgeTracker() {
108
+ return (this._knowledgeTracker ||= new KnowledgeTracker());
113
109
  }
114
- getExperienceTracker() {
115
- if (this.explorer) {
116
- return this.explorer.getStateManager().getExperienceTracker();
117
- }
118
- return new ExperienceTracker();
110
+ experienceTracker() {
111
+ return (this._experienceTracker ||= new ExperienceTracker(this.knowledgeTracker()));
119
112
  }
120
113
  getConfig() {
121
114
  return this.config;
@@ -123,12 +116,6 @@ export class ExplorBot {
123
116
  getOptions() {
124
117
  return this.options;
125
118
  }
126
- isReady() {
127
- return this.explorer?.isStarted;
128
- }
129
- getConfigParser() {
130
- return this.configParser;
131
- }
132
119
  getProvider() {
133
120
  return this.provider;
134
121
  }
@@ -138,10 +125,8 @@ export class ExplorBot {
138
125
  ai: this.provider,
139
126
  config: this.config,
140
127
  });
141
- const agentEmoji = agent.emoji || '';
142
128
  const agentName = agent.constructor.name.toLowerCase();
143
129
  tag('debug').log(`Created ${agentName} agent`);
144
- // Agent is stored by the calling method using a string key
145
130
  return agent;
146
131
  }
147
132
  agentResearcher() {
@@ -149,12 +134,12 @@ export class ExplorBot {
149
134
  }
150
135
  agentNavigator() {
151
136
  return (this.agents.navigator ||= this.createAgent(({ ai, explorer }) => {
152
- return new Navigator(explorer, ai, this.agentExperienceCompactor(), explorer.getStateManager().getExperienceTracker());
137
+ return new Navigator(explorer, ai);
153
138
  }));
154
139
  }
155
140
  agentPlanner() {
156
141
  if (!this.agents.planner) {
157
- this.agents.planner = this.createAgent(({ ai, explorer }) => new Planner(explorer, ai));
142
+ this.agents.planner = this.createAgent(({ ai, explorer }) => new Planner(explorer, ai, this.agentResearcher()));
158
143
  const fisherman = this.agentFisherman();
159
144
  if (fisherman)
160
145
  this.agents.planner.setFisherman(fisherman);
@@ -165,14 +150,8 @@ export class ExplorBot {
165
150
  return (this.agents.pilot ||= this.createAgent(({ ai, explorer }) => {
166
151
  const researcher = this.agentResearcher();
167
152
  const navigator = this.agentNavigator();
168
- const stateManager = explorer.getStateManager();
169
- const experienceTracker = stateManager.getExperienceTracker();
170
- const getState = () => {
171
- const state = stateManager.getCurrentState();
172
- return state ? ActionResult.fromState(state) : null;
173
- };
174
- const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState, supervisor: true });
175
- return new Pilot(ai, tools, researcher, explorer, experienceTracker);
153
+ const tools = createAgentTools({ explorer, researcher, navigator, supervisor: true });
154
+ return new Pilot(ai, tools, researcher, explorer);
176
155
  }));
177
156
  }
178
157
  agentTester() {
@@ -180,13 +159,7 @@ export class ExplorBot {
180
159
  this.agents.tester = this.createAgent(({ ai, explorer }) => {
181
160
  const researcher = this.agentResearcher();
182
161
  const navigator = this.agentNavigator();
183
- const stateManager = explorer.getStateManager();
184
- const experienceTracker = stateManager.getExperienceTracker();
185
- const getState = () => {
186
- const state = stateManager.getCurrentState();
187
- return state ? ActionResult.fromState(state) : null;
188
- };
189
- const tools = createAgentTools({ explorer, researcher, navigator, experienceTracker, getState });
162
+ const tools = createAgentTools({ explorer, researcher, navigator });
190
163
  return new Tester(explorer, ai, researcher, navigator, tools);
191
164
  });
192
165
  const qm = this.agentQuartermaster();
@@ -204,15 +177,11 @@ export class ExplorBot {
204
177
  agentCaptain() {
205
178
  if (!this.agents.captain) {
206
179
  this.agents.captain = new Captain(this);
207
- const qm = this.agentQuartermaster();
208
- if (qm)
209
- this.agents.captain.setQuartermaster(qm);
210
- this.agents.captain.setHistorian(this.agentHistorian());
211
180
  }
212
181
  return this.agents.captain;
213
182
  }
214
183
  agentExperienceCompactor() {
215
- return (this.agents.experienceCompactor ||= new ExperienceCompactor(this.provider, this.getExperienceTracker()));
184
+ return (this.agents.experienceCompactor ||= new ExperienceCompactor(this.provider, this.experienceTracker()));
216
185
  }
217
186
  agentQuartermaster() {
218
187
  const config = this.config.ai?.agents?.quartermaster;
@@ -241,12 +210,9 @@ export class ExplorBot {
241
210
  this.agents.rerunner = this.createAgent(({ ai, explorer }) => {
242
211
  const researcher = this.agentResearcher();
243
212
  const navigator = this.agentNavigator();
244
- const tools = createAgentTools({ explorer, researcher, navigator });
213
+ const tools = createAgentTools({ explorer, researcher, navigator, withExperience: false });
245
214
  return new Rerunner(explorer, ai, tools);
246
215
  });
247
- const qm = this.agentQuartermaster();
248
- if (qm)
249
- this.agents.rerunner.setQuartermaster(qm);
250
216
  this.agents.rerunner.setHistorian(this.agentHistorian());
251
217
  }
252
218
  return this.agents.rerunner;
@@ -443,7 +409,7 @@ export class ExplorBot {
443
409
  this.lastReportedTestCount = tests.length;
444
410
  }
445
411
  catch (error) {
446
- const message = error instanceof Error ? error.message : String(error);
412
+ const message = browserErrorMessage(error);
447
413
  tag('warning').log(`Session analysis failed: ${message}`);
448
414
  }
449
415
  }