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.
- package/README.md +14 -14
- package/bin/explorbot-cli.ts +4 -3
- package/boat/doc-collector/src/ai/documentarian.ts +22 -27
- package/boat/doc-collector/src/ai/tools.ts +67 -10
- package/boat/doc-collector/src/docbot.ts +28 -7
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +33 -1
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +3 -3
- package/dist/boat/doc-collector/src/ai/documentarian.js +16 -6
- package/dist/boat/doc-collector/src/ai/tools.js +47 -10
- package/dist/boat/doc-collector/src/docbot.js +25 -6
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +29 -0
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/package.json +20 -3
- package/dist/src/action-result.d.ts +8 -10
- package/dist/src/action-result.js +72 -164
- package/dist/src/action.d.ts +3 -10
- package/dist/src/action.js +18 -111
- package/dist/src/ai/captain/web-mode.js +0 -7
- package/dist/src/ai/captain.d.ts +1 -3
- package/dist/src/ai/captain.js +4 -19
- package/dist/src/ai/conversation.d.ts +2 -5
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +0 -4
- package/dist/src/ai/driller.js +3 -12
- package/dist/src/ai/experience-compactor.d.ts +2 -2
- package/dist/src/ai/experience-compactor.js +16 -33
- package/dist/src/ai/fisherman-tools.js +1 -1
- package/dist/src/ai/fisherman.d.ts +0 -1
- package/dist/src/ai/fisherman.js +0 -3
- package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
- package/dist/src/ai/historian/codeceptjs.js +3 -2
- package/dist/src/ai/historian/playwright.d.ts +2 -0
- package/dist/src/ai/historian/playwright.js +3 -2
- package/dist/src/ai/historian/utils.d.ts +0 -1
- package/dist/src/ai/historian/utils.js +0 -1
- package/dist/src/ai/historian.d.ts +5 -4
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +3 -5
- package/dist/src/ai/navigator.js +11 -46
- package/dist/src/ai/pilot.d.ts +1 -3
- package/dist/src/ai/pilot.js +6 -17
- package/dist/src/ai/planner.d.ts +3 -3
- package/dist/src/ai/planner.js +49 -37
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +77 -136
- package/dist/src/ai/quartermaster.d.ts +21 -22
- package/dist/src/ai/quartermaster.js +4 -10
- package/dist/src/ai/rerunner.d.ts +1 -2
- package/dist/src/ai/rerunner.js +14 -22
- package/dist/src/ai/researcher/cache.js +11 -18
- package/dist/src/ai/researcher/deep-analysis.js +3 -6
- package/dist/src/ai/researcher/parser.js +1 -1
- package/dist/src/ai/researcher/research-result.d.ts +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -19
- package/dist/src/ai/researcher/sections.js +5 -1
- package/dist/src/ai/researcher.js +12 -53
- package/dist/src/ai/rules.d.ts +1 -0
- package/dist/src/ai/rules.js +22 -15
- package/dist/src/ai/session-analyst.js +8 -5
- package/dist/src/ai/task-agent.d.ts +1 -1
- package/dist/src/ai/task-agent.js +2 -27
- package/dist/src/ai/tester.d.ts +0 -2
- package/dist/src/ai/tester.js +10 -61
- package/dist/src/ai/tools.d.ts +18 -7
- package/dist/src/ai/tools.js +119 -101
- package/dist/src/commands/compact-command.d.ts +1 -1
- package/dist/src/commands/compact-command.js +6 -6
- package/dist/src/commands/context-command.js +1 -1
- package/dist/src/commands/context-knowledge-command.js +1 -1
- package/dist/src/commands/experience-command.js +1 -1
- package/dist/src/commands/knows-command.js +1 -1
- package/dist/src/commands/learn-command.js +3 -1
- package/dist/src/components/AddKnowledge.d.ts +2 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/App.js +2 -2
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +12 -0
- package/dist/src/experience-tracker.d.ts +11 -6
- package/dist/src/experience-tracker.js +81 -154
- package/dist/src/explorbot.d.ts +4 -5
- package/dist/src/explorbot.js +17 -51
- package/dist/src/explorer.d.ts +4 -15
- package/dist/src/explorer.js +18 -68
- package/dist/src/knowledge-tracker.d.ts +2 -2
- package/dist/src/knowledge-tracker.js +38 -45
- package/dist/src/state-manager.d.ts +6 -31
- package/dist/src/state-manager.js +10 -129
- package/dist/src/utils/aria.d.ts +6 -1
- package/dist/src/utils/aria.js +4 -1
- package/dist/src/utils/cache.d.ts +15 -0
- package/dist/src/utils/cache.js +34 -0
- package/dist/src/utils/context-formatter.d.ts +6 -0
- package/dist/src/utils/context-formatter.js +15 -27
- package/dist/src/utils/html.d.ts +5 -0
- package/dist/src/utils/html.js +71 -0
- package/dist/src/utils/markdown-files.d.ts +10 -0
- package/dist/src/utils/markdown-files.js +21 -0
- package/dist/src/utils/markdown-query.d.ts +6 -0
- package/dist/src/utils/markdown-query.js +14 -1
- package/dist/src/utils/page-readiness.d.ts +1 -0
- package/dist/src/utils/page-readiness.js +1 -1
- package/dist/src/utils/secrets.js +2 -2
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +10 -0
- package/dist/src/utils/web-element.d.ts +2 -1
- package/dist/src/utils/web-element.js +3 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/package.json +20 -3
- package/src/action-result.ts +70 -173
- package/src/action.ts +16 -130
- package/src/ai/captain/web-mode.ts +0 -7
- package/src/ai/captain.ts +5 -17
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +3 -16
- package/src/ai/experience-compactor.ts +16 -35
- package/src/ai/fisherman-tools.ts +1 -1
- package/src/ai/fisherman.ts +0 -4
- package/src/ai/historian/codeceptjs.ts +4 -2
- package/src/ai/historian/playwright.ts +4 -2
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +4 -7
- package/src/ai/navigator.ts +13 -49
- package/src/ai/pilot.ts +6 -16
- package/src/ai/planner.ts +56 -44
- package/src/ai/provider.ts +85 -145
- package/src/ai/quartermaster.ts +34 -41
- package/src/ai/rerunner.ts +17 -21
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/deep-analysis.ts +3 -6
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +5 -1
- package/src/ai/researcher.ts +11 -59
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +3 -30
- package/src/ai/tester.ts +12 -73
- package/src/ai/tools.ts +119 -106
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-command.ts +1 -1
- package/src/commands/context-knowledge-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +3 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +2 -2
- package/src/config.ts +12 -0
- package/src/experience-tracker.ts +88 -151
- package/src/explorbot.ts +17 -51
- package/src/explorer.ts +20 -72
- package/src/knowledge-tracker.ts +42 -49
- package/src/state-manager.ts +15 -156
- package/src/utils/aria.ts +11 -2
- package/src/utils/cache.ts +40 -0
- package/src/utils/context-formatter.ts +12 -15
- package/src/utils/html.ts +79 -0
- package/src/utils/markdown-files.ts +30 -0
- package/src/utils/markdown-query.ts +15 -1
- package/src/utils/page-readiness.ts +1 -1
- package/src/utils/secrets.ts +2 -3
- package/src/utils/strings.ts +12 -0
- package/src/utils/web-element.ts +4 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/dist/boat/api-tester/example/apibot.config.js +0 -30
package/src/components/App.tsx
CHANGED
|
@@ -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.
|
|
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.
|
|
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,12 +1,12 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
2
|
-
import { basename,
|
|
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
11
|
import { redactSecrets } from './utils/secrets.js';
|
|
12
12
|
import { isNonReusableCode } from './utils/step-analyzer.ts';
|
|
@@ -39,20 +39,14 @@ export class ExperienceTracker {
|
|
|
39
39
|
private disabled: boolean;
|
|
40
40
|
private knowledgeTracker: KnowledgeTracker;
|
|
41
41
|
|
|
42
|
-
constructor(options: { disabled?: boolean } = {}) {
|
|
42
|
+
constructor(knowledgeTracker: KnowledgeTracker, options: { disabled?: boolean } = {}) {
|
|
43
43
|
const configParser = ConfigParser.getInstance();
|
|
44
44
|
const config = configParser.getConfig();
|
|
45
|
-
const configPath = configParser.getConfigPath();
|
|
46
45
|
this.disabled = options.disabled ?? false;
|
|
47
|
-
this.knowledgeTracker =
|
|
46
|
+
this.knowledgeTracker = knowledgeTracker;
|
|
48
47
|
|
|
49
48
|
// Resolve experience directory relative to the config file location (project root)
|
|
50
|
-
|
|
51
|
-
const projectRoot = dirname(configPath);
|
|
52
|
-
this.experienceDir = join(projectRoot, config.dirs?.experience || 'experience');
|
|
53
|
-
} else {
|
|
54
|
-
this.experienceDir = config.dirs?.experience || 'experience';
|
|
55
|
-
}
|
|
49
|
+
this.experienceDir = configParser.resolveProjectDir(config.dirs?.experience || 'experience');
|
|
56
50
|
|
|
57
51
|
if (!this.disabled) {
|
|
58
52
|
this.ensureDirectory(this.experienceDir);
|
|
@@ -99,7 +93,10 @@ export class ExperienceTracker {
|
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
readExperienceFile(stateHash: string): { content: string; data: any } {
|
|
102
|
-
|
|
96
|
+
return this.readExperienceFileAt(this.getExperienceFilePath(stateHash));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private readExperienceFileAt(filePath: string): { content: string; data: any } {
|
|
103
100
|
const fileContent = readFileSync(filePath, 'utf8');
|
|
104
101
|
const { content, data } = matter(fileContent);
|
|
105
102
|
return { content, data };
|
|
@@ -114,21 +111,6 @@ export class ExperienceTracker {
|
|
|
114
111
|
writeFileSync(filePath, fileContent, 'utf8');
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
hasRecentExperience(stateHash: string, prefix = ''): boolean {
|
|
118
|
-
if (this.disabled) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
if (prefix) {
|
|
122
|
-
stateHash = `${prefix}_${stateHash}`;
|
|
123
|
-
}
|
|
124
|
-
const filePath = this.getExperienceFilePath(stateHash);
|
|
125
|
-
if (!existsSync(filePath)) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
const stats = statSync(filePath);
|
|
129
|
-
return stats.mtime.getTime() > Date.now() - 1000 * 60 * 60 * 24;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
114
|
private getExperienceFilePath(stateHash: string): string {
|
|
133
115
|
return join(this.experienceDir, `${stateHash}.md`);
|
|
134
116
|
}
|
|
@@ -176,7 +158,12 @@ export class ExperienceTracker {
|
|
|
176
158
|
this.ensureExperienceFile(state);
|
|
177
159
|
const stateHash = state.getStateHash();
|
|
178
160
|
const { content, data } = this.readExperienceFile(stateHash);
|
|
179
|
-
if (
|
|
161
|
+
if (
|
|
162
|
+
mdq(content)
|
|
163
|
+
.query('code')
|
|
164
|
+
.meta()
|
|
165
|
+
.some((m) => m.text.trim() === action.code.trim())
|
|
166
|
+
) {
|
|
180
167
|
debugLog('Skipping duplicate action', action.code);
|
|
181
168
|
return;
|
|
182
169
|
}
|
|
@@ -204,7 +191,12 @@ export class ExperienceTracker {
|
|
|
204
191
|
const stateHash = state.getStateHash();
|
|
205
192
|
const { content, data } = this.readExperienceFile(stateHash);
|
|
206
193
|
|
|
207
|
-
if (
|
|
194
|
+
if (
|
|
195
|
+
mdq(content)
|
|
196
|
+
.query('section2')
|
|
197
|
+
.each()
|
|
198
|
+
.some((s) => s.text().trim() === body.trim())
|
|
199
|
+
) {
|
|
208
200
|
debugLog('Skipping duplicate flow body');
|
|
209
201
|
return;
|
|
210
202
|
}
|
|
@@ -231,25 +223,11 @@ export class ExperienceTracker {
|
|
|
231
223
|
}
|
|
232
224
|
|
|
233
225
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
const content = readFileSync(file, 'utf8');
|
|
241
|
-
const parsed = matter(content);
|
|
242
|
-
const mtime = statSync(file).mtime;
|
|
243
|
-
allFiles.push({
|
|
244
|
-
filePath: file,
|
|
245
|
-
data: parsed.data,
|
|
246
|
-
content: parsed.content,
|
|
247
|
-
mtime,
|
|
248
|
-
});
|
|
249
|
-
} catch (error) {
|
|
250
|
-
debugLog(`Failed to read experience file ${file}:`, error);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
226
|
+
allFiles.push(
|
|
227
|
+
...loadMarkdownFiles(experienceDir, {
|
|
228
|
+
onError: (filePath, error) => debugLog(`Failed to read experience file ${filePath}:`, error),
|
|
229
|
+
})
|
|
230
|
+
);
|
|
253
231
|
} catch (error) {
|
|
254
232
|
debugLog(`Failed to read experience directory ${experienceDir}:`, error);
|
|
255
233
|
}
|
|
@@ -275,20 +253,17 @@ export class ExperienceTracker {
|
|
|
275
253
|
});
|
|
276
254
|
})
|
|
277
255
|
.map((experience) => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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 };
|
|
281
264
|
});
|
|
282
265
|
}
|
|
283
266
|
|
|
284
|
-
/**
|
|
285
|
-
* Clean up experience tracker (for testing)
|
|
286
|
-
*/
|
|
287
|
-
cleanup(): void {
|
|
288
|
-
// Clear any in-memory state if needed
|
|
289
|
-
// The actual files will be cleaned up by test cleanup
|
|
290
|
-
}
|
|
291
|
-
|
|
292
267
|
getSuccessfulExperience(state: ActionResult, options?: { includeDescendants?: boolean; stripCode?: boolean }): string[] {
|
|
293
268
|
const records = this.getRelevantExperience(state, {
|
|
294
269
|
includeDescendantExperience: options?.includeDescendants,
|
|
@@ -326,36 +301,50 @@ export class ExperienceTracker {
|
|
|
326
301
|
return aHash.localeCompare(bHash);
|
|
327
302
|
});
|
|
328
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[] {
|
|
329
329
|
const toc: ExperienceTocEntry[] = [];
|
|
330
|
-
for (
|
|
331
|
-
const record = sorted[i];
|
|
332
|
-
const fileHash = basename(record.filePath, '.md');
|
|
333
|
-
const url = (record.data as WebPageState)?.url || '';
|
|
330
|
+
for (const record of records) {
|
|
334
331
|
const sections = listTocHeadings(record.content);
|
|
335
332
|
if (sections.length === 0) continue;
|
|
336
333
|
toc.push({
|
|
337
|
-
fileTag: indexToLetters(
|
|
338
|
-
fileHash,
|
|
339
|
-
url,
|
|
334
|
+
fileTag: indexToLetters(toc.length),
|
|
335
|
+
fileHash: basename(record.filePath, '.md'),
|
|
336
|
+
url: (record.data as WebPageState)?.url || '',
|
|
340
337
|
sections,
|
|
341
338
|
});
|
|
342
339
|
}
|
|
343
340
|
return toc;
|
|
344
341
|
}
|
|
345
342
|
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
const entry = toc.find((e) => e.fileTag === fileTag);
|
|
349
|
-
if (!entry) return null;
|
|
350
|
-
|
|
351
|
-
const filePath = this.findExperienceFileByHash(entry.fileHash);
|
|
343
|
+
private readSectionByHash(fileHash: string, sectionIndex: number): { title: string; body: string } | null {
|
|
344
|
+
const filePath = this.findExperienceFileByHash(fileHash);
|
|
352
345
|
if (!filePath) return null;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const extracted = extractHeadingSection(content, sectionIndex);
|
|
356
|
-
if (!extracted) return null;
|
|
357
|
-
|
|
358
|
-
return { title: extracted.title, url: entry.url, content: extracted.body };
|
|
346
|
+
const { content } = this.readExperienceFileAt(filePath);
|
|
347
|
+
return extractHeadingSection(content, sectionIndex);
|
|
359
348
|
}
|
|
360
349
|
|
|
361
350
|
private findExperienceFileByHash(fileHash: string): string | null {
|
|
@@ -402,19 +391,7 @@ export class ExperienceTracker {
|
|
|
402
391
|
return aUrl.localeCompare(bUrl);
|
|
403
392
|
});
|
|
404
393
|
|
|
405
|
-
|
|
406
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
407
|
-
const record = sorted[i];
|
|
408
|
-
const sections = listTocHeadings(record.content);
|
|
409
|
-
if (sections.length === 0) continue;
|
|
410
|
-
toc.push({
|
|
411
|
-
fileTag: indexToLetters(toc.length),
|
|
412
|
-
fileHash: basename(record.filePath, '.md'),
|
|
413
|
-
url: (record.data as WebPageState)?.url || '',
|
|
414
|
-
sections,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
return toc;
|
|
394
|
+
return this.buildToc(sorted);
|
|
418
395
|
}
|
|
419
396
|
|
|
420
397
|
getExperienceSectionByTag(fileTag: string, sectionIndex: number, filter?: string): { title: string; url: string; content: string; fileHash: string } | null {
|
|
@@ -422,11 +399,7 @@ export class ExperienceTracker {
|
|
|
422
399
|
const entry = toc.find((e) => e.fileTag === fileTag);
|
|
423
400
|
if (!entry) return null;
|
|
424
401
|
|
|
425
|
-
const
|
|
426
|
-
if (!filePath) return null;
|
|
427
|
-
|
|
428
|
-
const { content } = this.readExperienceFile(entry.fileHash);
|
|
429
|
-
const extracted = extractHeadingSection(content, sectionIndex);
|
|
402
|
+
const extracted = this.readSectionByHash(entry.fileHash, sectionIndex);
|
|
430
403
|
if (!extracted) return null;
|
|
431
404
|
|
|
432
405
|
return { title: extracted.title, url: entry.url, content: extracted.body, fileHash: entry.fileHash };
|
|
@@ -434,49 +407,22 @@ export class ExperienceTracker {
|
|
|
434
407
|
}
|
|
435
408
|
|
|
436
409
|
function listTocHeadings(content: string): { index: number; level: 2 | 3; title: string }[] {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const heading = token as Tokens.Heading;
|
|
443
|
-
if (heading.depth !== 2 && heading.depth !== 3) continue;
|
|
444
|
-
index++;
|
|
445
|
-
result.push({ index, level: heading.depth as 2 | 3, title: heading.text });
|
|
446
|
-
}
|
|
447
|
-
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 }));
|
|
448
415
|
}
|
|
449
416
|
|
|
450
417
|
function extractHeadingSection(content: string, sectionIndex: number): { title: string; body: string } | null {
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
455
|
-
const token = tokens[i];
|
|
456
|
-
if (token.type !== 'heading') continue;
|
|
457
|
-
const heading = token as Tokens.Heading;
|
|
458
|
-
if (heading.depth !== 2 && heading.depth !== 3) continue;
|
|
459
|
-
matching.push({ tokenIdx: i, depth: heading.depth, text: heading.text });
|
|
460
|
-
}
|
|
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);
|
|
461
421
|
|
|
462
422
|
if (sectionIndex < 1 || sectionIndex > matching.length) return null;
|
|
463
423
|
|
|
464
424
|
const target = matching[sectionIndex - 1];
|
|
465
|
-
|
|
466
|
-
for (let j = target.tokenIdx + 1; j < tokens.length; j++) {
|
|
467
|
-
const token = tokens[j];
|
|
468
|
-
if (token.type !== 'heading') continue;
|
|
469
|
-
if ((token as Tokens.Heading).depth <= target.depth) {
|
|
470
|
-
endTokenIdx = j;
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const body = tokens
|
|
476
|
-
.slice(target.tokenIdx, endTokenIdx)
|
|
477
|
-
.map((t) => (t as any).raw || '')
|
|
478
|
-
.join('');
|
|
479
|
-
return { title: target.text, body };
|
|
425
|
+
return { title: target.meta.text, body: target.section.text() };
|
|
480
426
|
}
|
|
481
427
|
|
|
482
428
|
function indexToLetters(index: number): string {
|
|
@@ -545,23 +491,14 @@ function generateActionContent(title: string, code: string, explanation?: string
|
|
|
545
491
|
}
|
|
546
492
|
|
|
547
493
|
function renderAsHowTo(content: string): string {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (text.startsWith('
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
if (text.startsWith('ACTION:')) {
|
|
558
|
-
result += `## HOW to ${text.slice(7).trim()} (single-step)\n\n`;
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
result += (token as any).raw || '';
|
|
563
|
-
}
|
|
564
|
-
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
|
+
});
|
|
565
502
|
}
|
|
566
503
|
|
|
567
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 =
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
return this.explorer.getKnowledgeTracker();
|
|
139
|
-
}
|
|
140
|
-
return new KnowledgeTracker();
|
|
136
|
+
knowledgeTracker(): KnowledgeTracker {
|
|
137
|
+
return (this._knowledgeTracker ||= new KnowledgeTracker());
|
|
141
138
|
}
|
|
142
139
|
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
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
|
|
209
|
-
|
|
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
|
|
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.
|
|
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 =
|
|
478
|
+
const message = browserErrorMessage(error);
|
|
513
479
|
tag('warning').log(`Session analysis failed: ${message}`);
|
|
514
480
|
}
|
|
515
481
|
}
|