explorbot 0.2.0 → 0.2.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.
Files changed (141) hide show
  1. package/README.md +21 -5
  2. package/bin/explorbot-cli.ts +29 -7
  3. package/boat/api-tester/src/config.ts +45 -3
  4. package/boat/doc-collector/src/ai/documentarian.ts +5 -2
  5. package/boat/doc-collector/src/ai/tools.ts +12 -11
  6. package/boat/doc-collector/src/cli.ts +1 -6
  7. package/boat/doc-collector/src/docbot.ts +3 -3
  8. package/boat/doc-collector/src/screenshots.ts +2 -2
  9. package/dist/bin/explorbot-cli.js +26 -6
  10. package/dist/boat/api-tester/src/config.js +43 -4
  11. package/dist/boat/doc-collector/src/ai/documentarian.js +4 -2
  12. package/dist/boat/doc-collector/src/ai/tools.js +11 -11
  13. package/dist/boat/doc-collector/src/cli.js +1 -5
  14. package/dist/boat/doc-collector/src/docbot.js +3 -3
  15. package/dist/boat/doc-collector/src/screenshots.js +2 -2
  16. package/dist/models.json +30 -0
  17. package/dist/package.json +5 -2
  18. package/dist/src/action.d.ts +11 -3
  19. package/dist/src/action.js +22 -5
  20. package/dist/src/ai/agent.d.ts +17 -0
  21. package/dist/src/ai/captain/idle-mode.js +1 -1
  22. package/dist/src/ai/captain/test-mode.js +1 -1
  23. package/dist/src/ai/captain/web-mode.js +18 -22
  24. package/dist/src/ai/captain.js +13 -26
  25. package/dist/src/ai/driller.d.ts +2 -13
  26. package/dist/src/ai/driller.js +23 -38
  27. package/dist/src/ai/historian/codeceptjs.js +1 -1
  28. package/dist/src/ai/historian/playwright.d.ts +3 -2
  29. package/dist/src/ai/historian/playwright.js +1 -1
  30. package/dist/src/ai/historian/screencast.d.ts +2 -1
  31. package/dist/src/ai/historian/screencast.js +2 -2
  32. package/dist/src/ai/historian.d.ts +5 -4
  33. package/dist/src/ai/navigator.d.ts +6 -2
  34. package/dist/src/ai/navigator.js +26 -22
  35. package/dist/src/ai/pilot.d.ts +13 -6
  36. package/dist/src/ai/pilot.js +49 -44
  37. package/dist/src/ai/planner.d.ts +3 -2
  38. package/dist/src/ai/planner.js +19 -17
  39. package/dist/src/ai/provider.js +0 -1
  40. package/dist/src/ai/quartermaster.d.ts +3 -2
  41. package/dist/src/ai/quartermaster.js +4 -4
  42. package/dist/src/ai/rerunner.d.ts +2 -11
  43. package/dist/src/ai/rerunner.js +9 -22
  44. package/dist/src/ai/researcher/coordinates.js +4 -3
  45. package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
  46. package/dist/src/ai/researcher/deep-analysis.js +6 -6
  47. package/dist/src/ai/researcher/locators.d.ts +9 -2
  48. package/dist/src/ai/researcher/locators.js +65 -8
  49. package/dist/src/ai/researcher/sections.d.ts +2 -0
  50. package/dist/src/ai/researcher/sections.js +2 -2
  51. package/dist/src/ai/researcher.js +29 -54
  52. package/dist/src/ai/task-agent.d.ts +18 -5
  53. package/dist/src/ai/task-agent.js +35 -6
  54. package/dist/src/ai/tester.d.ts +6 -12
  55. package/dist/src/ai/tester.js +37 -51
  56. package/dist/src/ai/tools.d.ts +11 -10
  57. package/dist/src/ai/tools.js +24 -36
  58. package/dist/src/command-handler.js +1 -1
  59. package/dist/src/commands/context-aria-command.js +1 -1
  60. package/dist/src/commands/context-command.js +7 -5
  61. package/dist/src/commands/context-data-command.js +2 -2
  62. package/dist/src/commands/context-experience-command.js +2 -2
  63. package/dist/src/commands/context-html-command.js +2 -2
  64. package/dist/src/commands/context-knowledge-command.js +1 -1
  65. package/dist/src/commands/drill-command.js +1 -1
  66. package/dist/src/commands/explore-command.js +4 -3
  67. package/dist/src/commands/freesail-command.js +2 -2
  68. package/dist/src/commands/learn-command.js +4 -4
  69. package/dist/src/commands/path-command.js +1 -1
  70. package/dist/src/commands/research-command.js +1 -1
  71. package/dist/src/commands/test-command.js +1 -1
  72. package/dist/src/components/App.js +1 -1
  73. package/dist/src/config.d.ts +17 -0
  74. package/dist/src/config.js +144 -10
  75. package/dist/src/experience-tracker.js +1 -1
  76. package/dist/src/explorbot.d.ts +17 -8
  77. package/dist/src/explorbot.js +74 -36
  78. package/dist/src/explorer.d.ts +66 -91
  79. package/dist/src/explorer.js +326 -567
  80. package/dist/src/state-manager.d.ts +6 -1
  81. package/dist/src/state-manager.js +1 -0
  82. package/dist/src/utils/hooks-runner.js +2 -4
  83. package/dist/src/utils/test-files.js +1 -1
  84. package/dist/src/utils/web-annotate.d.ts +5 -0
  85. package/dist/src/utils/web-annotate.js +58 -0
  86. package/dist/src/utils/web-eidx.d.ts +2 -0
  87. package/dist/src/utils/web-eidx.js +20 -0
  88. package/dist/src/utils/web-element.d.ts +1 -0
  89. package/dist/src/utils/web-element.js +29 -0
  90. package/models.json +30 -0
  91. package/package.json +5 -2
  92. package/src/action.ts +25 -5
  93. package/src/ai/agent.ts +20 -0
  94. package/src/ai/captain/idle-mode.ts +1 -1
  95. package/src/ai/captain/test-mode.ts +1 -1
  96. package/src/ai/captain/web-mode.ts +18 -23
  97. package/src/ai/captain.ts +12 -25
  98. package/src/ai/driller.ts +50 -67
  99. package/src/ai/historian/codeceptjs.ts +1 -1
  100. package/src/ai/historian/playwright.ts +4 -3
  101. package/src/ai/historian/screencast.ts +4 -3
  102. package/src/ai/historian.ts +3 -2
  103. package/src/ai/navigator.ts +30 -25
  104. package/src/ai/pilot.ts +55 -47
  105. package/src/ai/planner.ts +22 -20
  106. package/src/ai/provider.ts +0 -1
  107. package/src/ai/quartermaster.ts +5 -4
  108. package/src/ai/rerunner.ts +10 -29
  109. package/src/ai/researcher/coordinates.ts +4 -3
  110. package/src/ai/researcher/deep-analysis.ts +8 -6
  111. package/src/ai/researcher/locators.ts +66 -9
  112. package/src/ai/researcher/sections.ts +4 -2
  113. package/src/ai/researcher.ts +32 -61
  114. package/src/ai/task-agent.ts +45 -11
  115. package/src/ai/tester.ts +40 -57
  116. package/src/ai/tools.ts +32 -53
  117. package/src/command-handler.ts +1 -1
  118. package/src/commands/context-aria-command.ts +1 -1
  119. package/src/commands/context-command.ts +7 -5
  120. package/src/commands/context-data-command.ts +2 -2
  121. package/src/commands/context-experience-command.ts +2 -2
  122. package/src/commands/context-html-command.ts +2 -2
  123. package/src/commands/context-knowledge-command.ts +1 -1
  124. package/src/commands/drill-command.ts +1 -1
  125. package/src/commands/explore-command.ts +4 -3
  126. package/src/commands/freesail-command.ts +2 -2
  127. package/src/commands/learn-command.ts +4 -4
  128. package/src/commands/path-command.ts +1 -1
  129. package/src/commands/research-command.ts +1 -1
  130. package/src/commands/test-command.ts +1 -1
  131. package/src/components/App.tsx +1 -1
  132. package/src/config.ts +173 -11
  133. package/src/experience-tracker.ts +1 -1
  134. package/src/explorbot.ts +78 -39
  135. package/src/explorer.ts +389 -628
  136. package/src/state-manager.ts +7 -1
  137. package/src/utils/hooks-runner.ts +2 -4
  138. package/src/utils/test-files.ts +1 -1
  139. package/src/utils/web-annotate.ts +64 -0
  140. package/src/utils/web-eidx.ts +21 -0
  141. package/src/utils/web-element.ts +30 -0
@@ -1,6 +1,6 @@
1
1
  import { ActionResult } from './action-result.js';
2
2
  import type { ExperienceTracker } from './experience-tracker.js';
3
- import type { KnowledgeTracker, Knowledge } from './knowledge-tracker.js';
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
- const page = this.explorer.playwrightHelper.page;
58
- await hook.hook({ page, url });
57
+ await this.explorer.withPage(async (page) => hook.hook({ page, url }));
59
58
  } else {
60
- const I = this.explorer.actor;
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}`);
@@ -76,7 +76,7 @@ export async function dryRunTestFile(filePath: string): Promise<void> {
76
76
  },
77
77
  };
78
78
 
79
- (global as any).output_dir = path.join(projectRoot, 'output', 'states');
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
+ }
@@ -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 };