explorbot 0.2.0 → 0.2.2
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 +21 -5
- package/bin/explorbot-cli.ts +29 -7
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +8 -2
- package/boat/doc-collector/src/ai/tools.ts +29 -16
- package/boat/doc-collector/src/cli.ts +2 -6
- package/boat/doc-collector/src/config.ts +2 -0
- package/boat/doc-collector/src/docbot.ts +42 -14
- package/boat/doc-collector/src/interaction-screenshots.ts +160 -0
- package/boat/doc-collector/src/screenshots.ts +24 -16
- package/dist/bin/explorbot-cli.js +26 -6
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +7 -2
- package/dist/boat/doc-collector/src/ai/tools.js +21 -15
- package/dist/boat/doc-collector/src/cli.js +2 -5
- package/dist/boat/doc-collector/src/config.js +1 -0
- package/dist/boat/doc-collector/src/docbot.js +39 -13
- package/dist/boat/doc-collector/src/interaction-screenshots.js +156 -0
- package/dist/boat/doc-collector/src/screenshots.js +25 -16
- package/dist/models.json +30 -0
- package/dist/package.json +8 -2
- package/dist/src/action.d.ts +11 -3
- package/dist/src/action.js +22 -5
- package/dist/src/ai/agent.d.ts +17 -0
- package/dist/src/ai/captain/idle-mode.js +1 -1
- package/dist/src/ai/captain/test-mode.js +1 -1
- package/dist/src/ai/captain/web-mode.js +18 -22
- package/dist/src/ai/captain.js +13 -26
- package/dist/src/ai/driller.d.ts +2 -13
- package/dist/src/ai/driller.js +23 -38
- package/dist/src/ai/historian/codeceptjs.js +1 -1
- package/dist/src/ai/historian/playwright.d.ts +3 -2
- package/dist/src/ai/historian/playwright.js +1 -1
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -2
- package/dist/src/ai/historian.d.ts +5 -4
- package/dist/src/ai/navigator.d.ts +6 -2
- package/dist/src/ai/navigator.js +26 -22
- package/dist/src/ai/pilot.d.ts +13 -6
- package/dist/src/ai/pilot.js +49 -44
- package/dist/src/ai/planner.d.ts +3 -2
- package/dist/src/ai/planner.js +19 -17
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.d.ts +3 -2
- package/dist/src/ai/quartermaster.js +4 -4
- package/dist/src/ai/rerunner.d.ts +2 -11
- package/dist/src/ai/rerunner.js +9 -22
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
- package/dist/src/ai/researcher/deep-analysis.js +6 -6
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- package/dist/src/ai/researcher/sections.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +2 -2
- package/dist/src/ai/researcher.js +29 -54
- package/dist/src/ai/task-agent.d.ts +18 -5
- package/dist/src/ai/task-agent.js +35 -6
- package/dist/src/ai/tester.d.ts +6 -12
- package/dist/src/ai/tester.js +37 -51
- package/dist/src/ai/tools.d.ts +11 -10
- package/dist/src/ai/tools.js +24 -36
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +7 -5
- package/dist/src/commands/context-data-command.js +2 -2
- package/dist/src/commands/context-experience-command.js +2 -2
- package/dist/src/commands/context-html-command.js +2 -2
- package/dist/src/commands/context-knowledge-command.js +1 -1
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/explore-command.js +4 -3
- package/dist/src/commands/freesail-command.js +2 -2
- package/dist/src/commands/learn-command.js +4 -4
- package/dist/src/commands/path-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/test-command.js +1 -1
- package/dist/src/components/App.js +1 -1
- package/dist/src/config.d.ts +17 -0
- package/dist/src/config.js +144 -10
- package/dist/src/experience-tracker.js +1 -1
- package/dist/src/explorbot.d.ts +17 -8
- package/dist/src/explorbot.js +74 -36
- package/dist/src/explorer.d.ts +67 -91
- package/dist/src/explorer.js +327 -567
- package/dist/src/state-manager.d.ts +6 -1
- package/dist/src/state-manager.js +1 -0
- package/dist/src/utils/hooks-runner.js +2 -4
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/web-annotate.d.ts +5 -0
- package/dist/src/utils/web-annotate.js +58 -0
- package/dist/src/utils/web-eidx.d.ts +2 -0
- package/dist/src/utils/web-eidx.js +20 -0
- package/dist/src/utils/web-element.d.ts +1 -0
- package/dist/src/utils/web-element.js +29 -0
- package/models.json +30 -0
- package/package.json +8 -2
- package/src/action.ts +25 -5
- package/src/ai/agent.ts +20 -0
- package/src/ai/captain/idle-mode.ts +1 -1
- package/src/ai/captain/test-mode.ts +1 -1
- package/src/ai/captain/web-mode.ts +18 -23
- package/src/ai/captain.ts +12 -25
- package/src/ai/driller.ts +50 -67
- package/src/ai/historian/codeceptjs.ts +1 -1
- package/src/ai/historian/playwright.ts +4 -3
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian.ts +3 -2
- package/src/ai/navigator.ts +30 -25
- package/src/ai/pilot.ts +55 -47
- package/src/ai/planner.ts +22 -20
- package/src/ai/provider.ts +0 -1
- package/src/ai/quartermaster.ts +5 -4
- package/src/ai/rerunner.ts +10 -29
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +8 -6
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/sections.ts +4 -2
- package/src/ai/researcher.ts +32 -61
- package/src/ai/task-agent.ts +45 -11
- package/src/ai/tester.ts +40 -57
- package/src/ai/tools.ts +32 -53
- package/src/command-handler.ts +1 -1
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +7 -5
- package/src/commands/context-data-command.ts +2 -2
- package/src/commands/context-experience-command.ts +2 -2
- package/src/commands/context-html-command.ts +2 -2
- package/src/commands/context-knowledge-command.ts +1 -1
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/learn-command.ts +4 -4
- package/src/commands/path-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/test-command.ts +1 -1
- package/src/components/App.tsx +1 -1
- package/src/config.ts +173 -11
- package/src/experience-tracker.ts +1 -1
- package/src/explorbot.ts +78 -39
- package/src/explorer.ts +388 -626
- package/src/state-manager.ts +7 -1
- package/src/utils/hooks-runner.ts +2 -4
- package/src/utils/test-files.ts +1 -1
- package/src/utils/web-annotate.ts +64 -0
- package/src/utils/web-eidx.ts +21 -0
- package/src/utils/web-element.ts +30 -0
package/src/state-manager.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ActionResult } from './action-result.js';
|
|
2
2
|
import type { ExperienceTracker } from './experience-tracker.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Knowledge, KnowledgeTracker } from './knowledge-tracker.js';
|
|
4
4
|
import { detectFocusArea } from './utils/aria.js';
|
|
5
5
|
import { createDebug } from './utils/logger.js';
|
|
6
6
|
import { slugify } from './utils/strings.js';
|
|
@@ -67,9 +67,15 @@ export interface StateTransition {
|
|
|
67
67
|
|
|
68
68
|
export type StateChangeListener = (event: StateTransition) => void;
|
|
69
69
|
|
|
70
|
+
export interface TabInfo {
|
|
71
|
+
url: string;
|
|
72
|
+
title: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
export type { Knowledge };
|
|
71
76
|
|
|
72
77
|
export class StateManager {
|
|
78
|
+
otherTabs: TabInfo[] = [];
|
|
73
79
|
private currentState: WebPageState | null = null;
|
|
74
80
|
private stateHistory: StateTransition[] = [];
|
|
75
81
|
private allVisitedUrls: Set<string> = new Set();
|
|
@@ -54,11 +54,9 @@ export class HooksRunner {
|
|
|
54
54
|
private async executeHook(hook: Hook, url: string): Promise<void> {
|
|
55
55
|
try {
|
|
56
56
|
if (hook.type === 'playwright') {
|
|
57
|
-
|
|
58
|
-
await hook.hook({ page, url });
|
|
57
|
+
await this.explorer.withPage(async (page) => hook.hook({ page, url }));
|
|
59
58
|
} else {
|
|
60
|
-
|
|
61
|
-
await hook.hook({ I, url });
|
|
59
|
+
await hook.hook({ I: this.explorer.actor, url });
|
|
62
60
|
}
|
|
63
61
|
} catch (error) {
|
|
64
62
|
debugLog(`Hook error: ${error}`);
|
package/src/utils/test-files.ts
CHANGED
|
@@ -76,7 +76,7 @@ export async function dryRunTestFile(filePath: string): Promise<void> {
|
|
|
76
76
|
},
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
(global as any).output_dir =
|
|
79
|
+
(global as any).output_dir = ConfigParser.getInstance().getStatesDir();
|
|
80
80
|
(global as any).codecept_dir = projectRoot;
|
|
81
81
|
|
|
82
82
|
codeceptjs.container.create(codeceptConfig, {});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ELEMENT_EXTRACTION_CONFIG, getElementDataExtractorSource } from './html.ts';
|
|
2
|
+
import { createDebug } from './logger.js';
|
|
3
|
+
import { WebElement } from './web-element.ts';
|
|
4
|
+
|
|
5
|
+
const debugLog = createDebug('explorbot:web-annotate');
|
|
6
|
+
|
|
7
|
+
const REF_LINE_PATTERN = /^(\s*)-\s+(\w+)\s*(?:"([^"]*)")?.*?\[ref=(e\d+)\]/;
|
|
8
|
+
|
|
9
|
+
const ANNOTATABLE_ROLES = new Set(['button', 'link', 'textbox', 'searchbox', 'checkbox', 'radio', 'switch', 'combobox', 'tab', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'slider', 'spinbutton', 'treeitem']);
|
|
10
|
+
|
|
11
|
+
function parseAriaRefs(ariaSnapshot: string): Array<{ role: string; name: string; ref: string }> {
|
|
12
|
+
const entries: Array<{ role: string; name: string; ref: string }> = [];
|
|
13
|
+
for (const line of ariaSnapshot.split('\n')) {
|
|
14
|
+
const match = line.match(REF_LINE_PATTERN);
|
|
15
|
+
if (!match) continue;
|
|
16
|
+
if (!ANNOTATABLE_ROLES.has(match[2])) continue;
|
|
17
|
+
entries.push({ role: match[2], name: match[3] || '', ref: match[4] });
|
|
18
|
+
}
|
|
19
|
+
return entries;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function annotatePageElements(page: any): Promise<{ ariaSnapshot: string; elements: WebElement[] }> {
|
|
23
|
+
const ariaSnapshot: string = await page.locator('body').ariaSnapshot({ mode: 'ai' });
|
|
24
|
+
const refEntries = parseAriaRefs(ariaSnapshot);
|
|
25
|
+
|
|
26
|
+
const byRole = new Map<string, Array<{ name: string; ref: string }>>();
|
|
27
|
+
for (const { role, name, ref } of refEntries) {
|
|
28
|
+
let list = byRole.get(role);
|
|
29
|
+
if (!list) {
|
|
30
|
+
list = [];
|
|
31
|
+
byRole.set(role, list);
|
|
32
|
+
}
|
|
33
|
+
list.push({ name, ref });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const elements: WebElement[] = [];
|
|
37
|
+
for (const [role, entries] of byRole) {
|
|
38
|
+
try {
|
|
39
|
+
const rawList = await page.getByRole(role).evaluateAll(
|
|
40
|
+
(domElements: Element[], [data, extractFnStr, config]: [Array<{ name: string; ref: string }>, string, typeof ELEMENT_EXTRACTION_CONFIG]) => {
|
|
41
|
+
const extract = new Function(`return ${extractFnStr}`)() as (el: Element) => any;
|
|
42
|
+
const results: any[] = [];
|
|
43
|
+
let ariaIdx = 0;
|
|
44
|
+
for (const el of domElements) {
|
|
45
|
+
if (ariaIdx >= data.length) break;
|
|
46
|
+
el.setAttribute(config.attrs.eidx, data[ariaIdx].ref);
|
|
47
|
+
const elData = extract(el, config);
|
|
48
|
+
if (elData) results.push(elData);
|
|
49
|
+
ariaIdx++;
|
|
50
|
+
}
|
|
51
|
+
return results;
|
|
52
|
+
},
|
|
53
|
+
[entries, getElementDataExtractorSource(), ELEMENT_EXTRACTION_CONFIG]
|
|
54
|
+
);
|
|
55
|
+
for (const raw of rawList) {
|
|
56
|
+
elements.push(WebElement.fromRawData(raw, role));
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
debugLog(`Failed to annotate role=${role}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { ariaSnapshot, elements };
|
|
64
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ELEMENT_EXTRACTION_CONFIG } from './html.ts';
|
|
2
|
+
|
|
3
|
+
export async function eidxInContainer(page: any, containerCss: string | null): Promise<string[]> {
|
|
4
|
+
const selector = containerCss ? `${containerCss} [${ELEMENT_EXTRACTION_CONFIG.attrs.eidx}]` : `[${ELEMENT_EXTRACTION_CONFIG.attrs.eidx}]`;
|
|
5
|
+
const elements = await page.locator(selector).all();
|
|
6
|
+
const result: string[] = [];
|
|
7
|
+
for (const el of elements) {
|
|
8
|
+
const attr = await el.getAttribute(ELEMENT_EXTRACTION_CONFIG.attrs.eidx).catch(() => null);
|
|
9
|
+
if (attr) result.push(attr);
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function eidxByLocator(page: any, locator: string, container?: string | null): Promise<string | null> {
|
|
15
|
+
const base = container ? page.locator(container) : page;
|
|
16
|
+
const el = locator.startsWith('//') ? base.locator(`xpath=${locator}`) : base.locator(locator);
|
|
17
|
+
return el
|
|
18
|
+
.first()
|
|
19
|
+
.getAttribute(ELEMENT_EXTRACTION_CONFIG.attrs.eidx)
|
|
20
|
+
.catch(() => null);
|
|
21
|
+
}
|
package/src/utils/web-element.ts
CHANGED
|
@@ -147,6 +147,36 @@ export class WebElement {
|
|
|
147
147
|
return rawList.map((d) => WebElement.fromRawData(d));
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
static async commonAncestor(page: any, eidxList: string[]): Promise<WebElement | null> {
|
|
151
|
+
const validEidxList = eidxList.filter((eidx) => /^e\d+$/i.test(eidx));
|
|
152
|
+
if (validEidxList.length < 2) return null;
|
|
153
|
+
|
|
154
|
+
const raw: RawElementData | null = await page.evaluate(
|
|
155
|
+
([list, extractFnStr, config]: [string[], string, ElementExtractionConfig]) => {
|
|
156
|
+
const extract = new Function(`return ${extractFnStr}`)() as (el: Element, cfg: ElementExtractionConfig) => any;
|
|
157
|
+
const els: Element[] = [];
|
|
158
|
+
for (const eidx of list) {
|
|
159
|
+
const el = document.querySelector(`[${config.attrs.eidx}="${eidx}"]`);
|
|
160
|
+
if (el && !els.includes(el)) els.push(el);
|
|
161
|
+
}
|
|
162
|
+
if (els.length < 2) return null;
|
|
163
|
+
|
|
164
|
+
const containsAll = (node: Element) => els.every((el) => node.contains(el));
|
|
165
|
+
let ancestor: Element | null = els[0].parentElement;
|
|
166
|
+
while (ancestor && !containsAll(ancestor)) ancestor = ancestor.parentElement;
|
|
167
|
+
if (!ancestor) return null;
|
|
168
|
+
|
|
169
|
+
const tag = ancestor.tagName.toLowerCase();
|
|
170
|
+
if (tag === 'body' || tag === 'html') return null;
|
|
171
|
+
return extract(ancestor, config);
|
|
172
|
+
},
|
|
173
|
+
[validEidxList, getElementDataExtractorSource(), ELEMENT_EXTRACTION_CONFIG] as [string[], string, ElementExtractionConfig]
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (!raw) return null;
|
|
177
|
+
return WebElement.fromRawData(raw);
|
|
178
|
+
}
|
|
179
|
+
|
|
150
180
|
static async findByXPath(html: string, xpath: string): Promise<{ totalFound: number; elements: WebElement[]; error?: string }> {
|
|
151
181
|
const result = await evaluateXPath(html, xpath);
|
|
152
182
|
if (result.error) return { totalFound: 0, elements: [], error: result.error };
|