explorbot 0.1.12 → 0.1.15
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 +21 -21
- package/dist/bin/explorbot-cli.js +3 -3
- package/dist/package.json +4 -2
- package/dist/rules/researcher/container-rules.md +2 -0
- package/dist/src/action-result.js +2 -1
- package/dist/src/action.js +3 -8
- package/dist/src/ai/captain.js +0 -2
- package/dist/src/ai/conversation.js +20 -4
- package/dist/src/ai/driller.js +1108 -0
- package/dist/src/ai/historian/utils.js +8 -1
- package/dist/src/ai/pilot.js +214 -267
- package/dist/src/ai/provider.js +25 -12
- package/dist/src/ai/quartermaster.js +2 -2
- package/dist/src/ai/rules.js +5 -5
- package/dist/src/ai/session-analyst.js +122 -0
- package/dist/src/ai/tester.js +69 -22
- package/dist/src/ai/tools.js +19 -4
- package/dist/src/commands/base-command.js +6 -6
- package/dist/src/commands/drill-command.js +3 -2
- package/dist/src/commands/exit-command.js +1 -0
- package/dist/src/commands/explore-command.js +9 -2
- package/dist/src/components/AddRule.js +1 -1
- package/dist/src/components/StatusPane.js +6 -1
- package/dist/src/experience-tracker.js +9 -0
- package/dist/src/explorbot.js +48 -8
- package/dist/src/explorer.js +11 -13
- package/dist/src/reporter.js +105 -4
- package/dist/src/state-manager.js +4 -3
- package/dist/src/stats.js +7 -1
- package/dist/src/test-plan.js +47 -3
- package/dist/src/utils/aria.js +354 -529
- package/dist/src/utils/hooks-runner.js +2 -8
- package/dist/src/utils/html.js +371 -0
- package/dist/src/utils/unique-names.js +12 -1
- package/dist/src/utils/url-matcher.js +6 -1
- package/dist/src/utils/web-element.js +27 -24
- package/dist/src/utils/xpath.js +1 -1
- package/package.json +4 -2
- package/rules/researcher/container-rules.md +2 -0
- package/src/action-result.ts +2 -1
- package/src/action.ts +3 -10
- package/src/ai/captain.ts +0 -2
- package/src/ai/conversation.ts +21 -4
- package/src/ai/driller.ts +1194 -0
- package/src/ai/historian/utils.ts +8 -1
- package/src/ai/pilot.ts +215 -265
- package/src/ai/provider.ts +24 -12
- package/src/ai/quartermaster.ts +2 -2
- package/src/ai/rules.ts +5 -5
- package/src/ai/session-analyst.ts +139 -0
- package/src/ai/tester.ts +63 -20
- package/src/ai/tools.ts +18 -4
- package/src/commands/base-command.ts +6 -6
- package/src/commands/drill-command.ts +3 -2
- package/src/commands/exit-command.ts +1 -0
- package/src/commands/explore-command.ts +10 -2
- package/src/components/AddRule.tsx +1 -1
- package/src/components/StatusPane.tsx +6 -3
- package/src/config.ts +4 -0
- package/src/experience-tracker.ts +9 -0
- package/src/explorbot.ts +55 -10
- package/src/explorer.ts +10 -12
- package/src/reporter.ts +108 -4
- package/src/state-manager.ts +4 -3
- package/src/stats.ts +10 -1
- package/src/test-plan.ts +62 -3
- package/src/utils/aria.ts +367 -537
- package/src/utils/hooks-runner.ts +2 -6
- package/src/utils/html.ts +381 -0
- package/src/utils/unique-names.ts +13 -0
- package/src/utils/url-matcher.ts +5 -1
- package/src/utils/web-element.ts +31 -28
- package/src/utils/xpath.ts +1 -1
- package/dist/src/ai/bosun.js +0 -456
- package/src/ai/bosun.ts +0 -571
package/bin/explorbot-cli.ts
CHANGED
|
@@ -586,32 +586,32 @@ addCommonOptions(program.command('research <url>').description('Research a page
|
|
|
586
586
|
}
|
|
587
587
|
);
|
|
588
588
|
|
|
589
|
-
addCommonOptions(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
589
|
+
addCommonOptions(
|
|
590
|
+
program.command('drill <url>').alias('driller').description('Drill all components on a page to learn interactions').option('--knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max-components <count>', 'Maximum number of components to drill')
|
|
591
|
+
).action(async (url, options) => {
|
|
592
|
+
try {
|
|
593
|
+
const explorBot = new ExplorBot(buildExplorBotOptions(url, options));
|
|
594
|
+
await explorBot.start();
|
|
594
595
|
|
|
595
|
-
|
|
596
|
+
await explorBot.visit(url);
|
|
596
597
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
598
|
+
const plan = await explorBot.agentDriller().drill({
|
|
599
|
+
knowledgePath: options.knowledge,
|
|
600
|
+
maxComponents: Number.parseInt(options.maxComponents || '30', 10),
|
|
601
|
+
interactive: false,
|
|
602
|
+
});
|
|
602
603
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
604
|
+
console.log(`\nDrill completed: ${plan.tests.length} components`);
|
|
605
|
+
console.log(`Successful: ${plan.tests.filter((t) => t.isSuccessful).length}`);
|
|
606
|
+
console.log(`Failed: ${plan.tests.filter((t) => t.hasFailed).length}`);
|
|
606
607
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
608
|
+
await explorBot.stop();
|
|
609
|
+
await showStatsAndExit(0);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
612
|
+
await showStatsAndExit(1);
|
|
613
613
|
}
|
|
614
|
-
);
|
|
614
|
+
});
|
|
615
615
|
|
|
616
616
|
program
|
|
617
617
|
.command('context <url>')
|
|
@@ -531,14 +531,14 @@ addCommonOptions(program.command('research <url>').description('Research a page
|
|
|
531
531
|
await showStatsAndExit(1);
|
|
532
532
|
}
|
|
533
533
|
});
|
|
534
|
-
addCommonOptions(program.command('drill <url>').description('Drill all components on a page to learn interactions').option('--knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max <count>', 'Maximum number of components to drill'
|
|
534
|
+
addCommonOptions(program.command('drill <url>').alias('driller').description('Drill all components on a page to learn interactions').option('--knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max-components <count>', 'Maximum number of components to drill')).action(async (url, options) => {
|
|
535
535
|
try {
|
|
536
536
|
const explorBot = new ExplorBot(buildExplorBotOptions(url, options));
|
|
537
537
|
await explorBot.start();
|
|
538
538
|
await explorBot.visit(url);
|
|
539
|
-
const plan = await explorBot.
|
|
539
|
+
const plan = await explorBot.agentDriller().drill({
|
|
540
540
|
knowledgePath: options.knowledge,
|
|
541
|
-
maxComponents: Number.parseInt(options.
|
|
541
|
+
maxComponents: Number.parseInt(options.maxComponents || '30', 10),
|
|
542
542
|
interactive: false,
|
|
543
543
|
});
|
|
544
544
|
console.log(`\nDrill completed: ${plan.tests.length} components`);
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "explorbot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "CLI app built with React Ink, CodeceptJS, and Playwright",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"@ai-sdk/openai": "^3.0",
|
|
68
68
|
"@axe-core/playwright": "^4.11.0",
|
|
69
69
|
"@codeceptjs/reflection": "^0.5.2",
|
|
70
|
+
"@faker-js/faker": "^10.4.0",
|
|
70
71
|
"@inkjs/ui": "^2.0.0",
|
|
71
72
|
"@langfuse/otel": "^4.5.1",
|
|
72
73
|
"@openrouter/ai-sdk-provider": "^2.3.3",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"@opentelemetry/sdk-trace-base": "^2.2.0",
|
|
79
80
|
"@opentelemetry/semantic-conventions": "^1.38.0",
|
|
80
81
|
"@scalar/openapi-parser": "^0.25.6",
|
|
81
|
-
"@testomatio/reporter": "^2.7.
|
|
82
|
+
"@testomatio/reporter": "^2.7.9-beta.3-markdown",
|
|
82
83
|
"ai": "^6.0.6",
|
|
83
84
|
"axe-core": "^4.11.1",
|
|
84
85
|
"bash-tool": "^1.3.15",
|
|
@@ -109,6 +110,7 @@
|
|
|
109
110
|
"strip-ansi": "^7.1.2",
|
|
110
111
|
"turndown": "^7.2.1",
|
|
111
112
|
"unique-names-generator": "^4.7.1",
|
|
113
|
+
"yaml": "^2.8.3",
|
|
112
114
|
"yargs": "^17.7.2",
|
|
113
115
|
"zod": "^4.1.8"
|
|
114
116
|
},
|
|
@@ -5,6 +5,8 @@ Container CSS must be a SINGLE semantic selector — one class, one id, or one a
|
|
|
5
5
|
- VALID: semantic class names that describe what the section IS (`.product-list`, `.sidebar-menu`, `.user-profile`, `.search-results`), semantic roles (`[role="dialog"]`), semantic ids (`#main-content`)
|
|
6
6
|
|
|
7
7
|
The container must uniquely identify a semantic wrapper, not a path through the DOM.
|
|
8
|
+
|
|
9
|
+
Iframes are not sections — list each iframe as a single row of type `iframe` inside the section that contains it. Never use an iframe selector as a section's `> Container:`.
|
|
8
10
|
</container_rules>
|
|
9
11
|
|
|
10
12
|
<css_selector_rules>
|
|
@@ -371,8 +371,9 @@ export class ActionResult {
|
|
|
371
371
|
try {
|
|
372
372
|
const urlObj = new URL(this.url);
|
|
373
373
|
const path = urlObj.pathname.replace(/\/$/, '') || '/';
|
|
374
|
+
const search = urlObj.search || '';
|
|
374
375
|
const hash = urlObj.hash || '';
|
|
375
|
-
return path + hash;
|
|
376
|
+
return path + search + hash;
|
|
376
377
|
}
|
|
377
378
|
catch {
|
|
378
379
|
// If URL parsing fails, assume it's already a relative URL
|
package/dist/src/action.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { faker } from '@faker-js/faker';
|
|
3
4
|
import { context, trace } from '@opentelemetry/api';
|
|
4
5
|
import { highlight } from 'cli-highlight';
|
|
5
6
|
import { container, recorder } from 'codeceptjs';
|
|
@@ -218,8 +219,8 @@ class Action {
|
|
|
218
219
|
await sleep(this.config.action?.delay || 500);
|
|
219
220
|
}
|
|
220
221
|
else {
|
|
221
|
-
const codeFunction = new Function('I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', sanitizedCode);
|
|
222
|
-
codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step);
|
|
222
|
+
const codeFunction = new Function('I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', 'faker', sanitizedCode);
|
|
223
|
+
codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step, faker);
|
|
223
224
|
await recorder.add(() => sleep(this.config.action?.delay || 500));
|
|
224
225
|
await recorder.promise();
|
|
225
226
|
}
|
|
@@ -324,12 +325,6 @@ class Action {
|
|
|
324
325
|
}
|
|
325
326
|
catch (error) {
|
|
326
327
|
this.lastError = error;
|
|
327
|
-
if (error && typeof error === 'object') {
|
|
328
|
-
const errorObj = error;
|
|
329
|
-
if (typeof errorObj.fetchDetails === 'function') {
|
|
330
|
-
await errorObj.fetchDetails();
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
328
|
debugLog(`Attempt failed: ${codeBlock}: ${errorToString(error) || this.lastError?.toString()}`);
|
|
334
329
|
return false;
|
|
335
330
|
}
|
package/dist/src/ai/captain.js
CHANGED
|
@@ -8,6 +8,7 @@ export class Conversation {
|
|
|
8
8
|
messages;
|
|
9
9
|
model;
|
|
10
10
|
telemetryFunctionId;
|
|
11
|
+
protectedPrefixCount = 0;
|
|
11
12
|
autoTrimRules;
|
|
12
13
|
constructor(messages = [], model, telemetryFunctionId) {
|
|
13
14
|
this.id = this.generateId();
|
|
@@ -16,6 +17,9 @@ export class Conversation {
|
|
|
16
17
|
this.telemetryFunctionId = telemetryFunctionId;
|
|
17
18
|
this.autoTrimRules = new Map();
|
|
18
19
|
}
|
|
20
|
+
protectPrefix(count) {
|
|
21
|
+
this.protectedPrefixCount = count;
|
|
22
|
+
}
|
|
19
23
|
addUserText(text) {
|
|
20
24
|
this.messages.push({
|
|
21
25
|
role: 'user',
|
|
@@ -63,8 +67,10 @@ export class Conversation {
|
|
|
63
67
|
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
64
68
|
const regex = new RegExp(`<${escapedTag}>[\\s\\S]*?<\\/${escapedTag}>`, 'g');
|
|
65
69
|
const replacementText = `<${tagName}>${replacement}</${tagName}>`;
|
|
70
|
+
const start = this.protectedPrefixCount;
|
|
66
71
|
if (keepLast === 0) {
|
|
67
|
-
for (
|
|
72
|
+
for (let i = start; i < this.messages.length; i++) {
|
|
73
|
+
const message = this.messages[i];
|
|
68
74
|
if (typeof message.content === 'string') {
|
|
69
75
|
message.content = message.content.replace(regex, replacementText);
|
|
70
76
|
}
|
|
@@ -72,7 +78,7 @@ export class Conversation {
|
|
|
72
78
|
return;
|
|
73
79
|
}
|
|
74
80
|
const allMatches = [];
|
|
75
|
-
for (let i =
|
|
81
|
+
for (let i = start; i < this.messages.length; i++) {
|
|
76
82
|
const message = this.messages[i];
|
|
77
83
|
if (typeof message.content === 'string') {
|
|
78
84
|
const matches = [...message.content.matchAll(new RegExp(`<${escapedTag}>[\\s\\S]*?<\\/${escapedTag}>`, 'g'))];
|
|
@@ -86,7 +92,7 @@ export class Conversation {
|
|
|
86
92
|
const keepCount = Math.min(keepLast, allMatches.length);
|
|
87
93
|
const keepMatches = allMatches.slice(-keepCount);
|
|
88
94
|
const keepSet = new Set(keepMatches.map((m) => `${m.messageIndex}:${m.startIndex}`));
|
|
89
|
-
for (let i =
|
|
95
|
+
for (let i = start; i < this.messages.length; i++) {
|
|
90
96
|
const message = this.messages[i];
|
|
91
97
|
if (typeof message.content === 'string') {
|
|
92
98
|
const matches = [...message.content.matchAll(new RegExp(`<${escapedTag}>[\\s\\S]*?<\\/${escapedTag}>`, 'g'))];
|
|
@@ -109,7 +115,7 @@ export class Conversation {
|
|
|
109
115
|
}
|
|
110
116
|
compactToolResults(keepLastN) {
|
|
111
117
|
const toolMessageIndexes = [];
|
|
112
|
-
for (let i =
|
|
118
|
+
for (let i = this.protectedPrefixCount; i < this.messages.length; i++) {
|
|
113
119
|
if (this.messages[i].role === 'tool')
|
|
114
120
|
toolMessageIndexes.push(i);
|
|
115
121
|
}
|
|
@@ -144,6 +150,16 @@ export class Conversation {
|
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
152
|
}
|
|
153
|
+
markLastMessageCacheable() {
|
|
154
|
+
const last = this.messages[this.messages.length - 1];
|
|
155
|
+
if (!last)
|
|
156
|
+
return;
|
|
157
|
+
last.providerOptions = {
|
|
158
|
+
...last.providerOptions,
|
|
159
|
+
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
160
|
+
bedrock: { cachePoint: { type: 'default' } },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
147
163
|
hasTag(tagName, lastN) {
|
|
148
164
|
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
149
165
|
const regex = new RegExp(`<${escapedTag}>`, 'g');
|