explorbot 0.1.10 → 0.1.12
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 +37 -1
- package/bin/explorbot-cli.ts +27 -18
- package/dist/bin/explorbot-cli.js +26 -18
- package/dist/package.json +3 -3
- package/dist/rules/navigator/output.md +9 -0
- package/dist/rules/navigator/verification-actions.md +2 -0
- package/dist/src/action-result.js +23 -1
- package/dist/src/action.js +51 -42
- package/dist/src/ai/bosun.js +11 -1
- package/dist/src/ai/conversation.js +39 -0
- package/dist/src/ai/historian/codeceptjs.js +109 -0
- package/dist/src/ai/historian/experience.js +321 -0
- package/dist/src/ai/historian/mixin.js +2 -0
- package/dist/src/ai/historian/playwright.js +145 -0
- package/dist/src/ai/historian/screencast.js +121 -0
- package/dist/src/ai/historian/utils.js +18 -0
- package/dist/src/ai/historian.js +21 -405
- package/dist/src/ai/navigator.js +82 -29
- package/dist/src/ai/pilot.js +232 -13
- package/dist/src/ai/planner.js +29 -9
- package/dist/src/ai/provider.js +54 -17
- package/dist/src/ai/researcher.js +41 -32
- package/dist/src/ai/rules.js +26 -14
- package/dist/src/ai/tester.js +90 -26
- package/dist/src/ai/tools.js +13 -7
- package/dist/src/browser-server.js +16 -3
- package/dist/src/commands/add-rule-command.js +11 -8
- package/dist/src/commands/clean-command.js +2 -1
- package/dist/src/commands/explore-command.js +43 -15
- package/dist/src/commands/init-command.js +9 -8
- package/dist/src/commands/plan-command.js +32 -0
- package/dist/src/commands/plan-save-command.js +19 -7
- package/dist/src/commands/rerun-command.js +4 -0
- package/dist/src/components/App.js +15 -5
- package/dist/src/execution-controller.js +13 -2
- package/dist/src/experience-tracker.js +20 -64
- package/dist/src/explorbot.js +8 -8
- package/dist/src/explorer.js +11 -3
- package/dist/src/observability.js +50 -99
- package/dist/src/playwright-recorder.js +309 -0
- package/dist/src/reporter.js +4 -1
- package/dist/src/test-plan.js +12 -0
- package/dist/src/utils/aria.js +37 -1
- package/dist/src/utils/error-page.js +20 -7
- package/dist/src/utils/next-steps.js +37 -0
- package/dist/src/utils/strings.js +15 -0
- package/package.json +3 -3
- package/rules/navigator/output.md +9 -0
- package/rules/navigator/verification-actions.md +2 -0
- package/src/action-result.ts +26 -1
- package/src/action.ts +49 -41
- package/src/ai/bosun.ts +11 -1
- package/src/ai/conversation.ts +37 -0
- package/src/ai/historian/codeceptjs.ts +130 -0
- package/src/ai/historian/experience.ts +384 -0
- package/src/ai/historian/mixin.ts +4 -0
- package/src/ai/historian/playwright.ts +169 -0
- package/src/ai/historian/screencast.ts +133 -0
- package/src/ai/historian/utils.ts +23 -0
- package/src/ai/historian.ts +37 -473
- package/src/ai/navigator.ts +82 -29
- package/src/ai/pilot.ts +237 -14
- package/src/ai/planner.ts +29 -9
- package/src/ai/provider.ts +51 -17
- package/src/ai/researcher.ts +45 -33
- package/src/ai/rules.ts +27 -14
- package/src/ai/tester.ts +94 -26
- package/src/ai/tools.ts +47 -25
- package/src/browser-server.ts +17 -3
- package/src/commands/add-rule-command.ts +11 -7
- package/src/commands/clean-command.ts +2 -1
- package/src/commands/explore-command.ts +46 -14
- package/src/commands/init-command.ts +9 -8
- package/src/commands/plan-command.ts +35 -0
- package/src/commands/plan-save-command.ts +18 -7
- package/src/commands/rerun-command.ts +5 -0
- package/src/components/App.tsx +16 -5
- package/src/config.ts +12 -1
- package/src/execution-controller.ts +14 -3
- package/src/experience-tracker.ts +21 -72
- package/src/explorbot.ts +8 -8
- package/src/explorer.ts +13 -3
- package/src/observability.ts +50 -109
- package/src/playwright-recorder.ts +305 -0
- package/src/reporter.ts +4 -1
- package/src/test-plan.ts +12 -0
- package/src/utils/aria.ts +38 -1
- package/src/utils/error-page.ts +22 -7
- package/src/utils/next-steps.ts +51 -0
- package/src/utils/strings.ts +17 -0
package/src/utils/aria.ts
CHANGED
|
@@ -644,9 +644,18 @@ const resolveDisplayName = (node: AriaNode): string | undefined => {
|
|
|
644
644
|
return undefined;
|
|
645
645
|
};
|
|
646
646
|
|
|
647
|
+
const SIBLING_COLLAPSE_THRESHOLD = 50;
|
|
648
|
+
const SIBLING_COLLAPSE_KEEP_EACH_SIDE = 5;
|
|
649
|
+
|
|
647
650
|
const serializeAriaNodes = (nodes: AriaNode[], depth = 0): string => {
|
|
648
651
|
const lines: string[] = [];
|
|
649
|
-
|
|
652
|
+
const collapsed = collapseSimilarSiblingRuns(nodes, depth);
|
|
653
|
+
for (const entry of collapsed) {
|
|
654
|
+
if (entry.placeholder) {
|
|
655
|
+
lines.push(entry.placeholder);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const node = entry.node!;
|
|
650
659
|
const indent = ' '.repeat(depth);
|
|
651
660
|
let line = `${indent}- ${renderNodeLine(node.role, resolveDisplayName(node), node.attributes, node.value)}`;
|
|
652
661
|
if (node.children.length > 0) {
|
|
@@ -660,6 +669,34 @@ const serializeAriaNodes = (nodes: AriaNode[], depth = 0): string => {
|
|
|
660
669
|
return lines.join('\n');
|
|
661
670
|
};
|
|
662
671
|
|
|
672
|
+
type SerializeEntry = { node?: AriaNode; placeholder?: string };
|
|
673
|
+
|
|
674
|
+
const collapseSimilarSiblingRuns = (nodes: AriaNode[], depth: number): SerializeEntry[] => {
|
|
675
|
+
const result: SerializeEntry[] = [];
|
|
676
|
+
let i = 0;
|
|
677
|
+
while (i < nodes.length) {
|
|
678
|
+
const role = nodes[i].role;
|
|
679
|
+
let j = i;
|
|
680
|
+
while (j < nodes.length && nodes[j].role === role) j++;
|
|
681
|
+
const runLength = j - i;
|
|
682
|
+
if (runLength > SIBLING_COLLAPSE_THRESHOLD) {
|
|
683
|
+
for (let k = i; k < i + SIBLING_COLLAPSE_KEEP_EACH_SIDE; k++) {
|
|
684
|
+
result.push({ node: nodes[k] });
|
|
685
|
+
}
|
|
686
|
+
const omitted = runLength - SIBLING_COLLAPSE_KEEP_EACH_SIDE * 2;
|
|
687
|
+
const indent = ' '.repeat(depth);
|
|
688
|
+
result.push({ placeholder: `${indent}- ...${omitted} similar "${role}" items omitted...` });
|
|
689
|
+
for (let k = j - SIBLING_COLLAPSE_KEEP_EACH_SIDE; k < j; k++) {
|
|
690
|
+
result.push({ node: nodes[k] });
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
for (let k = i; k < j; k++) result.push({ node: nodes[k] });
|
|
694
|
+
}
|
|
695
|
+
i = j;
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
698
|
+
};
|
|
699
|
+
|
|
663
700
|
export const compactAriaSnapshot = (snapshot: string | null, keepNamed = false): string => {
|
|
664
701
|
if (!snapshot) return '';
|
|
665
702
|
const nodes = parseAriaSnapshot(snapshot, keepNamed);
|
package/src/utils/error-page.ts
CHANGED
|
@@ -4,22 +4,37 @@ import { isBodyEmpty } from './html.js';
|
|
|
4
4
|
const HTTP_ERRORS = ['400 Bad Request', '401 Unauthorized', '403 Forbidden', '404 Not Found', '405 Method Not Allowed', '408 Request Timeout', '500 Internal Server Error', '502 Bad Gateway', '503 Service Unavailable', '504 Gateway Timeout'];
|
|
5
5
|
|
|
6
6
|
const SMALL_PAGE_THRESHOLD = 500;
|
|
7
|
+
const LOADING_WORD = /\bloading\b/i;
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
-
|
|
9
|
+
export type PageCondition = 'ok' | 'loading' | 'error';
|
|
10
|
+
|
|
11
|
+
export function detectPageCondition(actionResult: ActionResult): PageCondition {
|
|
12
|
+
const headingFields = [actionResult.title, actionResult.h1, actionResult.h2].filter(Boolean) as string[];
|
|
10
13
|
|
|
11
|
-
for (const field of
|
|
14
|
+
for (const field of headingFields) {
|
|
12
15
|
for (const error of HTTP_ERRORS) {
|
|
13
|
-
if (field.toLowerCase().includes(error.toLowerCase())) return
|
|
16
|
+
if (field.toLowerCase().includes(error.toLowerCase())) return 'error';
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
const aria = actionResult.ariaSnapshot || '';
|
|
21
|
+
if (/\bprogressbar\b/i.test(aria)) return 'loading';
|
|
22
|
+
if (/\[busy\]/.test(aria)) return 'loading';
|
|
23
|
+
|
|
24
|
+
for (const field of headingFields) {
|
|
25
|
+
if (LOADING_WORD.test(field)) return 'loading';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!actionResult.html || isBodyEmpty(actionResult.html)) return 'loading';
|
|
18
29
|
|
|
19
30
|
const bodyMatch = actionResult.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
20
|
-
if (bodyMatch && bodyMatch[1].trim().length < SMALL_PAGE_THRESHOLD) return
|
|
31
|
+
if (bodyMatch && bodyMatch[1].trim().length < SMALL_PAGE_THRESHOLD) return 'loading';
|
|
32
|
+
|
|
33
|
+
return 'ok';
|
|
34
|
+
}
|
|
21
35
|
|
|
22
|
-
|
|
36
|
+
export function isErrorPage(actionResult: ActionResult): boolean {
|
|
37
|
+
return detectPageCondition(actionResult) === 'error';
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
export class ErrorPageError extends Error {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { tag } from './logger.js';
|
|
3
|
+
|
|
4
|
+
export interface NextStepCommand {
|
|
5
|
+
label: string;
|
|
6
|
+
command: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface NextStepSection {
|
|
10
|
+
label: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
commands?: NextStepCommand[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function relativeToCwd(absPath: string): string {
|
|
16
|
+
const rel = path.relative(process.cwd(), absPath);
|
|
17
|
+
return rel || '.';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function printNextSteps(sections: NextStepSection[]): void {
|
|
21
|
+
if (sections.length === 0) return;
|
|
22
|
+
|
|
23
|
+
const blocks: string[] = [];
|
|
24
|
+
for (const section of sections) {
|
|
25
|
+
const lines: string[] = [];
|
|
26
|
+
const headerPath = section.path ? relativeToCwd(section.path) : '';
|
|
27
|
+
lines.push(headerPath ? `${section.label}: ${headerPath}` : section.label);
|
|
28
|
+
|
|
29
|
+
const commands = section.commands || [];
|
|
30
|
+
if (commands.length > 0) {
|
|
31
|
+
const labeled = commands.filter((c) => c.label);
|
|
32
|
+
const maxLabel = labeled.length > 0 ? Math.max(...labeled.map((c) => c.label.length)) : 0;
|
|
33
|
+
for (const cmd of commands) {
|
|
34
|
+
if (!cmd.label) {
|
|
35
|
+
lines.push(` ${cmd.command}`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const padded = `${cmd.label}:`.padEnd(maxLabel + 2);
|
|
39
|
+
lines.push(` ${padded} ${cmd.command}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
blocks.push(lines.join('\n'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
46
|
+
if (i > 0) tag('info').log('');
|
|
47
|
+
for (const line of blocks[i].split('\n')) {
|
|
48
|
+
tag('info').log(line);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/utils/strings.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
1
3
|
export function truncateJson(input: any): string {
|
|
2
4
|
if (!input) return '';
|
|
3
5
|
const str = JSON.stringify(input);
|
|
@@ -11,3 +13,18 @@ export function sanitizeFilename(name: string): string {
|
|
|
11
13
|
.replace(/^_+|_+$/g, '')
|
|
12
14
|
.slice(0, 50);
|
|
13
15
|
}
|
|
16
|
+
|
|
17
|
+
export function safeFilename(name: string, ext = '', maxBytes = 240): string {
|
|
18
|
+
const sanitized = name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
|
|
19
|
+
const extBytes = Buffer.byteLength(ext, 'utf8');
|
|
20
|
+
const budget = maxBytes - extBytes;
|
|
21
|
+
if (Buffer.byteLength(sanitized, 'utf8') <= budget) return sanitized + ext;
|
|
22
|
+
|
|
23
|
+
const hash = createHash('sha1').update(name).digest('hex').slice(0, 8);
|
|
24
|
+
const suffix = `_${hash}`;
|
|
25
|
+
let truncated = sanitized;
|
|
26
|
+
while (Buffer.byteLength(truncated + suffix, 'utf8') > budget && truncated.length > 0) {
|
|
27
|
+
truncated = truncated.slice(0, -1);
|
|
28
|
+
}
|
|
29
|
+
return truncated + suffix + ext;
|
|
30
|
+
}
|