openchrome-mcp 1.2.13 → 1.3.0
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/dist/cli/update-check.js +3 -3
- package/dist/cli/update-check.js.map +1 -1
- package/dist/tools/click-element.d.ts.map +1 -1
- package/dist/tools/click-element.js +10 -79
- package/dist/tools/click-element.js.map +1 -1
- package/dist/tools/computer.d.ts.map +1 -1
- package/dist/tools/computer.js +35 -5
- package/dist/tools/computer.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspect.d.ts +12 -0
- package/dist/tools/inspect.d.ts.map +1 -0
- package/dist/tools/inspect.js +392 -0
- package/dist/tools/inspect.js.map +1 -0
- package/dist/tools/interact.d.ts +9 -0
- package/dist/tools/interact.d.ts.map +1 -0
- package/dist/tools/interact.js +426 -0
- package/dist/tools/interact.js.map +1 -0
- package/dist/tools/navigate.d.ts.map +1 -1
- package/dist/tools/navigate.js +17 -0
- package/dist/tools/navigate.js.map +1 -1
- package/dist/utils/adaptive-screenshot.d.ts +48 -0
- package/dist/utils/adaptive-screenshot.d.ts.map +1 -0
- package/dist/utils/adaptive-screenshot.js +114 -0
- package/dist/utils/adaptive-screenshot.js.map +1 -0
- package/dist/utils/element-finder.d.ts +49 -0
- package/dist/utils/element-finder.d.ts.map +1 -0
- package/dist/utils/element-finder.js +130 -0
- package/dist/utils/element-finder.js.map +1 -0
- package/dist/utils/visual-summary.d.ts +17 -0
- package/dist/utils/visual-summary.d.ts.map +1 -0
- package/dist/utils/visual-summary.js +198 -0
- package/dist/utils/visual-summary.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive Screenshot - Tracks screenshot history per tab and degrades
|
|
4
|
+
* response format on repetition to reduce token waste from screenshot loops.
|
|
5
|
+
*
|
|
6
|
+
* Returns a mode ('full' | 'annotated' | 'text_only') based on how many times
|
|
7
|
+
* the same scroll position has been captured within a recent time window.
|
|
8
|
+
* The actual screenshot capture and visual summary generation are handled by
|
|
9
|
+
* the caller (computer.ts) — this module only decides the response mode.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AdaptiveScreenshot = void 0;
|
|
13
|
+
/** How close two scroll positions must be (in pixels) to count as "same" */
|
|
14
|
+
const POSITION_TOLERANCE_PX = 50;
|
|
15
|
+
/** Window within which repeated screenshots at the same position degrade (ms) */
|
|
16
|
+
const REPEAT_WINDOW_MS = 30_000;
|
|
17
|
+
/** How long before a record is pruned entirely (ms) */
|
|
18
|
+
const PRUNE_AGE_MS = 60_000;
|
|
19
|
+
const ANNOTATED_NOTE = 'Note: No significant visual change detected since last screenshot at this scroll position.';
|
|
20
|
+
/**
|
|
21
|
+
* Singleton that tracks screenshot history per tabId and returns the
|
|
22
|
+
* appropriate response mode for each screenshot request.
|
|
23
|
+
*/
|
|
24
|
+
class AdaptiveScreenshot {
|
|
25
|
+
static instance;
|
|
26
|
+
/** tabId → ordered list of screenshot records (oldest first) */
|
|
27
|
+
history = new Map();
|
|
28
|
+
constructor() { }
|
|
29
|
+
static getInstance() {
|
|
30
|
+
if (!AdaptiveScreenshot.instance) {
|
|
31
|
+
AdaptiveScreenshot.instance = new AdaptiveScreenshot();
|
|
32
|
+
}
|
|
33
|
+
return AdaptiveScreenshot.instance;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Evaluate what response mode to use for the next screenshot on this tab.
|
|
37
|
+
*
|
|
38
|
+
* Reads the current scroll position from the page, prunes stale records,
|
|
39
|
+
* counts how many recent screenshots have been taken at the same position,
|
|
40
|
+
* then appends the new record and returns the mode.
|
|
41
|
+
*
|
|
42
|
+
* - 1st screenshot at a position → 'full'
|
|
43
|
+
* - 2nd screenshot at same position (within ±50px, within 30s) → 'annotated'
|
|
44
|
+
* - 3rd+ at same position → 'text_only'
|
|
45
|
+
*
|
|
46
|
+
* Fails gracefully: if page.evaluate throws, returns 'full'.
|
|
47
|
+
*/
|
|
48
|
+
async evaluate(page, tabId) {
|
|
49
|
+
let scrollTop = 0;
|
|
50
|
+
let scrollLeft = 0;
|
|
51
|
+
try {
|
|
52
|
+
const pos = await page.evaluate(() => ({
|
|
53
|
+
scrollTop: window.scrollY,
|
|
54
|
+
scrollLeft: window.scrollX,
|
|
55
|
+
}));
|
|
56
|
+
scrollTop = pos.scrollTop;
|
|
57
|
+
scrollLeft = pos.scrollLeft;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Page not available or evaluate failed — default to full
|
|
61
|
+
return 'full';
|
|
62
|
+
}
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
// Prune old entries for this tab
|
|
65
|
+
this.pruneTab(tabId, now);
|
|
66
|
+
const records = this.history.get(tabId) ?? [];
|
|
67
|
+
// Count recent records at the same position
|
|
68
|
+
const recentAtPosition = records.filter((r) => now - r.timestamp <= REPEAT_WINDOW_MS &&
|
|
69
|
+
Math.abs(r.scrollTop - scrollTop) <= POSITION_TOLERANCE_PX &&
|
|
70
|
+
Math.abs(r.scrollLeft - scrollLeft) <= POSITION_TOLERANCE_PX);
|
|
71
|
+
const count = recentAtPosition.length;
|
|
72
|
+
// Append new record
|
|
73
|
+
records.push({ scrollTop, scrollLeft, timestamp: now });
|
|
74
|
+
this.history.set(tabId, records);
|
|
75
|
+
if (count === 0) {
|
|
76
|
+
return 'full';
|
|
77
|
+
}
|
|
78
|
+
else if (count === 1) {
|
|
79
|
+
return 'annotated';
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
return 'text_only';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns the annotation note used when mode is 'annotated'.
|
|
87
|
+
*/
|
|
88
|
+
getAnnotation() {
|
|
89
|
+
return ANNOTATED_NOTE;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Clear screenshot history for a tab (call on navigation).
|
|
93
|
+
*/
|
|
94
|
+
reset(tabId) {
|
|
95
|
+
this.history.delete(tabId);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Remove records older than PRUNE_AGE_MS for the given tab.
|
|
99
|
+
*/
|
|
100
|
+
pruneTab(tabId, now) {
|
|
101
|
+
const records = this.history.get(tabId);
|
|
102
|
+
if (!records || records.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
const pruned = records.filter((r) => now - r.timestamp < PRUNE_AGE_MS);
|
|
105
|
+
if (pruned.length === 0) {
|
|
106
|
+
this.history.delete(tabId);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.history.set(tabId, pruned);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.AdaptiveScreenshot = AdaptiveScreenshot;
|
|
114
|
+
//# sourceMappingURL=adaptive-screenshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-screenshot.js","sourceRoot":"","sources":["../../src/utils/adaptive-screenshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element Finder - Shared element search and scoring utilities
|
|
3
|
+
*
|
|
4
|
+
* Used by click-element, interact, and find tools to locate elements
|
|
5
|
+
* by natural language query with consistent scoring logic.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Represents a found element with its properties and match score.
|
|
9
|
+
*/
|
|
10
|
+
export interface FoundElement {
|
|
11
|
+
backendDOMNodeId: number;
|
|
12
|
+
role: string;
|
|
13
|
+
name: string;
|
|
14
|
+
tagName: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
ariaLabel?: string;
|
|
18
|
+
textContent?: string;
|
|
19
|
+
rect: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
score: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Tokenize a query string into meaningful search tokens.
|
|
29
|
+
* Filters out stop words and single-character tokens.
|
|
30
|
+
*/
|
|
31
|
+
export declare function tokenizeQuery(query: string): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Score an element based on how well it matches the query.
|
|
34
|
+
*
|
|
35
|
+
* Scoring priorities:
|
|
36
|
+
* - Exact name/text match: +100
|
|
37
|
+
* - Exact aria-label match: +90
|
|
38
|
+
* - Contains full query: +50/+45
|
|
39
|
+
* - Token matches: +15 per token
|
|
40
|
+
* - Role match bonus: +30
|
|
41
|
+
* - Interactive element bonus: +20
|
|
42
|
+
* - Size bonuses/penalties: +10/-20
|
|
43
|
+
*/
|
|
44
|
+
export declare function scoreElement(element: FoundElement, queryLower: string, queryTokens: string[]): number;
|
|
45
|
+
/**
|
|
46
|
+
* CSS selectors for interactive elements, used by in-page search.
|
|
47
|
+
*/
|
|
48
|
+
export declare const INTERACTIVE_SELECTORS: string[];
|
|
49
|
+
//# sourceMappingURL=element-finder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-finder.d.ts","sourceRoot":"","sources":["../../src/utils/element-finder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,KAAK,EAAE,MAAM,CAAC;CACf;AASD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EAAE,GACpB,MAAM,CAuER;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,UAyBjC,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Element Finder - Shared element search and scoring utilities
|
|
4
|
+
*
|
|
5
|
+
* Used by click-element, interact, and find tools to locate elements
|
|
6
|
+
* by natural language query with consistent scoring logic.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.INTERACTIVE_SELECTORS = void 0;
|
|
10
|
+
exports.tokenizeQuery = tokenizeQuery;
|
|
11
|
+
exports.scoreElement = scoreElement;
|
|
12
|
+
/**
|
|
13
|
+
* Stop words filtered out when tokenizing queries.
|
|
14
|
+
*/
|
|
15
|
+
const STOP_WORDS = new Set([
|
|
16
|
+
'the', 'a', 'an', 'to', 'for', 'of', 'in', 'on', 'at', 'and', 'or',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Tokenize a query string into meaningful search tokens.
|
|
20
|
+
* Filters out stop words and single-character tokens.
|
|
21
|
+
*/
|
|
22
|
+
function tokenizeQuery(query) {
|
|
23
|
+
return query
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.split(/\s+/)
|
|
26
|
+
.filter(t => t.length > 1)
|
|
27
|
+
.filter(t => !STOP_WORDS.has(t));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Score an element based on how well it matches the query.
|
|
31
|
+
*
|
|
32
|
+
* Scoring priorities:
|
|
33
|
+
* - Exact name/text match: +100
|
|
34
|
+
* - Exact aria-label match: +90
|
|
35
|
+
* - Contains full query: +50/+45
|
|
36
|
+
* - Token matches: +15 per token
|
|
37
|
+
* - Role match bonus: +30
|
|
38
|
+
* - Interactive element bonus: +20
|
|
39
|
+
* - Size bonuses/penalties: +10/-20
|
|
40
|
+
*/
|
|
41
|
+
function scoreElement(element, queryLower, queryTokens) {
|
|
42
|
+
let score = 0;
|
|
43
|
+
const nameLower = element.name.toLowerCase();
|
|
44
|
+
const textLower = element.textContent?.toLowerCase() || '';
|
|
45
|
+
const ariaLower = element.ariaLabel?.toLowerCase() || '';
|
|
46
|
+
const placeholderLower = element.placeholder?.toLowerCase() || '';
|
|
47
|
+
// Exact match bonus (highest priority)
|
|
48
|
+
if (nameLower === queryLower || textLower === queryLower) {
|
|
49
|
+
score += 100;
|
|
50
|
+
}
|
|
51
|
+
// Aria label exact match
|
|
52
|
+
if (ariaLower === queryLower) {
|
|
53
|
+
score += 90;
|
|
54
|
+
}
|
|
55
|
+
// Contains full query
|
|
56
|
+
if (nameLower.includes(queryLower) || textLower.includes(queryLower)) {
|
|
57
|
+
score += 50;
|
|
58
|
+
}
|
|
59
|
+
if (ariaLower.includes(queryLower)) {
|
|
60
|
+
score += 45;
|
|
61
|
+
}
|
|
62
|
+
// Token matching (partial match for multi-word queries)
|
|
63
|
+
const combinedText = `${nameLower} ${textLower} ${ariaLower} ${placeholderLower}`;
|
|
64
|
+
const matchedTokens = queryTokens.filter(token => combinedText.includes(token));
|
|
65
|
+
score += matchedTokens.length * 15;
|
|
66
|
+
// Role matching bonus - if query mentions role
|
|
67
|
+
const roleMatches = [
|
|
68
|
+
['button', () => element.role === 'button' || element.tagName === 'button'],
|
|
69
|
+
['link', () => element.role === 'link' || element.tagName === 'a'],
|
|
70
|
+
['radio', () => element.role === 'radio' || element.type === 'radio'],
|
|
71
|
+
['checkbox', () => element.role === 'checkbox' || element.type === 'checkbox'],
|
|
72
|
+
['input', () => element.tagName === 'input' || element.tagName === 'textarea'],
|
|
73
|
+
['switch', () => element.role === 'switch'],
|
|
74
|
+
['toggle', () => element.role === 'switch'],
|
|
75
|
+
['dropdown', () => element.role === 'combobox' || element.role === 'listbox'],
|
|
76
|
+
['select', () => element.role === 'combobox' || element.role === 'listbox'],
|
|
77
|
+
['slider', () => element.role === 'slider'],
|
|
78
|
+
];
|
|
79
|
+
for (const [keyword, matcher] of roleMatches) {
|
|
80
|
+
if (queryLower.includes(keyword) && matcher()) {
|
|
81
|
+
score += 30;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Interactive element bonus
|
|
85
|
+
if ([
|
|
86
|
+
'button', 'link', 'checkbox', 'radio', 'menuitem', 'tab',
|
|
87
|
+
'option', 'switch', 'combobox', 'listbox', 'slider', 'treeitem',
|
|
88
|
+
].includes(element.role)) {
|
|
89
|
+
score += 20;
|
|
90
|
+
}
|
|
91
|
+
// Visible size bonus (larger elements are usually more important)
|
|
92
|
+
if (element.rect.width > 50 && element.rect.height > 20) {
|
|
93
|
+
score += 10;
|
|
94
|
+
}
|
|
95
|
+
// Penalty for very small elements (likely icons or hidden)
|
|
96
|
+
if (element.rect.width < 10 || element.rect.height < 10) {
|
|
97
|
+
score -= 20;
|
|
98
|
+
}
|
|
99
|
+
return score;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* CSS selectors for interactive elements, used by in-page search.
|
|
103
|
+
*/
|
|
104
|
+
exports.INTERACTIVE_SELECTORS = [
|
|
105
|
+
'button',
|
|
106
|
+
'[role="button"]',
|
|
107
|
+
'a',
|
|
108
|
+
'[role="link"]',
|
|
109
|
+
'input[type="submit"]',
|
|
110
|
+
'input[type="button"]',
|
|
111
|
+
'input[type="radio"]',
|
|
112
|
+
'input[type="checkbox"]',
|
|
113
|
+
'[role="radio"]',
|
|
114
|
+
'[role="checkbox"]',
|
|
115
|
+
'[role="menuitem"]',
|
|
116
|
+
'[role="tab"]',
|
|
117
|
+
'[role="option"]',
|
|
118
|
+
'[onclick]',
|
|
119
|
+
'[tabindex]',
|
|
120
|
+
'[contenteditable="true"]',
|
|
121
|
+
'[role="combobox"]',
|
|
122
|
+
'[role="listbox"]',
|
|
123
|
+
'[role="switch"]',
|
|
124
|
+
'[role="slider"]',
|
|
125
|
+
'[role="treeitem"]',
|
|
126
|
+
'[role="dialog"] [aria-label]',
|
|
127
|
+
'[role="alertdialog"] [aria-label]',
|
|
128
|
+
'[data-testid]',
|
|
129
|
+
];
|
|
130
|
+
//# sourceMappingURL=element-finder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-finder.js","sourceRoot":"","sources":["../../src/utils/element-finder.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AA6BH,sCAMC;AAcD,oCA2EC;AA1GD;;GAEG;AACH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;CACnE,CAAC,CAAC;AAEH;;;GAGG;AACH,SAAgB,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK;SACT,WAAW,EAAE;SACb,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,YAAY,CAC1B,OAAqB,EACrB,UAAkB,EAClB,WAAqB;IAErB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACzD,MAAM,gBAAgB,GAAG,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAElE,uCAAuC;IACvC,IAAI,SAAS,KAAK,UAAU,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QACzD,KAAK,IAAI,GAAG,CAAC;IACf,CAAC;IAED,yBAAyB;IACzB,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC7B,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,sBAAsB;IACtB,IAAI,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACrE,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IACD,IAAI,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACnC,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,wDAAwD;IACxD,MAAM,YAAY,GAAG,GAAG,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,gBAAgB,EAAE,CAAC;IAClF,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAChF,KAAK,IAAI,aAAa,CAAC,MAAM,GAAG,EAAE,CAAC;IAEnC,+CAA+C;IAC/C,MAAM,WAAW,GAAmC;QAClD,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,KAAK,QAAQ,CAAC;QAC3E,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,OAAO,KAAK,GAAG,CAAC;QAClE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC;QACrE,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC;QAC9E,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC;QAC9E,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC;QAC3C,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC;QAC3C,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC;QAC7E,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC;QAC3E,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC;KAC5C,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,WAAW,EAAE,CAAC;QAC7C,IAAI,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,EAAE,EAAE,CAAC;YAC9C,KAAK,IAAI,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,IACE;QACE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK;QACxD,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU;KAChE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EACxB,CAAC;QACD,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,kEAAkE;IAClE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACxD,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,2DAA2D;IAC3D,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACxD,KAAK,IAAI,EAAE,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACU,QAAA,qBAAqB,GAAG;IACnC,QAAQ;IACR,iBAAiB;IACjB,GAAG;IACH,eAAe;IACf,sBAAsB;IACtB,sBAAsB;IACtB,qBAAqB;IACrB,wBAAwB;IACxB,gBAAgB;IAChB,mBAAmB;IACnB,mBAAmB;IACnB,cAAc;IACd,iBAAiB;IACjB,WAAW;IACX,YAAY;IACZ,0BAA0B;IAC1B,mBAAmB;IACnB,kBAAkB;IAClB,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;IACnB,8BAA8B;IAC9B,mCAAmC;IACnC,eAAe;CAChB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual Summary - Generates a lightweight text description of visible page state
|
|
3
|
+
*
|
|
4
|
+
* After click/navigate actions, extracts enough context (~100-150 tokens) for
|
|
5
|
+
* LLMs to understand the current page state without requiring follow-up screenshots.
|
|
6
|
+
*/
|
|
7
|
+
import type { Page } from 'puppeteer-core';
|
|
8
|
+
/**
|
|
9
|
+
* Generate a lightweight text description of the visible page state.
|
|
10
|
+
*
|
|
11
|
+
* Extracts URL, title, scroll position, active elements, visible panel content,
|
|
12
|
+
* headings, and form state — formatted for LLM consumption (~100-200 tokens).
|
|
13
|
+
*
|
|
14
|
+
* Returns an empty string on any error (non-blocking, fail-safe).
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateVisualSummary(page: Page): Promise<string>;
|
|
17
|
+
//# sourceMappingURL=visual-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-summary.d.ts","sourceRoot":"","sources":["../../src/utils/visual-summary.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA8L3C;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAavE"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Visual Summary - Generates a lightweight text description of visible page state
|
|
4
|
+
*
|
|
5
|
+
* After click/navigate actions, extracts enough context (~100-150 tokens) for
|
|
6
|
+
* LLMs to understand the current page state without requiring follow-up screenshots.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.generateVisualSummary = generateVisualSummary;
|
|
10
|
+
// Script injected into the page to extract visible state in one evaluate call
|
|
11
|
+
const EXTRACT_STATE_SCRIPT = `(() => {
|
|
12
|
+
try {
|
|
13
|
+
const url = location.href;
|
|
14
|
+
const title = document.title;
|
|
15
|
+
|
|
16
|
+
// Scroll position
|
|
17
|
+
const scrollX = Math.round(window.scrollX);
|
|
18
|
+
const scrollY = Math.round(window.scrollY);
|
|
19
|
+
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight || 0;
|
|
20
|
+
const clientHeight = document.documentElement.clientHeight || window.innerHeight || 0;
|
|
21
|
+
|
|
22
|
+
// Active/focused element
|
|
23
|
+
let activeEl = null;
|
|
24
|
+
try {
|
|
25
|
+
const el = document.activeElement;
|
|
26
|
+
if (el && el !== document.body && el !== document.documentElement) {
|
|
27
|
+
const tag = el.tagName.toLowerCase();
|
|
28
|
+
const label =
|
|
29
|
+
el.getAttribute('aria-label') ||
|
|
30
|
+
el.getAttribute('title') ||
|
|
31
|
+
el.getAttribute('name') ||
|
|
32
|
+
(el.textContent || '').trim().slice(0, 40);
|
|
33
|
+
const ariaSelected = el.getAttribute('aria-selected');
|
|
34
|
+
const ariaCurrent = el.getAttribute('aria-current');
|
|
35
|
+
const isActive = el.classList.contains('active');
|
|
36
|
+
activeEl = { tag, label, ariaSelected, ariaCurrent, isActive };
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {}
|
|
39
|
+
|
|
40
|
+
// Visible scrollable panels (role=tabpanel, [class*=panel], [class*=content], main, article)
|
|
41
|
+
const panelSelectors = [
|
|
42
|
+
'[role="tabpanel"]',
|
|
43
|
+
'main',
|
|
44
|
+
'article',
|
|
45
|
+
'[class*="panel"]',
|
|
46
|
+
'[class*="content"]',
|
|
47
|
+
];
|
|
48
|
+
const panels = [];
|
|
49
|
+
const seenPanels = new Set();
|
|
50
|
+
for (const sel of panelSelectors) {
|
|
51
|
+
if (panels.length >= 3) break;
|
|
52
|
+
let found;
|
|
53
|
+
try { found = document.querySelectorAll(sel); } catch (e) { continue; }
|
|
54
|
+
for (const el of found) {
|
|
55
|
+
if (panels.length >= 3) break;
|
|
56
|
+
if (seenPanels.has(el)) continue;
|
|
57
|
+
const rect = el.getBoundingClientRect();
|
|
58
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
59
|
+
if (rect.bottom < 0 || rect.top > window.innerHeight) continue;
|
|
60
|
+
const text = (el.textContent || '').trim().replace(/\\s+/g, ' ').slice(0, 100);
|
|
61
|
+
if (!text) continue;
|
|
62
|
+
seenPanels.add(el);
|
|
63
|
+
panels.push(text);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Active tab/button states (aria-selected, aria-current, .active)
|
|
68
|
+
const activeStates = [];
|
|
69
|
+
try {
|
|
70
|
+
const candidates = document.querySelectorAll(
|
|
71
|
+
'[aria-selected="true"], [aria-current], .active, [class*="tab--active"], [class*="tab-active"]'
|
|
72
|
+
);
|
|
73
|
+
for (const el of candidates) {
|
|
74
|
+
if (activeStates.length >= 5) break;
|
|
75
|
+
const rect = el.getBoundingClientRect();
|
|
76
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
77
|
+
const tag = el.tagName.toLowerCase();
|
|
78
|
+
const text = (el.textContent || '').trim().slice(0, 40);
|
|
79
|
+
const ariaSelected = el.getAttribute('aria-selected');
|
|
80
|
+
const ariaCurrent = el.getAttribute('aria-current');
|
|
81
|
+
const qualifier = ariaSelected === 'true' ? 'aria-selected' : ariaCurrent ? 'aria-current' : 'active';
|
|
82
|
+
if (text) activeStates.push({ tag, text, qualifier });
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {}
|
|
85
|
+
|
|
86
|
+
// Visible form state (inputs, selects, checkboxes)
|
|
87
|
+
const formState = [];
|
|
88
|
+
try {
|
|
89
|
+
const inputs = document.querySelectorAll('input:not([type="hidden"]), select, textarea');
|
|
90
|
+
for (const el of inputs) {
|
|
91
|
+
if (formState.length >= 5) break;
|
|
92
|
+
const rect = el.getBoundingClientRect();
|
|
93
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
94
|
+
if (rect.bottom < 0 || rect.top > window.innerHeight) continue;
|
|
95
|
+
const tag = el.tagName.toLowerCase();
|
|
96
|
+
const type = el.getAttribute('type') || tag;
|
|
97
|
+
const name = el.getAttribute('name') || el.getAttribute('aria-label') || el.getAttribute('placeholder') || '';
|
|
98
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
99
|
+
formState.push({ type, name, checked: el.checked });
|
|
100
|
+
} else if (tag === 'select') {
|
|
101
|
+
const opt = el.options[el.selectedIndex];
|
|
102
|
+
const val = opt ? (opt.text || opt.value).slice(0, 30) : '';
|
|
103
|
+
if (val) formState.push({ type: 'select', name, value: val });
|
|
104
|
+
} else {
|
|
105
|
+
const val = (el.value || '').slice(0, 40);
|
|
106
|
+
if (val) formState.push({ type, name, value: val });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {}
|
|
110
|
+
|
|
111
|
+
// Visible headings for section context
|
|
112
|
+
const headings = [];
|
|
113
|
+
try {
|
|
114
|
+
const hEls = document.querySelectorAll('h1, h2, h3, h4');
|
|
115
|
+
for (const el of hEls) {
|
|
116
|
+
if (headings.length >= 4) break;
|
|
117
|
+
const rect = el.getBoundingClientRect();
|
|
118
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
119
|
+
if (rect.bottom < 0 || rect.top > window.innerHeight) continue;
|
|
120
|
+
const tag = el.tagName.toLowerCase();
|
|
121
|
+
const text = (el.textContent || '').trim().slice(0, 50);
|
|
122
|
+
if (text) headings.push({ tag, text });
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {}
|
|
125
|
+
|
|
126
|
+
return { url, title, scrollX, scrollY, scrollHeight, clientHeight, panels, activeStates, formState, headings, activeEl };
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
})()`;
|
|
131
|
+
/**
|
|
132
|
+
* Format extracted page state into a compact multi-line string.
|
|
133
|
+
*/
|
|
134
|
+
function formatPageState(state) {
|
|
135
|
+
const lines = [];
|
|
136
|
+
// Line 1: URL, title, scroll position
|
|
137
|
+
const scrollPct = state.scrollHeight > 0
|
|
138
|
+
? Math.round((state.scrollY / (state.scrollHeight - state.clientHeight || state.scrollHeight)) * 100)
|
|
139
|
+
: 0;
|
|
140
|
+
const titleShort = state.title.slice(0, 60);
|
|
141
|
+
lines.push(`[Page State] url: ${state.url} | title: "${titleShort}" | scroll: ${state.scrollY}/${state.scrollHeight} (${scrollPct}%)`);
|
|
142
|
+
// Active focused element (deduplicated with activeStates)
|
|
143
|
+
if (state.activeEl) {
|
|
144
|
+
const { tag, label, ariaSelected, ariaCurrent, isActive } = state.activeEl;
|
|
145
|
+
const qualifier = ariaSelected === 'true' ? ' (aria-selected)' : ariaCurrent ? ' (aria-current)' : isActive ? ' (active)' : '';
|
|
146
|
+
if (label)
|
|
147
|
+
lines.push(`[Active] ${tag} "${label}"${qualifier}`);
|
|
148
|
+
}
|
|
149
|
+
// Active tab/button states
|
|
150
|
+
if (state.activeStates.length > 0) {
|
|
151
|
+
const parts = state.activeStates.map(s => `${s.tag} "${s.text}" (${s.qualifier})`);
|
|
152
|
+
lines.push(`[Selected] ${parts.join(' | ')}`);
|
|
153
|
+
}
|
|
154
|
+
// Visible panels
|
|
155
|
+
if (state.panels.length > 0) {
|
|
156
|
+
const parts = state.panels.map((p, i) => `Panel ${i + 1}: "${p}"`);
|
|
157
|
+
lines.push(`[Visible] ${parts.join(' | ')}`);
|
|
158
|
+
}
|
|
159
|
+
// Headings
|
|
160
|
+
if (state.headings.length > 0) {
|
|
161
|
+
const parts = state.headings.map(h => `${h.tag}: "${h.text}"`);
|
|
162
|
+
lines.push(`[Headings] ${parts.join(' | ')}`);
|
|
163
|
+
}
|
|
164
|
+
// Form state
|
|
165
|
+
if (state.formState.length > 0) {
|
|
166
|
+
const parts = state.formState.map(f => {
|
|
167
|
+
if (f.type === 'checkbox' || f.type === 'radio') {
|
|
168
|
+
return `${f.name || f.type}=${f.checked ? 'checked' : 'unchecked'}`;
|
|
169
|
+
}
|
|
170
|
+
return `${f.name || f.type}="${f.value}"`;
|
|
171
|
+
});
|
|
172
|
+
lines.push(`[Form] ${parts.join(' | ')}`);
|
|
173
|
+
}
|
|
174
|
+
return lines.join('\n');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Generate a lightweight text description of the visible page state.
|
|
178
|
+
*
|
|
179
|
+
* Extracts URL, title, scroll position, active elements, visible panel content,
|
|
180
|
+
* headings, and form state — formatted for LLM consumption (~100-200 tokens).
|
|
181
|
+
*
|
|
182
|
+
* Returns an empty string on any error (non-blocking, fail-safe).
|
|
183
|
+
*/
|
|
184
|
+
async function generateVisualSummary(page) {
|
|
185
|
+
try {
|
|
186
|
+
const state = await Promise.race([
|
|
187
|
+
page.evaluate(EXTRACT_STATE_SCRIPT),
|
|
188
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 3000)),
|
|
189
|
+
]);
|
|
190
|
+
if (!state)
|
|
191
|
+
return '';
|
|
192
|
+
return formatPageState(state);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=visual-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-summary.js","sourceRoot":"","sources":["../../src/utils/visual-summary.ts"],"names":[],"mappings":"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"}
|