explorbot 0.1.32-beta.1 → 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 (168) 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 +20 -3
  17. package/dist/src/action-result.d.ts +8 -10
  18. package/dist/src/action-result.js +72 -164
  19. package/dist/src/action.d.ts +3 -10
  20. package/dist/src/action.js +18 -111
  21. package/dist/src/ai/captain/web-mode.js +0 -7
  22. package/dist/src/ai/captain.d.ts +1 -3
  23. package/dist/src/ai/captain.js +4 -19
  24. package/dist/src/ai/conversation.d.ts +2 -5
  25. package/dist/src/ai/conversation.js +9 -28
  26. package/dist/src/ai/driller.d.ts +0 -4
  27. package/dist/src/ai/driller.js +3 -12
  28. package/dist/src/ai/experience-compactor.d.ts +2 -2
  29. package/dist/src/ai/experience-compactor.js +16 -33
  30. package/dist/src/ai/fisherman-tools.js +1 -1
  31. package/dist/src/ai/fisherman.d.ts +0 -1
  32. package/dist/src/ai/fisherman.js +0 -3
  33. package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
  34. package/dist/src/ai/historian/codeceptjs.js +3 -2
  35. package/dist/src/ai/historian/playwright.d.ts +2 -0
  36. package/dist/src/ai/historian/playwright.js +3 -2
  37. package/dist/src/ai/historian/utils.d.ts +0 -1
  38. package/dist/src/ai/historian/utils.js +0 -1
  39. package/dist/src/ai/historian.d.ts +5 -4
  40. package/dist/src/ai/historian.js +1 -5
  41. package/dist/src/ai/navigator.d.ts +3 -5
  42. package/dist/src/ai/navigator.js +11 -46
  43. package/dist/src/ai/pilot.d.ts +1 -3
  44. package/dist/src/ai/pilot.js +6 -17
  45. package/dist/src/ai/planner.d.ts +3 -3
  46. package/dist/src/ai/planner.js +49 -37
  47. package/dist/src/ai/provider.d.ts +4 -1
  48. package/dist/src/ai/provider.js +77 -136
  49. package/dist/src/ai/quartermaster.d.ts +21 -22
  50. package/dist/src/ai/quartermaster.js +4 -10
  51. package/dist/src/ai/rerunner.d.ts +1 -2
  52. package/dist/src/ai/rerunner.js +14 -22
  53. package/dist/src/ai/researcher/cache.js +11 -18
  54. package/dist/src/ai/researcher/deep-analysis.js +3 -6
  55. package/dist/src/ai/researcher/parser.js +1 -1
  56. package/dist/src/ai/researcher/research-result.d.ts +0 -1
  57. package/dist/src/ai/researcher/research-result.js +0 -19
  58. package/dist/src/ai/researcher/sections.js +5 -1
  59. package/dist/src/ai/researcher.js +12 -53
  60. package/dist/src/ai/rules.d.ts +1 -0
  61. package/dist/src/ai/rules.js +22 -15
  62. package/dist/src/ai/session-analyst.js +8 -5
  63. package/dist/src/ai/task-agent.d.ts +1 -1
  64. package/dist/src/ai/task-agent.js +2 -27
  65. package/dist/src/ai/tester.d.ts +0 -2
  66. package/dist/src/ai/tester.js +10 -61
  67. package/dist/src/ai/tools.d.ts +18 -7
  68. package/dist/src/ai/tools.js +119 -101
  69. package/dist/src/commands/compact-command.d.ts +1 -1
  70. package/dist/src/commands/compact-command.js +6 -6
  71. package/dist/src/commands/context-command.js +1 -1
  72. package/dist/src/commands/context-knowledge-command.js +1 -1
  73. package/dist/src/commands/experience-command.js +1 -1
  74. package/dist/src/commands/knows-command.js +1 -1
  75. package/dist/src/commands/learn-command.js +3 -1
  76. package/dist/src/components/AddKnowledge.d.ts +2 -0
  77. package/dist/src/components/AddKnowledge.js +3 -7
  78. package/dist/src/components/App.js +2 -2
  79. package/dist/src/config.d.ts +2 -0
  80. package/dist/src/config.js +12 -0
  81. package/dist/src/experience-tracker.d.ts +11 -6
  82. package/dist/src/experience-tracker.js +81 -154
  83. package/dist/src/explorbot.d.ts +4 -5
  84. package/dist/src/explorbot.js +17 -51
  85. package/dist/src/explorer.d.ts +4 -15
  86. package/dist/src/explorer.js +18 -68
  87. package/dist/src/knowledge-tracker.d.ts +2 -2
  88. package/dist/src/knowledge-tracker.js +38 -45
  89. package/dist/src/state-manager.d.ts +6 -31
  90. package/dist/src/state-manager.js +10 -129
  91. package/dist/src/utils/aria.d.ts +6 -1
  92. package/dist/src/utils/aria.js +4 -1
  93. package/dist/src/utils/cache.d.ts +15 -0
  94. package/dist/src/utils/cache.js +34 -0
  95. package/dist/src/utils/context-formatter.d.ts +6 -0
  96. package/dist/src/utils/context-formatter.js +15 -27
  97. package/dist/src/utils/html.d.ts +5 -0
  98. package/dist/src/utils/html.js +71 -0
  99. package/dist/src/utils/markdown-files.d.ts +10 -0
  100. package/dist/src/utils/markdown-files.js +21 -0
  101. package/dist/src/utils/markdown-query.d.ts +6 -0
  102. package/dist/src/utils/markdown-query.js +14 -1
  103. package/dist/src/utils/page-readiness.d.ts +1 -0
  104. package/dist/src/utils/page-readiness.js +1 -1
  105. package/dist/src/utils/secrets.js +2 -2
  106. package/dist/src/utils/strings.d.ts +2 -0
  107. package/dist/src/utils/strings.js +10 -0
  108. package/dist/src/utils/web-element.d.ts +2 -1
  109. package/dist/src/utils/web-element.js +3 -0
  110. package/dist/src/utils/web-sandbox.d.ts +4 -0
  111. package/dist/src/utils/web-sandbox.js +37 -0
  112. package/package.json +20 -3
  113. package/src/action-result.ts +70 -173
  114. package/src/action.ts +16 -130
  115. package/src/ai/captain/web-mode.ts +0 -7
  116. package/src/ai/captain.ts +5 -17
  117. package/src/ai/conversation.ts +9 -32
  118. package/src/ai/driller.ts +3 -16
  119. package/src/ai/experience-compactor.ts +16 -35
  120. package/src/ai/fisherman-tools.ts +1 -1
  121. package/src/ai/fisherman.ts +0 -4
  122. package/src/ai/historian/codeceptjs.ts +4 -2
  123. package/src/ai/historian/playwright.ts +4 -2
  124. package/src/ai/historian/utils.ts +0 -1
  125. package/src/ai/historian.ts +4 -7
  126. package/src/ai/navigator.ts +13 -49
  127. package/src/ai/pilot.ts +6 -16
  128. package/src/ai/planner.ts +56 -44
  129. package/src/ai/provider.ts +85 -145
  130. package/src/ai/quartermaster.ts +34 -41
  131. package/src/ai/rerunner.ts +17 -21
  132. package/src/ai/researcher/cache.ts +10 -16
  133. package/src/ai/researcher/deep-analysis.ts +3 -6
  134. package/src/ai/researcher/parser.ts +1 -1
  135. package/src/ai/researcher/research-result.ts +0 -18
  136. package/src/ai/researcher/sections.ts +5 -1
  137. package/src/ai/researcher.ts +11 -59
  138. package/src/ai/rules.ts +24 -13
  139. package/src/ai/session-analyst.ts +8 -5
  140. package/src/ai/task-agent.ts +3 -30
  141. package/src/ai/tester.ts +12 -73
  142. package/src/ai/tools.ts +119 -106
  143. package/src/commands/compact-command.ts +7 -8
  144. package/src/commands/context-command.ts +1 -1
  145. package/src/commands/context-knowledge-command.ts +1 -1
  146. package/src/commands/experience-command.ts +1 -1
  147. package/src/commands/knows-command.ts +1 -1
  148. package/src/commands/learn-command.ts +3 -1
  149. package/src/components/AddKnowledge.tsx +5 -7
  150. package/src/components/App.tsx +2 -2
  151. package/src/config.ts +12 -0
  152. package/src/experience-tracker.ts +88 -151
  153. package/src/explorbot.ts +17 -51
  154. package/src/explorer.ts +20 -72
  155. package/src/knowledge-tracker.ts +42 -49
  156. package/src/state-manager.ts +15 -156
  157. package/src/utils/aria.ts +11 -2
  158. package/src/utils/cache.ts +40 -0
  159. package/src/utils/context-formatter.ts +12 -15
  160. package/src/utils/html.ts +79 -0
  161. package/src/utils/markdown-files.ts +30 -0
  162. package/src/utils/markdown-query.ts +15 -1
  163. package/src/utils/page-readiness.ts +1 -1
  164. package/src/utils/secrets.ts +2 -3
  165. package/src/utils/strings.ts +12 -0
  166. package/src/utils/web-element.ts +4 -0
  167. package/src/utils/web-sandbox.ts +43 -0
  168. package/dist/boat/api-tester/example/apibot.config.js +0 -30
