explorbot 0.3.5 → 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 +23 -16
- 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 +6 -1
- package/boat/doc-collector/src/docbot.ts +4 -2
- package/boat/prima/bin/prima-cli.ts +2 -0
- package/boat/prima/src/cli.ts +23 -8
- package/boat/prima/src/envelope.ts +35 -9
- package/boat/prima/src/prima.ts +23 -10
- package/dist/bin/explorbot-cli.js +24 -16
- 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 +6 -1
- package/dist/boat/doc-collector/src/docbot.js +4 -2
- package/dist/boat/prima/bin/prima-cli.js +2 -0
- package/dist/boat/prima/src/cli.js +22 -8
- package/dist/boat/prima/src/envelope.js +24 -6
- package/dist/boat/prima/src/prima.js +23 -11
- package/dist/package.json +2 -2
- package/dist/rules/planner/styles/normal.md +1 -1
- package/dist/src/action-result.d.ts +9 -1
- package/dist/src/action-result.js +57 -18
- package/dist/src/action.d.ts +1 -1
- package/dist/src/action.js +87 -12
- package/dist/src/ai/captain.js +1 -1
- package/dist/src/ai/driller.d.ts +0 -1
- package/dist/src/ai/driller.js +8 -20
- package/dist/src/ai/fisherman-tools.d.ts +9 -0
- package/dist/src/ai/fisherman-tools.js +52 -6
- package/dist/src/ai/fisherman.d.ts +4 -2
- package/dist/src/ai/fisherman.js +48 -27
- package/dist/src/ai/historian/codeceptjs.js +1 -1
- package/dist/src/ai/historian/playwright.js +1 -1
- package/dist/src/ai/navigator.js +1 -1
- package/dist/src/ai/pilot.d.ts +1 -0
- package/dist/src/ai/pilot.js +13 -1
- package/dist/src/ai/planner.js +9 -7
- package/dist/src/ai/provider.js +20 -3
- package/dist/src/ai/researcher/deep-analysis.js +1 -3
- package/dist/src/ai/researcher.js +5 -3
- package/dist/src/ai/rules.js +3 -3
- package/dist/src/ai/tester.d.ts +3 -0
- package/dist/src/ai/tester.js +40 -3
- package/dist/src/ai/tools.d.ts +1 -0
- package/dist/src/ai/tools.js +13 -6
- package/dist/src/api/request-result.d.ts +2 -0
- package/dist/src/api/request-result.js +8 -2
- package/dist/src/api/request-store.d.ts +3 -2
- package/dist/src/api/request-store.js +66 -14
- 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.d.ts +6 -0
- package/dist/src/commands/explore-command.js +39 -3
- package/dist/src/commands/freesail-command.js +10 -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/commands/plans-command.js +6 -6
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.js +12 -0
- package/dist/src/experience-tracker.js +5 -0
- package/dist/src/explorbot.d.ts +0 -1
- package/dist/src/explorbot.js +24 -37
- 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/dist/src/state-manager.d.ts +5 -1
- package/dist/src/state-manager.js +10 -7
- package/dist/src/test-plan.d.ts +3 -0
- package/dist/src/test-plan.js +27 -0
- package/dist/src/utils/aria.d.ts +1 -1
- package/dist/src/utils/aria.js +6 -42
- package/dist/src/utils/html-diff.d.ts +4 -0
- package/dist/src/utils/html-diff.js +62 -7
- package/dist/src/utils/html.d.ts +5 -15
- package/dist/src/utils/html.js +14 -85
- package/dist/src/utils/overlay.d.ts +56 -11
- package/dist/src/utils/overlay.js +191 -21
- package/dist/src/utils/request-map.d.ts +7 -0
- package/dist/src/utils/request-map.js +16 -0
- package/dist/src/utils/url-matcher.js +4 -2
- package/docs/api-testing/basics.md +15 -0
- package/docs/api-testing/planning.md +10 -1
- package/docs/reference/commands.md +32 -5
- package/docs/reference/websocket.md +1 -0
- package/docs/superpowers/plans/2026-08-29-fisherman-reliability.md +953 -0
- package/docs/superpowers/plans/2026-08-29-region-states.md +1292 -0
- package/docs/superpowers/plans/2026-08-30-fisherman-live-session-auth.md +457 -0
- package/docs/superpowers/specs/2026-08-29-fisherman-reliability-design.md +45 -0
- package/docs/superpowers/specs/2026-08-29-region-states-design.md +262 -0
- package/docs/superpowers/specs/2026-08-29-region-states-fixes-design.md +269 -0
- package/docs/superpowers/specs/2026-08-30-fisherman-live-session-auth-design.md +37 -0
- package/docs/workflow/agentic-usage.md +12 -2
- package/docs/workflow/ci.md +1 -0
- package/docs/workflow/knowledge.md +46 -2
- package/package.json +2 -2
- package/rules/planner/styles/normal.md +1 -1
- package/src/action-result.ts +61 -22
- package/src/action.ts +87 -14
- package/src/ai/captain.ts +1 -1
- package/src/ai/driller.ts +7 -39
- package/src/ai/fisherman-tools.ts +56 -7
- package/src/ai/fisherman.ts +48 -28
- package/src/ai/historian/codeceptjs.ts +1 -1
- package/src/ai/historian/playwright.ts +1 -1
- package/src/ai/navigator.ts +1 -1
- package/src/ai/pilot.ts +9 -1
- package/src/ai/planner.ts +9 -8
- package/src/ai/provider.ts +21 -3
- package/src/ai/researcher/deep-analysis.ts +1 -2
- package/src/ai/researcher.ts +4 -3
- package/src/ai/rules.ts +3 -3
- package/src/ai/tester.ts +40 -3
- package/src/ai/tools.ts +17 -9
- package/src/api/request-result.ts +10 -2
- package/src/api/request-store.ts +60 -13
- 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 +37 -3
- package/src/commands/freesail-command.ts +7 -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/commands/plans-command.ts +6 -6
- package/src/config.ts +12 -0
- package/src/experience-tracker.ts +5 -1
- package/src/explorbot.ts +21 -37
- package/src/knowledge-tracker.ts +94 -36
- package/src/remote.ts +0 -20
- package/src/state-manager.ts +13 -7
- package/src/test-plan.ts +29 -0
- package/src/utils/aria.ts +7 -44
- package/src/utils/html-diff.ts +62 -7
- package/src/utils/html.ts +14 -91
- package/src/utils/overlay.ts +226 -23
- package/src/utils/request-map.ts +19 -0
- package/src/utils/url-matcher.ts +3 -2
package/dist/src/utils/aria.js
CHANGED
|
@@ -469,47 +469,17 @@ const formatDiff = (added, removed, toggled, typed = []) => {
|
|
|
469
469
|
sections.push(...formatDiffSection('added', added), ...formatDiffSection('removed', removed));
|
|
470
470
|
return sections.join('\n');
|
|
471
471
|
};
|
|
472
|
-
const CLOSE_OVERLAY_BUTTON_RE = /^close\s+(modal|dialog|popup|drawer|panel|sheet)\b/i;
|
|
473
|
-
const findOverlayByCloseButton = (nodeList) => {
|
|
474
|
-
const closeIdx = nodeList.findIndex((n) => n.role === 'button' && CLOSE_OVERLAY_BUTTON_RE.test(n.name || ''));
|
|
475
|
-
if (closeIdx !== -1) {
|
|
476
|
-
let heading;
|
|
477
|
-
for (let i = closeIdx - 1; i >= 0; i--) {
|
|
478
|
-
if (nodeList[i].role === 'heading' && nodeList[i].name) {
|
|
479
|
-
heading = nodeList[i];
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (!heading) {
|
|
484
|
-
for (let i = closeIdx + 1; i < nodeList.length; i++) {
|
|
485
|
-
if (nodeList[i].role === 'heading' && nodeList[i].name) {
|
|
486
|
-
heading = nodeList[i];
|
|
487
|
-
break;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return { detected: true, type: 'dialog', name: heading?.name || null };
|
|
492
|
-
}
|
|
493
|
-
for (const node of nodeList) {
|
|
494
|
-
const inner = findOverlayByCloseButton(node.children);
|
|
495
|
-
if (inner)
|
|
496
|
-
return inner;
|
|
497
|
-
}
|
|
498
|
-
return null;
|
|
499
|
-
};
|
|
500
472
|
const findDialogOrModal = (nodes) => {
|
|
473
|
+
let topmost = null;
|
|
501
474
|
for (const node of nodes) {
|
|
502
|
-
if (node.role === 'dialog' || node.role === 'alertdialog') {
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
if (node.attributes.modal === true || node.attributes.modal === 'true') {
|
|
506
|
-
return { detected: true, type: 'modal', name: node.name || null };
|
|
475
|
+
if (node.role === 'dialog' || node.role === 'alertdialog' || node.attributes.modal === true || node.attributes.modal === 'true') {
|
|
476
|
+
topmost = { detected: true, type: 'modal', name: node.name || null };
|
|
507
477
|
}
|
|
508
478
|
const child = findDialogOrModal(node.children);
|
|
509
479
|
if (child)
|
|
510
|
-
|
|
480
|
+
topmost = child;
|
|
511
481
|
}
|
|
512
|
-
return
|
|
482
|
+
return topmost;
|
|
513
483
|
};
|
|
514
484
|
export const compactAriaSnapshot = (snapshot, keepNamed = false, offload) => {
|
|
515
485
|
if (!snapshot)
|
|
@@ -547,13 +517,7 @@ export const detectFocusArea = (snapshot) => {
|
|
|
547
517
|
let tree = parseSnapshot(snapshot);
|
|
548
518
|
tree = unwrapIgnored(tree);
|
|
549
519
|
tree = dropEmpty(tree, { keepNamed: true });
|
|
550
|
-
|
|
551
|
-
if (direct)
|
|
552
|
-
return direct;
|
|
553
|
-
const fallback = findOverlayByCloseButton(tree);
|
|
554
|
-
if (fallback?.name)
|
|
555
|
-
return fallback;
|
|
556
|
-
return { detected: false, type: null, name: null };
|
|
520
|
+
return findDialogOrModal(tree) ?? { detected: false, type: null, name: null };
|
|
557
521
|
};
|
|
558
522
|
export const collectInteractiveNodes = (snapshot) => {
|
|
559
523
|
let tree = parseSnapshot(snapshot);
|
|
@@ -2,14 +2,17 @@ import type { HtmlConfig } from '../config.js';
|
|
|
2
2
|
export interface HtmlDiffPart {
|
|
3
3
|
container: string;
|
|
4
4
|
subtree: string;
|
|
5
|
+
rawSize: number;
|
|
5
6
|
added: string[];
|
|
6
7
|
removed: string[];
|
|
8
|
+
appearedSelector?: string;
|
|
7
9
|
}
|
|
8
10
|
export interface HtmlDiffResult {
|
|
9
11
|
parts: HtmlDiffPart[];
|
|
10
12
|
added: string[];
|
|
11
13
|
removed: string[];
|
|
12
14
|
similarity: number;
|
|
15
|
+
pageSize: number;
|
|
13
16
|
summary: string;
|
|
14
17
|
messages: string[];
|
|
15
18
|
}
|
|
@@ -22,3 +25,4 @@ export declare function htmlDiff(originalHtml: string, modifiedHtml: string, htm
|
|
|
22
25
|
* Text the app announced across a navigation. Only live regions: everything else on a new page is its content, not a message.
|
|
23
26
|
*/
|
|
24
27
|
export declare function liveRegionMessages(originalHtml: string, modifiedHtml: string): string[];
|
|
28
|
+
export declare function pathToXPath(treePath: string): string;
|
|
@@ -2,6 +2,8 @@ import { parse, serialize } from 'parse5';
|
|
|
2
2
|
import { TAILWIND_CLASS_PATTERNS, TRASH_HTML_CLASSES, minifyHtml } from "./html.js";
|
|
3
3
|
import { isDynamicId, isGenericClass } from "./xpath.js";
|
|
4
4
|
const IGNORED_PATHS = new Set(['html[1]', 'html[1]/head[1]', 'html[1]/body[1]']);
|
|
5
|
+
const SHELL_RATIO = 0.8;
|
|
6
|
+
const ROOT_CONTENT_RATIO = 0.8;
|
|
5
7
|
const LIVE_REGION_ROLES = new Set(['alert', 'alertdialog', 'status', 'log']);
|
|
6
8
|
const TEXT_LINE_PREFIX = 'TEXT:';
|
|
7
9
|
const MESSAGE_MAX_LENGTH = 200;
|
|
@@ -147,7 +149,11 @@ export async function htmlDiff(originalHtml, modifiedHtml, htmlConfig) {
|
|
|
147
149
|
const { added, removed } = findDifferences(originalLines, modifiedLines);
|
|
148
150
|
const originalMap = collectElementMap(originalDocument);
|
|
149
151
|
const modifiedMap = collectElementMap(modifiedDocument);
|
|
150
|
-
const
|
|
152
|
+
const modifiedBody = findBodyElement(modifiedDocument);
|
|
153
|
+
let pageSize = modifiedHtml.length;
|
|
154
|
+
if (modifiedBody)
|
|
155
|
+
pageSize = serializeNode(modifiedBody).length;
|
|
156
|
+
const parts = await buildDiffParts(originalMap, modifiedMap, pageSize);
|
|
151
157
|
const structuralAdditions = parts.flatMap((p) => p.added.filter((a) => a.startsWith('ELEMENT:')));
|
|
152
158
|
const allAdded = [...added, ...structuralAdditions];
|
|
153
159
|
const totalChanges = allAdded.length + removed.length;
|
|
@@ -157,10 +163,14 @@ export async function htmlDiff(originalHtml, modifiedHtml, htmlConfig) {
|
|
|
157
163
|
added: allAdded,
|
|
158
164
|
removed,
|
|
159
165
|
similarity,
|
|
166
|
+
pageSize,
|
|
160
167
|
summary,
|
|
161
168
|
messages: collectMessages(originalMap, modifiedMap, allAdded),
|
|
162
169
|
};
|
|
163
170
|
}
|
|
171
|
+
function serializeNode(node) {
|
|
172
|
+
return serialize({ childNodes: [node], nodeName: '#document-fragment' });
|
|
173
|
+
}
|
|
164
174
|
/**
|
|
165
175
|
* Text the app announced while the page stayed the same: live region content first, then any other text that appeared.
|
|
166
176
|
*/
|
|
@@ -392,14 +402,14 @@ function buildContainerSelector(element, allElements) {
|
|
|
392
402
|
}
|
|
393
403
|
return matchCount === 1 ? selector : null;
|
|
394
404
|
}
|
|
395
|
-
function pathToXPath(treePath) {
|
|
405
|
+
export function pathToXPath(treePath) {
|
|
396
406
|
const parts = treePath.split('/');
|
|
397
407
|
const bodyIdx = parts.findIndex((p) => p.startsWith('body'));
|
|
398
408
|
if (bodyIdx === -1)
|
|
399
409
|
return `//${parts.join('/')}`;
|
|
400
410
|
return `//body/${parts.slice(bodyIdx + 1).join('/')}`;
|
|
401
411
|
}
|
|
402
|
-
function findStableContainer(topLevelPath, originalMap, modifiedMap) {
|
|
412
|
+
function findStableContainer(topLevelPath, originalMap, modifiedMap, pageSize) {
|
|
403
413
|
const segments = topLevelPath.split('/');
|
|
404
414
|
for (let i = segments.length - 2; i >= 1; i--) {
|
|
405
415
|
const candidatePath = segments.slice(0, i + 1).join('/');
|
|
@@ -408,6 +418,8 @@ function findStableContainer(topLevelPath, originalMap, modifiedMap) {
|
|
|
408
418
|
if (!originalMap.has(candidatePath) || !modifiedMap.has(candidatePath))
|
|
409
419
|
continue;
|
|
410
420
|
const element = modifiedMap.get(candidatePath);
|
|
421
|
+
if (pageSize > 0 && serializeNode(element).length >= SHELL_RATIO * pageSize)
|
|
422
|
+
break;
|
|
411
423
|
const css = buildContainerSelector(element, modifiedMap);
|
|
412
424
|
if (css)
|
|
413
425
|
return { path: candidatePath, selector: css };
|
|
@@ -415,7 +427,32 @@ function findStableContainer(topLevelPath, originalMap, modifiedMap) {
|
|
|
415
427
|
}
|
|
416
428
|
return { path: 'html[1]/body[1]', selector: 'body' };
|
|
417
429
|
}
|
|
418
|
-
|
|
430
|
+
function dominantChild(element) {
|
|
431
|
+
const children = (element.childNodes ?? []).filter((child) => 'tagName' in child && !!child.tagName);
|
|
432
|
+
if (children.length === 0)
|
|
433
|
+
return null;
|
|
434
|
+
if (children.length === 1)
|
|
435
|
+
return children[0];
|
|
436
|
+
const parentSize = serializeNode(element).length;
|
|
437
|
+
if (!parentSize)
|
|
438
|
+
return null;
|
|
439
|
+
for (const child of children) {
|
|
440
|
+
if (serializeNode(child).length >= ROOT_CONTENT_RATIO * parentSize)
|
|
441
|
+
return child;
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
function semanticSelectorFor(element, allElements) {
|
|
446
|
+
let current = element;
|
|
447
|
+
while (current) {
|
|
448
|
+
const selector = buildContainerSelector(current, allElements);
|
|
449
|
+
if (selector)
|
|
450
|
+
return selector;
|
|
451
|
+
current = dominantChild(current);
|
|
452
|
+
}
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
async function buildDiffParts(originalMap, modifiedMap, pageSize) {
|
|
419
456
|
const addedPaths = [];
|
|
420
457
|
const changedPaths = [];
|
|
421
458
|
for (const [path, element] of modifiedMap.entries()) {
|
|
@@ -438,7 +475,7 @@ async function buildDiffParts(originalMap, modifiedMap) {
|
|
|
438
475
|
const allTopLevel = [...addedTopLevel, ...changedTopLevel];
|
|
439
476
|
const grouped = new Map();
|
|
440
477
|
for (const path of allTopLevel) {
|
|
441
|
-
const { path: containerPath, selector } = findStableContainer(path, originalMap, modifiedMap);
|
|
478
|
+
const { path: containerPath, selector } = findStableContainer(path, originalMap, modifiedMap, pageSize);
|
|
442
479
|
const existing = grouped.get(containerPath);
|
|
443
480
|
if (existing) {
|
|
444
481
|
existing.paths.push(path);
|
|
@@ -482,9 +519,27 @@ async function buildDiffParts(originalMap, modifiedMap) {
|
|
|
482
519
|
const subtree = serialized ? await minifyHtml(serialized) : '';
|
|
483
520
|
if (!subtree)
|
|
484
521
|
continue;
|
|
485
|
-
const
|
|
522
|
+
const appearedPaths = paths.filter((p) => addedTopLevel.includes(p));
|
|
523
|
+
const sizeOf = (p) => {
|
|
524
|
+
const node = modifiedMap.get(p);
|
|
525
|
+
if (!node)
|
|
526
|
+
return 0;
|
|
527
|
+
return serializeNode(node).length;
|
|
528
|
+
};
|
|
529
|
+
appearedPaths.sort((a, b) => sizeOf(b) - sizeOf(a));
|
|
530
|
+
const addedLines = appearedPaths.map((p) => `ELEMENT:${p}`);
|
|
486
531
|
const removedLines = [];
|
|
487
|
-
|
|
532
|
+
const part = { container: selector, subtree, rawSize: serialized.length, added: addedLines, removed: removedLines };
|
|
533
|
+
const appearedPath = appearedPaths[0];
|
|
534
|
+
if (appearedPath) {
|
|
535
|
+
const appearedElement = modifiedMap.get(appearedPath);
|
|
536
|
+
if (appearedElement) {
|
|
537
|
+
const appearedSelector = semanticSelectorFor(appearedElement, modifiedMap);
|
|
538
|
+
if (appearedSelector)
|
|
539
|
+
part.appearedSelector = appearedSelector;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
parts.push(part);
|
|
488
543
|
}
|
|
489
544
|
return parts;
|
|
490
545
|
}
|
package/dist/src/utils/html.d.ts
CHANGED
|
@@ -16,16 +16,11 @@ export declare const HTML_SELECTORS: {
|
|
|
16
16
|
readonly semanticContextContainer: "section, article, form, fieldset, li, tr, td, th, [role=\"group\"], [role=\"tabpanel\"], [role=\"region\"], [class*=\"card\"], [class*=\"panel\"], [class*=\"item\"], [class*=\"usage\"], [class*=\"group\"]";
|
|
17
17
|
};
|
|
18
18
|
export declare const HTML_VISIBILITY_LIMITS: {
|
|
19
|
-
readonly maxViewportOverlayRatio: 0.95;
|
|
20
19
|
readonly minOpacity: 0.1;
|
|
21
|
-
readonly minOverlayHeight: 40;
|
|
22
|
-
readonly minOverlayWidth: 80;
|
|
23
20
|
};
|
|
24
21
|
export declare const HTML_EXTRACTION_LIMITS: {
|
|
25
22
|
readonly componentScopeHtmlLength: 8000;
|
|
26
|
-
readonly maxOverlayCount: 3;
|
|
27
23
|
readonly maxScopeInteractiveCount: 16;
|
|
28
|
-
readonly overlayHtmlLength: 6000;
|
|
29
24
|
};
|
|
30
25
|
export declare const CODE_EDITOR_MARKERS: readonly ["monaco", "codemirror", "ace", "ace_editor", "code"];
|
|
31
26
|
export declare const HTML_INTERACTIVE_ROLES: Set<string>;
|
|
@@ -62,14 +57,6 @@ export declare const ELEMENT_EXTRACTION_CONFIG: {
|
|
|
62
57
|
};
|
|
63
58
|
export type ElementExtractionConfig = typeof ELEMENT_EXTRACTION_CONFIG;
|
|
64
59
|
export type RawElementData = NonNullable<ReturnType<typeof extractElementData>>;
|
|
65
|
-
export type VisibleOverlayExtractionConfig = {
|
|
66
|
-
interactiveContentSelector: string;
|
|
67
|
-
limits: typeof HTML_EXTRACTION_LIMITS;
|
|
68
|
-
overlaySelectors: readonly string[];
|
|
69
|
-
overlaySemanticSelector: string;
|
|
70
|
-
visibilityLimits: typeof HTML_VISIBILITY_LIMITS;
|
|
71
|
-
geometryFallback?: boolean;
|
|
72
|
-
};
|
|
73
60
|
export type ComponentScopeExtractionConfig = {
|
|
74
61
|
eidxAttr: string;
|
|
75
62
|
interactiveControlSelector: string;
|
|
@@ -85,9 +72,7 @@ export declare function extractElementData(el: Element, config?: ElementExtracti
|
|
|
85
72
|
y: number;
|
|
86
73
|
};
|
|
87
74
|
export declare function getElementDataExtractorSource(): string;
|
|
88
|
-
export declare function extractVisibleOverlayHtml(config: VisibleOverlayExtractionConfig): string;
|
|
89
75
|
export declare function extractComponentScopeHtml(eidx: string, config: ComponentScopeExtractionConfig): string;
|
|
90
|
-
export declare function getVisibleOverlayHtmlExtractorSource(): string;
|
|
91
76
|
export declare function getComponentScopeHtmlExtractorSource(): string;
|
|
92
77
|
export declare const TRASH_HTML_CLASSES: RegExp;
|
|
93
78
|
export declare const TAILWIND_CLASS_PATTERNS: RegExp[];
|
|
@@ -106,6 +91,11 @@ export declare function minifyHtml(html: string): Promise<string>;
|
|
|
106
91
|
export declare function htmlCombinedSnapshot(html: string, htmlConfig?: HtmlConfig['combined'], opts?: {
|
|
107
92
|
keepPositions?: boolean;
|
|
108
93
|
}): string;
|
|
94
|
+
/**
|
|
95
|
+
* Cleans a small HTML snippet for AI consumption: keeps structure and text,
|
|
96
|
+
* drops noisy attributes and generated class names
|
|
97
|
+
*/
|
|
98
|
+
export declare function cleanHtmlSnippet(html: string): string;
|
|
109
99
|
/**
|
|
110
100
|
* Creates text-only snapshot with markdown formatting
|
|
111
101
|
*/
|
package/dist/src/utils/html.js
CHANGED
|
@@ -81,16 +81,11 @@ export const HTML_SELECTORS = {
|
|
|
81
81
|
semanticContextContainer: 'section, article, form, fieldset, li, tr, td, th, [role="group"], [role="tabpanel"], [role="region"], [class*="card"], [class*="panel"], [class*="item"], [class*="usage"], [class*="group"]',
|
|
82
82
|
};
|
|
83
83
|
export const HTML_VISIBILITY_LIMITS = {
|
|
84
|
-
maxViewportOverlayRatio: 0.95,
|
|
85
84
|
minOpacity: 0.1,
|
|
86
|
-
minOverlayHeight: 40,
|
|
87
|
-
minOverlayWidth: 80,
|
|
88
85
|
};
|
|
89
86
|
export const HTML_EXTRACTION_LIMITS = {
|
|
90
87
|
componentScopeHtmlLength: 8000,
|
|
91
|
-
maxOverlayCount: 3,
|
|
92
88
|
maxScopeInteractiveCount: 16,
|
|
93
|
-
overlayHtmlLength: 6000,
|
|
94
89
|
};
|
|
95
90
|
export const CODE_EDITOR_MARKERS = ['monaco', 'codemirror', 'ace', 'ace_editor', 'code'];
|
|
96
91
|
export const HTML_INTERACTIVE_ROLES = new Set(['button', 'link', 'checkbox', 'radio', 'switch', 'tab', 'combobox', 'iframe', 'code-editor', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'slider', 'spinbutton', 'textbox', 'searchbox', 'treeitem']);
|
|
@@ -411,83 +406,6 @@ export function extractElementData(el, config) {
|
|
|
411
406
|
export function getElementDataExtractorSource() {
|
|
412
407
|
return extractElementData.toString();
|
|
413
408
|
}
|
|
414
|
-
export function extractVisibleOverlayHtml(config) {
|
|
415
|
-
function isVisible(element) {
|
|
416
|
-
const html = element;
|
|
417
|
-
const style = window.getComputedStyle(html);
|
|
418
|
-
const rect = html.getBoundingClientRect();
|
|
419
|
-
if (rect.width === 0 && rect.height === 0)
|
|
420
|
-
return false;
|
|
421
|
-
if (style.display === 'none' || style.visibility === 'hidden')
|
|
422
|
-
return false;
|
|
423
|
-
if (Number.parseFloat(style.opacity || '1') < config.visibilityLimits.minOpacity)
|
|
424
|
-
return false;
|
|
425
|
-
return true;
|
|
426
|
-
}
|
|
427
|
-
function getUsefulContent(element) {
|
|
428
|
-
const text = (element.textContent || '').replace(/\s+/g, ' ').trim();
|
|
429
|
-
const interactiveCount = element.querySelectorAll(config.interactiveContentSelector).length;
|
|
430
|
-
return { interactiveCount, text };
|
|
431
|
-
}
|
|
432
|
-
function isLikelyFloatingOverlay(element) {
|
|
433
|
-
const html = element;
|
|
434
|
-
const style = window.getComputedStyle(html);
|
|
435
|
-
const rect = html.getBoundingClientRect();
|
|
436
|
-
const zIndex = Number.parseInt(style.zIndex || '0', 10);
|
|
437
|
-
const isFloating = style.position === 'fixed' || style.position === 'absolute' || style.position === 'sticky' || zIndex > 0;
|
|
438
|
-
if (!isFloating)
|
|
439
|
-
return false;
|
|
440
|
-
if (rect.width < config.visibilityLimits.minOverlayWidth || rect.height < config.visibilityLimits.minOverlayHeight)
|
|
441
|
-
return false;
|
|
442
|
-
if (rect.bottom < 0 || rect.right < 0 || rect.top > window.innerHeight || rect.left > window.innerWidth)
|
|
443
|
-
return false;
|
|
444
|
-
if (rect.width >= window.innerWidth * config.visibilityLimits.maxViewportOverlayRatio && rect.height >= window.innerHeight * config.visibilityLimits.maxViewportOverlayRatio)
|
|
445
|
-
return false;
|
|
446
|
-
const { interactiveCount, text } = getUsefulContent(element);
|
|
447
|
-
return interactiveCount > 0 || text.length > 0;
|
|
448
|
-
}
|
|
449
|
-
function isFloatingOverlay(element) {
|
|
450
|
-
const style = window.getComputedStyle(element);
|
|
451
|
-
return style.position === 'fixed' || style.position === 'absolute' || Number.parseInt(style.zIndex || '0', 10) > 0;
|
|
452
|
-
}
|
|
453
|
-
const seen = new Set();
|
|
454
|
-
const collected = [];
|
|
455
|
-
for (const selector of config.overlaySelectors) {
|
|
456
|
-
for (const element of Array.from(document.querySelectorAll(selector))) {
|
|
457
|
-
if (seen.has(element))
|
|
458
|
-
continue;
|
|
459
|
-
seen.add(element);
|
|
460
|
-
if (!isVisible(element))
|
|
461
|
-
continue;
|
|
462
|
-
if (!element.matches(config.overlaySemanticSelector) && !isFloatingOverlay(element))
|
|
463
|
-
continue;
|
|
464
|
-
const { interactiveCount, text } = getUsefulContent(element);
|
|
465
|
-
if (interactiveCount === 0 && text.length === 0)
|
|
466
|
-
continue;
|
|
467
|
-
collected.push(element);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
const overlays = collected.filter((element) => !collected.some((other) => other !== element && element.contains(other))).map((element) => element.outerHTML.slice(0, config.limits.overlayHtmlLength));
|
|
471
|
-
if (overlays.length === 0 && config.geometryFallback !== false) {
|
|
472
|
-
const floatingCandidates = Array.from(document.body.querySelectorAll('*'))
|
|
473
|
-
.filter((element) => !seen.has(element) && isVisible(element) && isLikelyFloatingOverlay(element))
|
|
474
|
-
.sort((left, right) => {
|
|
475
|
-
const leftStyle = window.getComputedStyle(left);
|
|
476
|
-
const rightStyle = window.getComputedStyle(right);
|
|
477
|
-
const leftZ = Number.parseInt(leftStyle.zIndex || '0', 10) || 0;
|
|
478
|
-
const rightZ = Number.parseInt(rightStyle.zIndex || '0', 10) || 0;
|
|
479
|
-
if (leftZ !== rightZ)
|
|
480
|
-
return rightZ - leftZ;
|
|
481
|
-
const leftRect = left.getBoundingClientRect();
|
|
482
|
-
const rightRect = right.getBoundingClientRect();
|
|
483
|
-
return leftRect.width * leftRect.height - rightRect.width * rightRect.height;
|
|
484
|
-
});
|
|
485
|
-
for (const element of floatingCandidates.slice(0, config.limits.maxOverlayCount)) {
|
|
486
|
-
overlays.push(element.outerHTML.slice(0, config.limits.overlayHtmlLength));
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
return overlays.slice(0, config.limits.maxOverlayCount).join('\n\n--- overlay ---\n\n');
|
|
490
|
-
}
|
|
491
409
|
export function extractComponentScopeHtml(eidx, config) {
|
|
492
410
|
const element = document.querySelector(`[${config.eidxAttr}="${eidx}"]`);
|
|
493
411
|
if (!element)
|
|
@@ -507,9 +425,6 @@ export function extractComponentScopeHtml(eidx, config) {
|
|
|
507
425
|
return element.outerHTML.slice(0, config.limits.componentScopeHtmlLength);
|
|
508
426
|
return '';
|
|
509
427
|
}
|
|
510
|
-
export function getVisibleOverlayHtmlExtractorSource() {
|
|
511
|
-
return extractVisibleOverlayHtml.toString();
|
|
512
|
-
}
|
|
513
428
|
export function getComponentScopeHtmlExtractorSource() {
|
|
514
429
|
return extractComponentScopeHtml.toString();
|
|
515
430
|
}
|
|
@@ -927,6 +842,19 @@ export function htmlCombinedSnapshot(html, htmlConfig, opts) {
|
|
|
927
842
|
cleanAllElements(body);
|
|
928
843
|
return serialize(document);
|
|
929
844
|
}
|
|
845
|
+
/**
|
|
846
|
+
* Cleans a small HTML snippet for AI consumption: keeps structure and text,
|
|
847
|
+
* drops noisy attributes and generated class names
|
|
848
|
+
*/
|
|
849
|
+
export function cleanHtmlSnippet(html) {
|
|
850
|
+
const fragment = parseFragment(html);
|
|
851
|
+
for (const node of fragment.childNodes) {
|
|
852
|
+
if (!('tagName' in node))
|
|
853
|
+
continue;
|
|
854
|
+
cleanAllElements(node);
|
|
855
|
+
}
|
|
856
|
+
return serialize(fragment);
|
|
857
|
+
}
|
|
930
858
|
/**
|
|
931
859
|
* Creates text-only snapshot with markdown formatting
|
|
932
860
|
*/
|
|
@@ -1381,6 +1309,7 @@ function cleanElement(element) {
|
|
|
1381
1309
|
if (attr.name === 'class') {
|
|
1382
1310
|
attr.value = attr.value
|
|
1383
1311
|
.split(/\s+/)
|
|
1312
|
+
.filter(Boolean)
|
|
1384
1313
|
.filter((className) => !/\d/.test(className))
|
|
1385
1314
|
.filter((className) => !className.includes(':'))
|
|
1386
1315
|
.filter((className) => !TAILWIND_CLASS_PATTERNS.some((pattern) => pattern.test(className)))
|
|
@@ -1,24 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export
|
|
3
|
-
readonly semanticOverlays: readonly ["[role=\"dialog\"]", "[role=\"listbox\"]", "[role=\"menu\"]", "[role=\"tooltip\"]:not([style*=\"display: none\"]):not([style*=\"visibility: hidden\"])", "[class*=\"modal\"]", "[class*=\"dialog\"]", "[class*=\"overlay\"]", "[class*=\"popup\"]", "[class*=\"drawer\"]", "[class*=\"lightbox\"]"];
|
|
4
|
-
readonly modalOverlays: readonly ["[role=\"dialog\"]", "[role=\"alertdialog\"]", "[aria-modal=\"true\"]", "[class*=\"modal\"]", "[class*=\"dialog\"]", "[class*=\"overlay\"]", "[class*=\"popup\"]", "[class*=\"drawer\"]", "[class*=\"lightbox\"]"];
|
|
5
|
-
readonly overlaySemanticSelector: "[role=\"dialog\"], [role=\"alertdialog\"], [aria-modal=\"true\"], [role=\"listbox\"], [role=\"menu\"], [role=\"tooltip\"]";
|
|
6
|
-
};
|
|
1
|
+
import type { HtmlDiffPart } from './html-diff.js';
|
|
2
|
+
export type OverlayType = 'modal' | 'region';
|
|
7
3
|
export type OverlayData = {
|
|
8
|
-
type?:
|
|
4
|
+
type?: OverlayType | null;
|
|
9
5
|
name?: string | null;
|
|
6
|
+
root?: string | null;
|
|
7
|
+
html?: string | null;
|
|
8
|
+
xpath?: string | null;
|
|
9
|
+
parent?: OverlayData | null;
|
|
10
10
|
};
|
|
11
11
|
export declare class Overlay {
|
|
12
|
-
readonly type:
|
|
12
|
+
readonly type: OverlayType | null;
|
|
13
13
|
readonly name: string | null;
|
|
14
|
+
readonly root: string | null;
|
|
15
|
+
readonly html: string | null;
|
|
16
|
+
readonly xpath: string | null;
|
|
17
|
+
readonly parent: OverlayData | null;
|
|
14
18
|
constructor(data?: OverlayData);
|
|
15
19
|
get detected(): boolean;
|
|
16
|
-
|
|
20
|
+
get present(): boolean;
|
|
21
|
+
describe(): string;
|
|
22
|
+
withGeometry(geometry: Overlay): Overlay;
|
|
23
|
+
withParent(parent: Overlay): Overlay;
|
|
17
24
|
static fromAria(snapshot: string | null): Overlay;
|
|
18
25
|
static resolve(data: {
|
|
19
|
-
overlayHtml?: string;
|
|
20
26
|
overlay?: OverlayData | null;
|
|
21
27
|
ariaSnapshot?: string | null;
|
|
22
28
|
}): Overlay;
|
|
23
|
-
static captureConfig(): VisibleOverlayExtractionConfig;
|
|
24
29
|
}
|
|
30
|
+
export declare class OverlayPage {
|
|
31
|
+
page: {
|
|
32
|
+
evaluate(fn: any, arg: any): Promise<any>;
|
|
33
|
+
} | null;
|
|
34
|
+
constructor(page: {
|
|
35
|
+
evaluate(fn: any, arg: any): Promise<any>;
|
|
36
|
+
} | null);
|
|
37
|
+
detectRegion(diff: RegionDiff): Promise<Overlay | null>;
|
|
38
|
+
isStillOpen(overlay: Overlay): Promise<boolean>;
|
|
39
|
+
appearedSubRoots(diff: RegionDiff): AppearedSubRoot[];
|
|
40
|
+
probe(xpath: string): Promise<RegionProbe | null>;
|
|
41
|
+
toOverlay(subRoot: AppearedSubRoot, probe: RegionProbe | null, previousHtml: string): Overlay;
|
|
42
|
+
nameFrom(html: string, previousHtml: string): string | null;
|
|
43
|
+
freshHeading(html: string, previousHtml: string): string | null;
|
|
44
|
+
headingsOf(html: string): string[];
|
|
45
|
+
}
|
|
46
|
+
export interface RegionDiff {
|
|
47
|
+
parts: HtmlDiffPart[];
|
|
48
|
+
pageSize: number;
|
|
49
|
+
similarity: number;
|
|
50
|
+
sameUrl: boolean;
|
|
51
|
+
previousHtml: string;
|
|
52
|
+
}
|
|
53
|
+
interface AppearedSubRoot {
|
|
54
|
+
container: string;
|
|
55
|
+
elementXPath: string;
|
|
56
|
+
subtree: string;
|
|
57
|
+
size: number;
|
|
58
|
+
rawSize: number;
|
|
59
|
+
appearedSelector?: string;
|
|
60
|
+
fresh: boolean;
|
|
61
|
+
}
|
|
62
|
+
interface RegionProbe {
|
|
63
|
+
found: boolean;
|
|
64
|
+
onScreen: boolean;
|
|
65
|
+
floating: boolean;
|
|
66
|
+
centerBelongs: boolean;
|
|
67
|
+
coveredByFloating: boolean;
|
|
68
|
+
}
|
|
69
|
+
export {};
|