explorbot 0.4.0 → 0.4.1
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/bin/explorbot-cli.ts +5 -3
- package/boat/api-tester/src/ai/chief.ts +7 -1
- package/boat/api-tester/src/ai/curler.ts +7 -1
- package/boat/api-tester/src/apibot.ts +10 -4
- package/boat/api-tester/src/cli.ts +12 -2
- package/boat/api-tester/src/config.ts +28 -8
- package/boat/doc-collector/bin/doc-collector-cli.ts +3 -2
- package/boat/doc-collector/src/cli.ts +3 -1
- package/boat/doc-collector/src/docbot.ts +1 -1
- package/boat/prima/bin/prima-cli.ts +2 -0
- package/boat/prima/src/cli.ts +2 -0
- package/dist/bin/explorbot-cli.js +5 -3
- package/dist/boat/api-tester/bin/apibot-cli.js +3 -2
- package/dist/boat/api-tester/src/ai/chief.js +6 -1
- package/dist/boat/api-tester/src/ai/curler.js +6 -1
- package/dist/boat/api-tester/src/apibot.js +7 -3
- package/dist/boat/api-tester/src/cli.js +12 -2
- package/dist/boat/api-tester/src/config.js +31 -8
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +3 -2
- package/dist/boat/doc-collector/src/cli.js +3 -1
- package/dist/boat/doc-collector/src/docbot.js +1 -1
- package/dist/boat/prima/bin/prima-cli.js +2 -0
- package/dist/boat/prima/src/cli.js +3 -0
- package/dist/package.json +1 -1
- package/dist/rules/planner/styles/normal.md +1 -1
- package/dist/src/ai/captain.js +1 -1
- package/dist/src/ai/navigator.js +1 -1
- package/dist/src/ai/planner.js +9 -7
- package/dist/src/ai/researcher.js +2 -0
- package/dist/src/ai/rules.js +3 -3
- package/dist/src/api/spec-reader.js +1 -1
- package/dist/src/commands/config-command.js +1 -1
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/explore-command.js +12 -1
- package/dist/src/commands/options/base-option.d.ts +8 -0
- package/dist/src/commands/options/base-option.js +12 -0
- package/dist/src/commands/options/index.d.ts +5 -0
- package/dist/src/commands/options/index.js +5 -0
- package/dist/src/commands/options/knowledge-option.d.ts +7 -0
- package/dist/src/commands/options/knowledge-option.js +12 -0
- package/dist/src/commands/options/ws-option.d.ts +7 -0
- package/dist/src/commands/options/ws-option.js +21 -0
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.js +11 -0
- package/dist/src/explorbot.js +1 -1
- package/dist/src/knowledge-tracker.d.ts +20 -7
- package/dist/src/knowledge-tracker.js +69 -31
- package/dist/src/remote.d.ts +0 -3
- package/dist/src/remote.js +0 -18
- package/docs/api-testing/basics.md +15 -0
- package/docs/api-testing/planning.md +10 -1
- package/docs/reference/commands.md +24 -4
- package/docs/workflow/agentic-usage.md +11 -2
- package/docs/workflow/knowledge.md +46 -2
- package/package.json +1 -1
- package/rules/planner/styles/normal.md +1 -1
- package/src/ai/captain.ts +1 -1
- package/src/ai/navigator.ts +1 -1
- package/src/ai/planner.ts +9 -8
- package/src/ai/researcher.ts +1 -0
- package/src/ai/rules.ts +3 -3
- package/src/api/spec-reader.ts +1 -1
- package/src/commands/config-command.ts +1 -1
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/explore-command.ts +12 -1
- package/src/commands/options/base-option.ts +18 -0
- package/src/commands/options/index.ts +7 -0
- package/src/commands/options/knowledge-option.ts +14 -0
- package/src/commands/options/ws-option.ts +24 -0
- package/src/config.ts +11 -0
- package/src/explorbot.ts +1 -1
- package/src/knowledge-tracker.ts +94 -36
- package/src/remote.ts +0 -20
package/src/knowledge-tracker.ts
CHANGED
|
@@ -11,36 +11,54 @@ import { loadMarkdownFiles } from './utils/markdown-files.js';
|
|
|
11
11
|
import { mdq } from './utils/markdown-query.js';
|
|
12
12
|
import { isSecretName, registerSecret } from './utils/secrets.js';
|
|
13
13
|
import { slugify } from './utils/strings.js';
|
|
14
|
+
import { extractStatePath, matchesUrl } from './utils/url-matcher.js';
|
|
14
15
|
|
|
15
16
|
const debugLog = createDebug('explorbot:knowledge-tracker');
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
filePath: string;
|
|
19
|
-
url: string;
|
|
20
|
-
content: string;
|
|
21
|
-
[key: string]: any;
|
|
22
|
-
}
|
|
18
|
+
const sessionEntries: string[] = [];
|
|
23
19
|
|
|
24
20
|
export class KnowledgeTracker {
|
|
25
21
|
private knowledgeDir: string;
|
|
26
22
|
private knowledgeFiles: Knowledge[] = [];
|
|
23
|
+
private sessionKnowledge: Knowledge[] = [];
|
|
27
24
|
private isLoaded = false;
|
|
28
25
|
private applicationSpec?: ApplicationSpec;
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
static appendSessionKnowledge(text: string): void {
|
|
28
|
+
sessionEntries.push(text);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static resetSessionKnowledge(): void {
|
|
32
|
+
sessionEntries.length = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
constructor(options: KnowledgeTrackerOptions = {}) {
|
|
36
|
+
let knowledgeDir = options.knowledgeDir;
|
|
37
|
+
let specPath = options.applicationSpec;
|
|
38
|
+
|
|
39
|
+
if (!knowledgeDir) {
|
|
40
|
+
const configParser = ConfigParser.getInstance();
|
|
41
|
+
const config = configParser.getConfig();
|
|
42
|
+
knowledgeDir = configParser.resolveProjectDir(config.dirs?.knowledge || 'knowledge');
|
|
43
|
+
specPath ||= config.dirs?.spec;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.knowledgeDir = knowledgeDir;
|
|
34
47
|
|
|
35
48
|
if (!existsSync(this.knowledgeDir)) {
|
|
36
49
|
mkdirSync(this.knowledgeDir, { recursive: true });
|
|
37
50
|
}
|
|
38
51
|
|
|
39
|
-
const specPath = applicationSpecPath || config.dirs?.spec;
|
|
40
52
|
if (specPath) {
|
|
41
53
|
this.applicationSpec = new ApplicationSpec(specPath);
|
|
42
54
|
tag('info').log(`Loaded application spec with ${this.applicationSpec.pageCount} documented pages`);
|
|
43
55
|
}
|
|
56
|
+
|
|
57
|
+
this.sessionKnowledge = sessionEntries.map((entry, index) => {
|
|
58
|
+
const parsed = matter(entry);
|
|
59
|
+
debugLog(`Session knowledge #${index + 1}`);
|
|
60
|
+
return this.toKnowledge(`--knowledge #${index + 1}`, parsed.data, parsed.content.trim());
|
|
61
|
+
});
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
private loadKnowledgeFiles(): void {
|
|
@@ -49,12 +67,7 @@ export class KnowledgeTracker {
|
|
|
49
67
|
this.knowledgeFiles = [];
|
|
50
68
|
|
|
51
69
|
for (const entry of loadMarkdownFiles(this.knowledgeDir, { recursive: true })) {
|
|
52
|
-
this.knowledgeFiles.push(
|
|
53
|
-
filePath: entry.filePath,
|
|
54
|
-
url: entry.data.url || entry.data.path || '*',
|
|
55
|
-
content: this.interpolateVars(entry.content),
|
|
56
|
-
...entry.data,
|
|
57
|
-
});
|
|
70
|
+
this.knowledgeFiles.push(this.toKnowledge(entry.filePath, entry.data, entry.content));
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
this.isLoaded = true;
|
|
@@ -63,28 +76,22 @@ export class KnowledgeTracker {
|
|
|
63
76
|
getRelevantKnowledge(state: ActionResult): Knowledge[] {
|
|
64
77
|
this.loadKnowledgeFiles();
|
|
65
78
|
|
|
66
|
-
return this.
|
|
67
|
-
return state.isMatchedBy(knowledge);
|
|
68
|
-
});
|
|
79
|
+
return this.allKnowledge().filter((knowledge) => knowledge.url && state.isMatchedBy(knowledge));
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
getEndpointKnowledge(endpoint: string): Knowledge[] {
|
|
83
|
+
this.loadKnowledgeFiles();
|
|
84
|
+
const path = extractStatePath(endpoint);
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.filter((k) => !!k)
|
|
78
|
-
.join('\n\n');
|
|
86
|
+
return this.allKnowledge().filter((knowledge) => knowledge.endpoint && matchesUrl(knowledge.endpoint, path));
|
|
87
|
+
}
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
Here is relevant knowledge for this page:
|
|
89
|
+
renderRelevantKnowledge(state: ActionResult): string {
|
|
90
|
+
return this.renderKnowledge(this.getRelevantKnowledge(state), 'page');
|
|
91
|
+
}
|
|
84
92
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`;
|
|
93
|
+
renderEndpointKnowledge(endpoint: string): string {
|
|
94
|
+
return this.renderKnowledge(this.getEndpointKnowledge(endpoint), 'endpoint');
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
renderRelevantContext(state: ActionResult): string {
|
|
@@ -188,7 +195,7 @@ export class KnowledgeTracker {
|
|
|
188
195
|
getExistingUrls(): string[] {
|
|
189
196
|
this.loadKnowledgeFiles();
|
|
190
197
|
|
|
191
|
-
return this.knowledgeFiles.map((knowledge) => knowledge.url).filter((url) => url && url !== '*');
|
|
198
|
+
return this.knowledgeFiles.map((knowledge) => knowledge.url || '').filter((url) => url && url !== '*');
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
getKnowledgeForUrl(urlPattern: string): string[] {
|
|
@@ -205,7 +212,7 @@ export class KnowledgeTracker {
|
|
|
205
212
|
const content = knowledge.content.trim();
|
|
206
213
|
const firstLine = mdq(content).meta()[0]?.text.split('\n')[0]?.trim() || '';
|
|
207
214
|
return {
|
|
208
|
-
url: knowledge.url,
|
|
215
|
+
url: knowledge.url || knowledge.endpoint || '',
|
|
209
216
|
firstLine,
|
|
210
217
|
filePath: knowledge.filePath,
|
|
211
218
|
};
|
|
@@ -242,4 +249,55 @@ export class KnowledgeTracker {
|
|
|
242
249
|
|
|
243
250
|
return result;
|
|
244
251
|
}
|
|
252
|
+
|
|
253
|
+
private allKnowledge(): Knowledge[] {
|
|
254
|
+
return [...this.knowledgeFiles, ...this.sessionKnowledge];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private toKnowledge(filePath: string, data: Record<string, any>, content: string): Knowledge {
|
|
258
|
+
const knowledge: Knowledge = {
|
|
259
|
+
...data,
|
|
260
|
+
filePath,
|
|
261
|
+
url: data.url || data.path,
|
|
262
|
+
content: this.interpolateVars(content),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
if (!data.url && !data.path && !data.endpoint) {
|
|
266
|
+
knowledge.url = '*';
|
|
267
|
+
knowledge.endpoint = '*';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return knowledge;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private renderKnowledge(knowledgeFiles: Knowledge[], scope: string): string {
|
|
274
|
+
if (knowledgeFiles.length === 0) return '';
|
|
275
|
+
|
|
276
|
+
const knowledgeContent = knowledgeFiles
|
|
277
|
+
.map((k) => k.content)
|
|
278
|
+
.filter((k) => !!k)
|
|
279
|
+
.join('\n\n');
|
|
280
|
+
|
|
281
|
+
tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
|
|
282
|
+
return dedent`
|
|
283
|
+
<knowledge>
|
|
284
|
+
Here is relevant knowledge for this ${scope}:
|
|
285
|
+
|
|
286
|
+
${knowledgeContent}
|
|
287
|
+
</knowledge>
|
|
288
|
+
`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface Knowledge {
|
|
293
|
+
filePath: string;
|
|
294
|
+
url?: string;
|
|
295
|
+
endpoint?: string;
|
|
296
|
+
content: string;
|
|
297
|
+
[key: string]: any;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface KnowledgeTrackerOptions {
|
|
301
|
+
applicationSpec?: string;
|
|
302
|
+
knowledgeDir?: string;
|
|
245
303
|
}
|
package/src/remote.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
1
|
import stripAnsi from 'strip-ansi';
|
|
3
2
|
import { type ActivityEntry, addActivityListener } from './activity.ts';
|
|
4
3
|
import { executionController } from './execution-controller.ts';
|
|
@@ -30,15 +29,6 @@ export class Remote implements LogDestination {
|
|
|
30
29
|
private askCounter = 0;
|
|
31
30
|
private lastActivity: string | null = null;
|
|
32
31
|
|
|
33
|
-
registerOption(program: Command): void {
|
|
34
|
-
program.option('--ws <url>', 'Stream this run to a remote UI over WebSocket');
|
|
35
|
-
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
36
|
-
const url = actionCommand.optsWithGlobals().ws || process.env.EXPLORBOT_WS_URL;
|
|
37
|
-
if (!url) return;
|
|
38
|
-
this.attach(String(url), this.commandPath(actionCommand));
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
32
|
attach(url: string, command: string): void {
|
|
43
33
|
if (this.url) return;
|
|
44
34
|
this.url = url;
|
|
@@ -225,16 +215,6 @@ export class Remote implements LogDestination {
|
|
|
225
215
|
if (typeof failure.message === 'string') return failure.message;
|
|
226
216
|
return undefined;
|
|
227
217
|
}
|
|
228
|
-
|
|
229
|
-
private commandPath(command: Command): string {
|
|
230
|
-
const parts: string[] = [];
|
|
231
|
-
let node: Command | null = command;
|
|
232
|
-
while (node) {
|
|
233
|
-
parts.unshift(node.name());
|
|
234
|
-
node = node.parent;
|
|
235
|
-
}
|
|
236
|
-
return parts.slice(1).join(' ') || parts.join(' ');
|
|
237
|
-
}
|
|
238
218
|
}
|
|
239
219
|
|
|
240
220
|
export const remote = new Remote();
|