@@ -230,6 +230,8 @@ export declare class ConfigParser {
230
230
  getConfig(): ExplorbotConfig;
231
231
  getConfigPath(): string | null;
232
232
  getOutputDir(): string;
233
+ getProjectRoot(): string;
234
+ resolveProjectDir(relativeDir: string): string;
233
235
  getStatesDir(): string;
234
236
  getPlansDir(): string;
235
237
  getTestsDir(): string;
@@ -96,6 +96,18 @@ export class ConfigParser {
96
96
  throw new Error('Config path not found');
97
97
  return path.join(path.dirname(configPath), config.dirs?.output || 'output');
98
98
  }
99
+ getProjectRoot() {
100
+ const configPath = this.getConfigPath();
101
+ if (configPath)
102
+ return path.dirname(configPath);
103
+ return process.cwd();
104
+ }
105
+ resolveProjectDir(relativeDir) {
106
+ const configPath = this.getConfigPath();
107
+ if (!configPath)
108
+ return relativeDir;
109
+ return path.join(path.dirname(configPath), relativeDir);
110
+ }
99
111
  getStatesDir() {
100
112
  return outputPath('states');
101
113
  }
@@ -22,7 +22,7 @@ export declare class ExperienceTracker {
22
22
  experienceDir: string;
23
23
  disabled: boolean;
24
24
  knowledgeTracker: KnowledgeTracker;
25
- constructor(options?: {
25
+ constructor(knowledgeTracker: KnowledgeTracker, options?: {
26
26
  disabled?: boolean;
27
27
  });
28
28
  getExperienceDirectories(): string[];
@@ -31,8 +31,11 @@ export declare class ExperienceTracker {
31
31
  content: string;
32
32
  data: any;
33
33
  };
34
+ readExperienceFileAt(filePath: string): {
35
+ content: string;
36
+ data: any;
37
+ };
34
38
  writeExperienceFile(stateHash: string, content: string, frontmatter?: any): void;
35
- hasRecentExperience(stateHash: string, prefix?: string): boolean;
36
39
  getExperienceFilePath(stateHash: string): string;
37
40
  ensureExperienceFile(state: ActionResult): string;
38
41
  updateSummary(state: ActionResult, summary: string): void;
@@ -43,10 +46,6 @@ export declare class ExperienceTracker {
43
46
  getRelevantExperience(state: ActionResult, options?: {
44
47
  includeDescendantExperience?: boolean;
45
48
  }): ExperienceFile[];
46
- /**
47
- * Clean up experience tracker (for testing)
48
- */
49
- cleanup(): void;
50
49
  getSuccessfulExperience(state: ActionResult, options?: {
51
50
  includeDescendants?: boolean;
52
51
  stripCode?: boolean;
@@ -54,6 +53,7 @@ export declare class ExperienceTracker {
54
53
  getExperienceTableOfContents(state: ActionResult, options?: {
55
54
  includeDescendantExperience?: boolean;
56
55
  }): ExperienceTocEntry[];
56
+ renderExperienceTocFor(state: ActionResult): string;
57
57
  getExperienceSection(fileTag: string, sectionIndex: number, state: ActionResult, options?: {
58
58
  includeDescendantExperience?: boolean;
59
59
  }): {
@@ -61,6 +61,11 @@ export declare class ExperienceTracker {
61
61
  url: string;
62
62
  content: string;
63
63
  } | null;
64
+ buildToc(records: ExperienceFile[]): ExperienceTocEntry[];
65
+ readSectionByHash(fileHash: string, sectionIndex: number): {
66
+ title: string;
67
+ body: string;
68
+ } | null;
64
69
  findExperienceFileByHash(fileHash: string): string | null;
65
70
  listAllExperienceToc(filter?: string, options?: {
66
71
  recency?: 'recent' | 'old';
@@ -1,10 +1,9 @@
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';
9
8
  import { redactSecrets } from './utils/secrets.js';
10
9
  import { isNonReusableCode } from "./utils/step-analyzer.js";
@@ -33,20 +32,13 @@ export class ExperienceTracker {
33
32
  experienceDir;
34
33
  disabled;
35
34
  knowledgeTracker;
36
- constructor(options = {}) {
35
+ constructor(knowledgeTracker, options = {}) {
37
36
  const configParser = ConfigParser.getInstance();
38
37
  const config = configParser.getConfig();
39
- const configPath = configParser.getConfigPath();
40
38
  this.disabled = options.disabled ?? false;
41
- this.knowledgeTracker = new KnowledgeTracker();
39
+ this.knowledgeTracker = knowledgeTracker;
42
40
  // Resolve experience directory relative to the config file location (project root)
43
- if (configPath) {
44
- const projectRoot = dirname(configPath);
45
- this.experienceDir = join(projectRoot, config.dirs?.experience || 'experience');
46
- }
47
- else {
48
- this.experienceDir = config.dirs?.experience || 'experience';
49
- }
41
+ this.experienceDir = configParser.resolveProjectDir(config.dirs?.experience || 'experience');
50
42
  if (!this.disabled) {
51
43
  this.ensureDirectory(this.experienceDir);
52
44
  }
@@ -84,7 +76,9 @@ export class ExperienceTracker {
84
76
  }
85
77
  }
86
78
  readExperienceFile(stateHash) {
87
- const filePath = this.getExperienceFilePath(stateHash);
79
+ return this.readExperienceFileAt(this.getExperienceFilePath(stateHash));
80
+ }
81
+ readExperienceFileAt(filePath) {
88
82
  const fileContent = readFileSync(filePath, 'utf8');
89
83
  const { content, data } = matter(fileContent);
90
84
  return { content, data };
@@ -97,20 +91,6 @@ export class ExperienceTracker {
97
91
  const fileContent = matter.stringify(redactSecrets(content), frontmatter || {});
98
92
  writeFileSync(filePath, fileContent, 'utf8');
99
93
  }
100
- hasRecentExperience(stateHash, prefix = '') {
101
- if (this.disabled) {
102
- return false;
103
- }
104
- if (prefix) {
105
- stateHash = `${prefix}_${stateHash}`;
106
- }
107
- const filePath = this.getExperienceFilePath(stateHash);
108
- if (!existsSync(filePath)) {
109
- return false;
110
- }
111
- const stats = statSync(filePath);
112
- return stats.mtime.getTime() > Date.now() - 1000 * 60 * 60 * 24;
113
- }
114
94
  getExperienceFilePath(stateHash) {
115
95
  return join(this.experienceDir, `${stateHash}.md`);
116
96
  }
@@ -154,7 +134,10 @@ export class ExperienceTracker {
154
134
  this.ensureExperienceFile(state);
155
135
  const stateHash = state.getStateHash();
156
136
  const { content, data } = this.readExperienceFile(stateHash);
157
- if (content.includes(action.code)) {
137
+ if (mdq(content)
138
+ .query('code')
139
+ .meta()
140
+ .some((m) => m.text.trim() === action.code.trim())) {
158
141
  debugLog('Skipping duplicate action', action.code);
159
142
  return;
160
143
  }
@@ -179,7 +162,10 @@ export class ExperienceTracker {
179
162
  this.ensureExperienceFile(state);
180
163
  const stateHash = state.getStateHash();
181
164
  const { content, data } = this.readExperienceFile(stateHash);
182
- if (content.includes(body)) {
165
+ if (mdq(content)
166
+ .query('section2')
167
+ .each()
168
+ .some((s) => s.text().trim() === body.trim())) {
183
169
  debugLog('Skipping duplicate flow body');
184
170
  return;
185
171
  }
@@ -200,25 +186,9 @@ export class ExperienceTracker {
200
186
  continue;
201
187
  }
202
188
  try {
203
- const files = readdirSync(experienceDir)
204
- .filter((file) => file.endsWith('.md'))
205
- .map((file) => join(experienceDir, file));
206
- for (const file of files) {
207
- try {
208
- const content = readFileSync(file, 'utf8');
209
- const parsed = matter(content);
210
- const mtime = statSync(file).mtime;
211
- allFiles.push({
212
- filePath: file,
213
- data: parsed.data,
214
- content: parsed.content,
215
- mtime,
216
- });
217
- }
218
- catch (error) {
219
- debugLog(`Failed to read experience file ${file}:`, error);
220
- }
221
- }
189
+ allFiles.push(...loadMarkdownFiles(experienceDir, {
190
+ onError: (filePath, error) => debugLog(`Failed to read experience file ${filePath}:`, error),
191
+ }));
222
192
  }
223
193
  catch (error) {
224
194
  debugLog(`Failed to read experience directory ${experienceDir}:`, error);
@@ -242,19 +212,18 @@ export class ExperienceTracker {
242
212
  });
243
213
  })
244
214
  .map((experience) => {
245
- const lines = experience.content.split('\n');
246
- if (lines.length <= maxLines)
215
+ if (experience.content.split('\n').length <= maxLines)
247
216
  return experience;
248
- 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 };
249
225
  });
250
226
  }
251
- /**
252
- * Clean up experience tracker (for testing)
253
- */
254
- cleanup() {
255
- // Clear any in-memory state if needed
256
- // The actual files will be cleaned up by test cleanup
257
- }
258
227
  getSuccessfulExperience(state, options) {
259
228
  const records = this.getRelevantExperience(state, {
260
229
  includeDescendantExperience: options?.includeDescendants,
@@ -286,36 +255,48 @@ export class ExperienceTracker {
286
255
  const bHash = basename(b.filePath, '.md');
287
256
  return aHash.localeCompare(bHash);
288
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) {
289
280
  const toc = [];
290
- for (let i = 0; i < sorted.length; i++) {
291
- const record = sorted[i];
292
- const fileHash = basename(record.filePath, '.md');
293
- const url = record.data?.url || '';
281
+ for (const record of records) {
294
282
  const sections = listTocHeadings(record.content);
295
283
  if (sections.length === 0)
296
284
  continue;
297
285
  toc.push({
298
- fileTag: indexToLetters(i),
299
- fileHash,
300
- url,
286
+ fileTag: indexToLetters(toc.length),
287
+ fileHash: basename(record.filePath, '.md'),
288
+ url: record.data?.url || '',
301
289
  sections,
302
290
  });
303
291
  }
304
292
  return toc;
305
293
  }
306
- getExperienceSection(fileTag, sectionIndex, state, options) {
307
- const toc = this.getExperienceTableOfContents(state, options);
308
- const entry = toc.find((e) => e.fileTag === fileTag);
309
- if (!entry)
310
- return null;
311
- const filePath = this.findExperienceFileByHash(entry.fileHash);
294
+ readSectionByHash(fileHash, sectionIndex) {
295
+ const filePath = this.findExperienceFileByHash(fileHash);
312
296
  if (!filePath)
313
297
  return null;
314
- const { content } = this.readExperienceFile(entry.fileHash);
315
- const extracted = extractHeadingSection(content, sectionIndex);
316
- if (!extracted)
317
- return null;
318
- return { title: extracted.title, url: entry.url, content: extracted.body };
298
+ const { content } = this.readExperienceFileAt(filePath);
299
+ return extractHeadingSection(content, sectionIndex);
319
300
  }
320
301
  findExperienceFileByHash(fileHash) {
321
302
  for (const dir of this.getExperienceDirectories()) {
@@ -359,81 +340,34 @@ export class ExperienceTracker {
359
340
  const bUrl = b.data?.url || '';
360
341
  return aUrl.localeCompare(bUrl);
361
342
  });
362
- const toc = [];
363
- for (let i = 0; i < sorted.length; i++) {
364
- const record = sorted[i];
365
- const sections = listTocHeadings(record.content);
366
- if (sections.length === 0)
367
- continue;
368
- toc.push({
369
- fileTag: indexToLetters(toc.length),
370
- fileHash: basename(record.filePath, '.md'),
371
- url: record.data?.url || '',
372
- sections,
373
- });
374
- }
375
- return toc;
343
+ return this.buildToc(sorted);
376
344
  }
377
345
  getExperienceSectionByTag(fileTag, sectionIndex, filter) {
378
346
  const toc = this.listAllExperienceToc(filter);
379
347
  const entry = toc.find((e) => e.fileTag === fileTag);
380
348
  if (!entry)
381
349
  return null;
382
- const filePath = this.findExperienceFileByHash(entry.fileHash);
383
- if (!filePath)
384
- return null;
385
- const { content } = this.readExperienceFile(entry.fileHash);
386
- const extracted = extractHeadingSection(content, sectionIndex);
350
+ const extracted = this.readSectionByHash(entry.fileHash, sectionIndex);
387
351
  if (!extracted)
388
352
  return null;
389
353
  return { title: extracted.title, url: entry.url, content: extracted.body, fileHash: entry.fileHash };
390
354
  }
391
355
  }
392
356
  function listTocHeadings(content) {
393
- const tokens = marked.lexer(content);
394
- const result = [];
395
- let index = 0;
396
- for (const token of tokens) {
397
- if (token.type !== 'heading')
398
- continue;
399
- const heading = token;
400
- if (heading.depth !== 2 && heading.depth !== 3)
401
- continue;
402
- index++;
403
- result.push({ index, level: heading.depth, title: heading.text });
404
- }
405
- 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 }));
406
362
  }
407
363
  function extractHeadingSection(content, sectionIndex) {
408
- const tokens = marked.lexer(content);
409
- const matching = [];
410
- for (let i = 0; i < tokens.length; i++) {
411
- const token = tokens[i];
412
- if (token.type !== 'heading')
413
- continue;
414
- const heading = token;
415
- if (heading.depth !== 2 && heading.depth !== 3)
416
- continue;
417
- matching.push({ tokenIdx: i, depth: heading.depth, text: heading.text });
418
- }
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);
419
367
  if (sectionIndex < 1 || sectionIndex > matching.length)
420
368
  return null;
421
369
  const target = matching[sectionIndex - 1];
422
- let endTokenIdx = tokens.length;
423
- for (let j = target.tokenIdx + 1; j < tokens.length; j++) {
424
- const token = tokens[j];
425
- if (token.type !== 'heading')
426
- continue;
427
- if (token.depth <= target.depth) {
428
- endTokenIdx = j;
429
- break;
430
- }
431
- }
432
- const body = tokens
433
- .slice(target.tokenIdx, endTokenIdx)
434
- .map((t) => t.raw || '')
435
- .join('');
436
- return { title: target.text, body };
370
+ return { title: target.meta.text, body: target.section.text() };
437
371
  }
438
372
  function indexToLetters(index) {
439
373
  let n = index;
@@ -499,21 +433,14 @@ function generateActionContent(title, code, explanation) {
499
433
  return lines.join('\n');
500
434
  }
501
435
  function renderAsHowTo(content) {
502
- const tokens = marked.lexer(content);
503
- let result = '';
504
- for (const token of tokens) {
505
- if (token.type === 'heading' && token.depth === 2) {
506
- const text = token.text.trim();
507
- if (text.startsWith('FLOW:')) {
508
- result += `## HOW to ${text.slice(5).trim()} (multi-step)\n\n`;
509
- continue;
510
- }
511
- if (text.startsWith('ACTION:')) {
512
- result += `## HOW to ${text.slice(7).trim()} (single-step)\n\n`;
513
- continue;
514
- }
515
- }
516
- result += token.raw || '';
517
- }
518
- 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
+ });
519
446
  }
@@ -39,7 +39,6 @@ export declare class ExplorBot {
39
39
  config: ExplorbotConfig;
40
40
  options: ExplorBotOptions;
41
41
  userResolveFn: UserResolveFunction | null;
42
- needsInput: boolean;
43
42
  currentPlan?: Plan;
44
43
  planFeature?: string;
45
44
  lastPlanError: Error | null;
@@ -47,6 +46,8 @@ export declare class ExplorBot {
47
46
  agents: Record<string, any>;
48
47
  sessionPlans: Plan[];
49
48
  lastReportedTestCount: number;
49
+ _experienceTracker?: ExperienceTracker;
50
+ _knowledgeTracker?: KnowledgeTracker;
50
51
  constructor(options?: ExplorBotOptions);
51
52
  get isExploring(): boolean;
52
53
  setUserResolve(fn: UserResolveFunction): void;
@@ -58,12 +59,10 @@ export declare class ExplorBot {
58
59
  openFreshTab(): Promise<void>;
59
60
  getCurrentState(): WebPageState | null;
60
61
  getExplorer(): Explorer;
61
- getKnowledgeTracker(): KnowledgeTracker;
62
- getExperienceTracker(): ExperienceTracker;
62
+ knowledgeTracker(): KnowledgeTracker;
63
+ experienceTracker(): ExperienceTracker;
63
64
  getConfig(): ExplorbotConfig;
64
65
  getOptions(): ExplorBotOptions;
65
- isReady(): boolean;
66
- getConfigParser(): ConfigParser;
67
66
  getProvider(): AIProvider;
68
67
  createAgent<T>(factory: (deps: {
69
68
  explorer: Explorer;
@@ -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
  }