explorbot 0.4.1 → 0.4.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/bin/explorbot-cli.ts +8 -3
- package/boat/api-tester/src/ai/curler-tools.ts +1 -1
- package/boat/api-tester/src/cli.ts +3 -0
- package/boat/doc-collector/src/ai/documentarian.ts +57 -5
- package/boat/doc-collector/src/ai/tools.ts +1 -1
- package/boat/doc-collector/src/cli.ts +18 -2
- package/boat/doc-collector/src/config.ts +3 -0
- package/boat/doc-collector/src/docbot.ts +56 -11
- package/boat/doc-collector/src/docs-renderer.ts +79 -55
- package/boat/doc-collector/src/interaction-screenshots.ts +19 -10
- package/boat/doc-collector/src/screenshots.ts +106 -3
- package/boat/doc-collector/src/template-dedup.ts +31 -0
- package/boat/prima/src/cli.ts +3 -0
- package/boat/prima/src/prima.ts +3 -4
- package/dist/bin/explorbot-cli.js +9 -3
- package/dist/boat/api-tester/src/ai/curler-tools.js +1 -1
- package/dist/boat/api-tester/src/cli.js +2 -0
- package/dist/boat/doc-collector/src/ai/documentarian.js +62 -4
- package/dist/boat/doc-collector/src/ai/tools.js +1 -1
- package/dist/boat/doc-collector/src/cli.js +15 -2
- package/dist/boat/doc-collector/src/config.js +1 -0
- package/dist/boat/doc-collector/src/docbot.js +52 -10
- package/dist/boat/doc-collector/src/docs-renderer.js +69 -46
- package/dist/boat/doc-collector/src/interaction-screenshots.js +18 -11
- package/dist/boat/doc-collector/src/screenshots.js +104 -1
- package/dist/boat/doc-collector/src/template-dedup.js +27 -0
- package/dist/boat/prima/src/cli.js +2 -0
- package/dist/boat/prima/src/prima.js +3 -4
- package/dist/package.json +1 -1
- package/dist/src/action-result.d.ts +2 -2
- package/dist/src/action-result.js +15 -17
- package/dist/src/action.d.ts +1 -1
- package/dist/src/action.js +15 -15
- package/dist/src/ai/fisherman/request-haul.d.ts +11 -0
- package/dist/src/ai/fisherman/request-haul.js +28 -0
- package/dist/src/ai/fisherman-tools.d.ts +10 -1
- package/dist/src/ai/fisherman-tools.js +35 -31
- package/dist/src/ai/fisherman.d.ts +3 -2
- package/dist/src/ai/fisherman.js +8 -6
- package/dist/src/ai/pilot.js +21 -16
- package/dist/src/ai/provider.js +8 -14
- package/dist/src/ai/researcher/coordinates.d.ts +2 -0
- package/dist/src/ai/researcher/coordinates.js +13 -1
- package/dist/src/ai/researcher/deep-analysis.js +9 -9
- package/dist/src/ai/researcher/fingerprint-worker.js +1 -11
- package/dist/src/ai/researcher.js +1 -1
- package/dist/src/ai/rules.js +8 -0
- package/dist/src/ai/tester.js +20 -20
- package/dist/src/ai/tools.d.ts +1 -1
- package/dist/src/ai/tools.js +18 -74
- package/dist/src/api/request-store.d.ts +1 -5
- package/dist/src/api/request-store.js +12 -22
- package/dist/src/commands/config-command.js +2 -9
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init-command.js +3 -7
- package/dist/src/commands/plans-command.d.ts +4 -1
- package/dist/src/commands/plans-command.js +4 -4
- package/dist/src/commands/recommended-models-command.d.ts +9 -0
- package/dist/src/commands/recommended-models-command.js +85 -0
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +7 -2
- package/dist/src/experience-tracker.js +1 -1
- package/dist/src/explorbot.js +3 -3
- package/dist/src/global-config.d.ts +2 -0
- package/dist/src/global-config.js +6 -0
- package/dist/src/state-manager.d.ts +3 -3
- package/dist/src/state-manager.js +8 -8
- package/dist/src/test-plan.d.ts +1 -0
- package/dist/src/test-plan.js +13 -9
- package/dist/src/utils/aria.d.ts +1 -1
- package/dist/src/utils/aria.js +23 -2
- package/dist/src/utils/cli-section.d.ts +1 -0
- package/dist/src/utils/cli-section.js +12 -0
- package/dist/src/utils/html-diff.d.ts +1 -0
- package/dist/src/utils/html-diff.js +5 -5
- package/dist/src/utils/html.d.ts +2 -0
- package/dist/src/utils/html.js +5 -4
- package/dist/src/utils/overlay.d.ts +19 -55
- package/dist/src/utils/overlay.js +94 -148
- package/dist/src/utils/region.d.ts +41 -0
- package/dist/src/utils/region.js +99 -0
- package/dist/src/utils/similarity.d.ts +1 -0
- package/dist/src/utils/similarity.js +11 -0
- package/dist/src/utils/url-matcher.d.ts +1 -1
- package/dist/src/utils/url-matcher.js +4 -4
- package/docs/doc-collection/basics.md +11 -5
- package/docs/doc-collection/crawling.md +21 -1
- package/docs/reference/commands.md +3 -0
- package/docs/superpowers/plans/2026-09-03-model-resolves-click-ambiguity.md +509 -0
- package/docs/workflow/application-spec.md +4 -0
- package/package.json +1 -1
- package/src/action-result.ts +15 -16
- package/src/action.ts +16 -15
- package/src/ai/fisherman/request-haul.ts +32 -0
- package/src/ai/fisherman-tools.ts +38 -32
- package/src/ai/fisherman.ts +8 -7
- package/src/ai/pilot.ts +19 -14
- package/src/ai/provider.ts +8 -12
- package/src/ai/researcher/coordinates.ts +13 -1
- package/src/ai/researcher/deep-analysis.ts +9 -9
- package/src/ai/researcher/fingerprint-worker.ts +1 -10
- package/src/ai/researcher.ts +1 -1
- package/src/ai/rules.ts +8 -0
- package/src/ai/tester.ts +18 -18
- package/src/ai/tools.ts +18 -86
- package/src/api/request-store.ts +13 -27
- package/src/commands/config-command.ts +2 -7
- package/src/commands/index.ts +2 -0
- package/src/commands/init-command.ts +4 -11
- package/src/commands/plans-command.ts +5 -5
- package/src/commands/recommended-models-command.ts +92 -0
- package/src/config.ts +9 -2
- package/src/experience-tracker.ts +1 -1
- package/src/explorbot.ts +3 -3
- package/src/global-config.ts +8 -0
- package/src/state-manager.ts +10 -9
- package/src/test-plan.ts +12 -10
- package/src/utils/aria.ts +26 -3
- package/src/utils/cli-section.ts +12 -0
- package/src/utils/html-diff.ts +6 -6
- package/src/utils/html.ts +6 -4
- package/src/utils/overlay.ts +105 -192
- package/src/utils/region.ts +132 -0
- package/src/utils/similarity.ts +9 -0
- package/src/utils/url-matcher.ts +3 -3
- package/dist/src/utils/request-map.d.ts +0 -7
- package/dist/src/utils/request-map.js +0 -16
- package/src/utils/request-map.ts +0 -19
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { HtmlDiffPart } from './html-diff.js';
|
|
2
|
+
import { pathToXPath } from './html-diff.js';
|
|
3
|
+
import { extractHeadings } from './html.js';
|
|
4
|
+
import { createDebug } from './logger.js';
|
|
5
|
+
|
|
6
|
+
const debugLog = createDebug('explorbot:region');
|
|
7
|
+
|
|
8
|
+
export class Region {
|
|
9
|
+
readonly type: RegionType | null;
|
|
10
|
+
readonly name: string | null;
|
|
11
|
+
readonly root: string | null;
|
|
12
|
+
readonly html: string | null;
|
|
13
|
+
readonly xpath: string | null;
|
|
14
|
+
readonly parent: RegionData | null;
|
|
15
|
+
|
|
16
|
+
constructor(data: RegionData = {}) {
|
|
17
|
+
this.type = data.type ?? null;
|
|
18
|
+
this.name = data.name ?? null;
|
|
19
|
+
this.root = data.root ?? null;
|
|
20
|
+
this.html = data.html ?? null;
|
|
21
|
+
this.xpath = data.xpath ?? null;
|
|
22
|
+
this.parent = data.parent ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get isOpen(): boolean {
|
|
26
|
+
return this.type !== null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get isModal(): boolean {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe(): string {
|
|
34
|
+
if (!this.isOpen) return '';
|
|
35
|
+
let text = `${this.type} "${this.name || 'unnamed'}" opened`;
|
|
36
|
+
if (this.root) text += `, scope: ${this.root}`;
|
|
37
|
+
return text;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
withParent(parent: Region): Region {
|
|
41
|
+
const Ctor = this.constructor as new (data: RegionData) => Region;
|
|
42
|
+
return new Ctor({
|
|
43
|
+
type: this.type,
|
|
44
|
+
name: this.name,
|
|
45
|
+
root: this.root,
|
|
46
|
+
html: this.html,
|
|
47
|
+
xpath: this.xpath,
|
|
48
|
+
parent: { type: parent.type, name: parent.name, root: parent.root, xpath: parent.xpath },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function addedSubtrees(diff: RegionDiff): AddedSubtree[] {
|
|
54
|
+
const candidates: AddedSubtree[] = [];
|
|
55
|
+
for (const part of diff.parts) {
|
|
56
|
+
const appeared = part.added.find((line) => line.startsWith('ELEMENT:'));
|
|
57
|
+
if (!appeared) continue;
|
|
58
|
+
if (diff.pageSize > 0 && part.rawSize > REGION_MAX_RATIO * diff.pageSize) {
|
|
59
|
+
debugLog(`Added subtree spans ${Math.round((part.rawSize / diff.pageSize) * 100)}% of the page — a new state, not a region`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const headings = headingsOf(part.subtree);
|
|
63
|
+
const newHeading = headings.find((heading) => !diff.previousHtml.includes(heading)) ?? null;
|
|
64
|
+
let name = newHeading;
|
|
65
|
+
if (!name && headings.length > 0) name = headings.join(' ');
|
|
66
|
+
const namedContainer = part.container !== 'body' && !part.container.startsWith('//');
|
|
67
|
+
let root: string | null = null;
|
|
68
|
+
if (namedContainer && part.containerWasEmpty) root = part.container;
|
|
69
|
+
if (!root) root = part.appearedSelector ?? null;
|
|
70
|
+
if (!root && namedContainer) root = part.container;
|
|
71
|
+
candidates.push({
|
|
72
|
+
xpath: pathToXPath(appeared.slice('ELEMENT:'.length)),
|
|
73
|
+
html: part.subtree,
|
|
74
|
+
size: part.subtree.length,
|
|
75
|
+
rawSize: part.rawSize,
|
|
76
|
+
sizable: part.subtree.length >= REGION_MIN_HTML,
|
|
77
|
+
name,
|
|
78
|
+
root,
|
|
79
|
+
hasNewHeading: !!newHeading,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const sizable = candidates.filter((candidate) => candidate.sizable);
|
|
83
|
+
const totalRawSize = sizable.reduce((sum, candidate) => sum + candidate.rawSize, 0);
|
|
84
|
+
const largest = Math.max(0, ...sizable.map((candidate) => candidate.rawSize));
|
|
85
|
+
if (largest < REGION_DOMINANCE * totalRawSize) {
|
|
86
|
+
debugLog('Changes are scattered across the page, no dominant region');
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const identified = candidates.filter((candidate) => candidate.name || candidate.root);
|
|
90
|
+
if (identified.length < candidates.length) debugLog('Dropped added content with no name and no scoping root');
|
|
91
|
+
identified.sort((a, b) => Number(b.hasNewHeading) - Number(a.hasNewHeading) || b.size - a.size);
|
|
92
|
+
return identified;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const headingsOf = (html: string): string[] => {
|
|
96
|
+
const headings = extractHeadings(html);
|
|
97
|
+
return [headings.h1, headings.h2, headings.h3, headings.h4].filter(Boolean) as string[];
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const REGION_MIN_HTML = 5_000;
|
|
101
|
+
const REGION_MAX_RATIO = 0.6;
|
|
102
|
+
const REGION_DOMINANCE = 0.7;
|
|
103
|
+
|
|
104
|
+
export type RegionType = 'overlay' | 'region';
|
|
105
|
+
|
|
106
|
+
export type RegionData = {
|
|
107
|
+
type?: RegionType | null;
|
|
108
|
+
name?: string | null;
|
|
109
|
+
root?: string | null;
|
|
110
|
+
html?: string | null;
|
|
111
|
+
xpath?: string | null;
|
|
112
|
+
parent?: RegionData | null;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export interface RegionDiff {
|
|
116
|
+
parts: HtmlDiffPart[];
|
|
117
|
+
pageSize: number;
|
|
118
|
+
similarity: number;
|
|
119
|
+
sameUrl: boolean;
|
|
120
|
+
previousHtml: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface AddedSubtree {
|
|
124
|
+
xpath: string;
|
|
125
|
+
html: string;
|
|
126
|
+
size: number;
|
|
127
|
+
rawSize: number;
|
|
128
|
+
sizable: boolean;
|
|
129
|
+
name: string | null;
|
|
130
|
+
root: string | null;
|
|
131
|
+
hasNewHeading: boolean;
|
|
132
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function diceSimilarity(a: Set<string>, b: Set<string>): number {
|
|
2
|
+
let intersection = 0;
|
|
3
|
+
for (const item of a) {
|
|
4
|
+
if (b.has(item)) intersection++;
|
|
5
|
+
}
|
|
6
|
+
const total = a.size + b.size;
|
|
7
|
+
if (total === 0) return 100;
|
|
8
|
+
return Math.round(((2 * intersection) / total) * 100);
|
|
9
|
+
}
|
package/src/utils/url-matcher.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ConfigParser } from '../config.js';
|
|
|
4
4
|
export function isDynamicSegment(segment: string): boolean {
|
|
5
5
|
try {
|
|
6
6
|
const configRegex = ConfigParser.getInstance().getConfig().dynamicPageRegex;
|
|
7
|
-
if (configRegex
|
|
7
|
+
if (configRegex && new RegExp(configRegex, 'i').test(segment)) return true;
|
|
8
8
|
} catch {
|
|
9
9
|
/* config not loaded yet */
|
|
10
10
|
}
|
|
@@ -52,10 +52,10 @@ export function generalizeSegment(segment: string): string {
|
|
|
52
52
|
return '[^/]+';
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function generalizeUrl(url: string): string {
|
|
55
|
+
export function generalizeUrl(url: string, replaceSegment: (segment: string) => string = generalizeSegment): string {
|
|
56
56
|
return url
|
|
57
57
|
.split('/')
|
|
58
|
-
.map((seg) => (seg.length > 0 && isDynamicSegment(seg) ?
|
|
58
|
+
.map((seg) => (seg.length > 0 && isDynamicSegment(seg) ? replaceSegment(seg) : seg))
|
|
59
59
|
.join('/');
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { RequestResult } from '../api/request-result.js';
|
|
2
|
-
export declare class RequestMap {
|
|
3
|
-
requests: Map<string, RequestResult>;
|
|
4
|
-
constructor(requests?: RequestResult[]);
|
|
5
|
-
add(request: RequestResult): void;
|
|
6
|
-
get(id: string | number): RequestResult | undefined;
|
|
7
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export class RequestMap {
|
|
2
|
-
requests = new Map();
|
|
3
|
-
constructor(requests = []) {
|
|
4
|
-
for (const request of requests)
|
|
5
|
-
this.add(request);
|
|
6
|
-
}
|
|
7
|
-
add(request) {
|
|
8
|
-
const { id } = request.extractIdAndTitle();
|
|
9
|
-
if (id === undefined)
|
|
10
|
-
return;
|
|
11
|
-
this.requests.set(String(id), request);
|
|
12
|
-
}
|
|
13
|
-
get(id) {
|
|
14
|
-
return this.requests.get(String(id));
|
|
15
|
-
}
|
|
16
|
-
}
|
package/src/utils/request-map.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { RequestResult } from '../api/request-result.ts';
|
|
2
|
-
|
|
3
|
-
export class RequestMap {
|
|
4
|
-
private requests = new Map<string, RequestResult>();
|
|
5
|
-
|
|
6
|
-
constructor(requests: RequestResult[] = []) {
|
|
7
|
-
for (const request of requests) this.add(request);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
add(request: RequestResult): void {
|
|
11
|
-
const { id } = request.extractIdAndTitle();
|
|
12
|
-
if (id === undefined) return;
|
|
13
|
-
this.requests.set(String(id), request);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
get(id: string | number): RequestResult | undefined {
|
|
17
|
-
return this.requests.get(String(id));
|
|
18
|
-
}
|
|
19
|
-
}
|