aether-mcp-server 2.0.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.
@@ -0,0 +1,2317 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CdpBridge = void 0;
4
+ exports.getCdpBridge = getCdpBridge;
5
+ const cdp_client_1 = require("./cdp-client");
6
+ const captcha_solver_1 = require("./captcha-solver");
7
+ const locator_engine_1 = require("./locator-engine");
8
+ const page_snapshot_cache_1 = require("./page-snapshot-cache");
9
+ /**
10
+ * Bridge layer that translates old extension-style commands to CDP commands.
11
+ * This allows the MCP server to work without the Chrome extension.
12
+ */
13
+ class CdpBridge {
14
+ client;
15
+ locator;
16
+ snapshotCache;
17
+ constructor() {
18
+ this.client = (0, cdp_client_1.getCdpClient)();
19
+ this.locator = new locator_engine_1.LocatorEngine(this.client);
20
+ this.snapshotCache = new page_snapshot_cache_1.PageSnapshotCache(this.client, this.locator);
21
+ }
22
+ async ensureConnected() {
23
+ if (!this.client.isConnected()) {
24
+ // Try to connect to existing Chrome on default port
25
+ try {
26
+ await this.client.connect(9222);
27
+ }
28
+ catch {
29
+ // Chrome not running, launch it
30
+ await this.client.launch({ headless: false });
31
+ }
32
+ }
33
+ }
34
+ async applyCognitivePause(type) {
35
+ let delay = 150 + Math.random() * 250; // base delay (150-400ms)
36
+ if (type === "type" || type === "fill") {
37
+ delay += 150 + Math.random() * 250; // typing prep delay (300-650ms total)
38
+ }
39
+ else if (type === "click") {
40
+ delay += 50 + Math.random() * 150; // click prep delay (200-550ms total)
41
+ }
42
+ console.error(`[Bridge] Emulating cognitive pause: ${Math.round(delay)}ms for action type: ${type}`);
43
+ await new Promise((r) => setTimeout(r, delay));
44
+ }
45
+ async sendCommand(method, params = {}) {
46
+ // Cognitive pause mapping
47
+ const interactionTypes = {
48
+ click_by_ref: "click",
49
+ click_by_selector: "click",
50
+ fill_by_selector: "fill",
51
+ press_key: "key",
52
+ key_combo: "key",
53
+ click_text: "click",
54
+ click_role: "click",
55
+ fill_label: "fill",
56
+ click: "click",
57
+ click_element: "click",
58
+ click_element_by_selector: "click",
59
+ type: "type",
60
+ fill: "fill",
61
+ select: "click",
62
+ check: "click",
63
+ hover: "click",
64
+ drag_and_drop: "click"
65
+ };
66
+ if (interactionTypes[method]) {
67
+ await this.applyCognitivePause(interactionTypes[method]);
68
+ }
69
+ switch (method) {
70
+ case "browser_status":
71
+ return this.browserStatus(params);
72
+ case "snapshot_compact":
73
+ await this.ensureConnected();
74
+ return this.snapshotCompact(params);
75
+ case "list_interactive_elements":
76
+ await this.ensureConnected();
77
+ return this.listInteractiveElements(params);
78
+ case "click_by_ref":
79
+ await this.ensureConnected();
80
+ return this.clickByRef(params);
81
+ case "click_by_selector":
82
+ await this.ensureConnected();
83
+ return this.clickBySelector(params);
84
+ case "fill_by_selector":
85
+ await this.ensureConnected();
86
+ return this.fillBySelector(params);
87
+ case "wait_for_selector":
88
+ await this.ensureConnected();
89
+ return this.waitForSelectorCompact(params);
90
+ case "wait_for_text":
91
+ await this.ensureConnected();
92
+ return this.waitForText(params);
93
+ case "get_network_errors":
94
+ await this.ensureConnected();
95
+ return this.getNetworkErrors(params);
96
+ case "detect_captcha":
97
+ await this.ensureConnected();
98
+ return this.detectCaptcha();
99
+ case "solve_captcha":
100
+ await this.ensureConnected();
101
+ return this.solveCaptchaAction(params);
102
+ case "browser_intent":
103
+ await this.ensureConnected();
104
+ await this.ensureNoCaptcha("browser_intent");
105
+ return this.browserIntent(params);
106
+ case "get_logs":
107
+ await this.ensureConnected();
108
+ return this.getLogs(params);
109
+ case "press_key":
110
+ case "key_combo":
111
+ await this.ensureConnected();
112
+ await this.ensureNoCaptcha("press_key");
113
+ return this.pressKey(params);
114
+ case "click_text":
115
+ await this.ensureConnected();
116
+ await this.ensureNoCaptcha("click_text");
117
+ return this.clickText(params);
118
+ case "click_role":
119
+ await this.ensureConnected();
120
+ await this.ensureNoCaptcha("click_role");
121
+ return this.clickRole(params);
122
+ case "fill_label":
123
+ await this.ensureConnected();
124
+ await this.ensureNoCaptcha("fill_label");
125
+ return this.fillLabel(params);
126
+ case "element_at_point":
127
+ await this.ensureConnected();
128
+ return this.elementAtPoint(params);
129
+ case "connect":
130
+ return this.connect(params);
131
+ case "get_state":
132
+ await this.ensureConnected();
133
+ return this.getState(params);
134
+ case "navigate":
135
+ await this.ensureConnected();
136
+ return this.navigate(params);
137
+ case "click":
138
+ await this.ensureConnected();
139
+ await this.ensureNoCaptcha("click");
140
+ return this.click(params);
141
+ case "click_element":
142
+ await this.ensureConnected();
143
+ await this.ensureNoCaptcha("click_element");
144
+ return this.clickElement(params);
145
+ case "click_element_by_selector":
146
+ await this.ensureConnected();
147
+ await this.ensureNoCaptcha("click_element_by_selector");
148
+ return this.clickElementBySelector(params);
149
+ case "type":
150
+ await this.ensureConnected();
151
+ await this.ensureNoCaptcha("type");
152
+ return this.type(params);
153
+ case "evaluate":
154
+ await this.ensureConnected();
155
+ return this.evaluate(params);
156
+ case "screenshot":
157
+ case "screenshot_region":
158
+ await this.ensureConnected();
159
+ return this.screenshot(params);
160
+ case "scroll":
161
+ await this.ensureConnected();
162
+ return this.scroll(params);
163
+ case "wait":
164
+ await this.ensureConnected();
165
+ return this.wait(params);
166
+ case "cdp_command":
167
+ await this.ensureConnected();
168
+ return this.cdpCommand(params);
169
+ case "get_dom_snapshot":
170
+ await this.ensureConnected();
171
+ return this.getDomSnapshot(params);
172
+ case "get_tabs":
173
+ await this.ensureConnected();
174
+ return this.getTabs(params);
175
+ case "new_tab":
176
+ await this.ensureConnected();
177
+ return this.newTab(params);
178
+ case "switch_tab":
179
+ await this.ensureConnected();
180
+ return this.switchTab(params);
181
+ case "close_tab":
182
+ await this.ensureConnected();
183
+ return this.closeTab(params);
184
+ case "start_screencast":
185
+ await this.ensureConnected();
186
+ return this.startScreencast(params);
187
+ case "stop_screencast":
188
+ await this.ensureConnected();
189
+ return this.stopScreencast(params);
190
+ case "record_session":
191
+ await this.ensureConnected();
192
+ return this.recordSession(params);
193
+ case "sample_visual_frames":
194
+ await this.ensureConnected();
195
+ return this.sampleVisualFrames(params);
196
+ case "start_tracing":
197
+ await this.ensureConnected();
198
+ return this.startTracing(params);
199
+ case "stop_tracing":
200
+ await this.ensureConnected();
201
+ return this.stopTracing(params);
202
+ case "get_performance_metrics":
203
+ await this.ensureConnected();
204
+ return this.getPerformanceMetrics(params);
205
+ case "hover":
206
+ await this.ensureConnected();
207
+ await this.ensureNoCaptcha("hover");
208
+ return this.hover(params);
209
+ case "drag_and_drop":
210
+ await this.ensureConnected();
211
+ await this.ensureNoCaptcha("drag_and_drop");
212
+ return this.dragAndDrop(params);
213
+ // ==================== MISSING ACT TOOL ACTIONS ====================
214
+ case "fill":
215
+ await this.ensureConnected();
216
+ await this.ensureNoCaptcha("fill");
217
+ return this.fillInput(params);
218
+ case "select":
219
+ await this.ensureConnected();
220
+ await this.ensureNoCaptcha("select");
221
+ return this.selectOption(params);
222
+ case "check":
223
+ await this.ensureConnected();
224
+ await this.ensureNoCaptcha("check");
225
+ return this.checkElement(params);
226
+ case "get_tree":
227
+ await this.ensureConnected();
228
+ return this.getAccessibilityTree(params);
229
+ case "get_dom_tree":
230
+ await this.ensureConnected();
231
+ return this.getDOMTree(params);
232
+ case "assert":
233
+ await this.ensureConnected();
234
+ return this.assertCondition(params);
235
+ case "get_cookies":
236
+ await this.ensureConnected();
237
+ return this.getCookies(params);
238
+ case "set_cookie":
239
+ await this.ensureConnected();
240
+ return this.setCookie(params);
241
+ case "clear_cache":
242
+ await this.ensureConnected();
243
+ return this.clearCache(params);
244
+ case "set_geolocation":
245
+ await this.ensureConnected();
246
+ return this.setGeolocation(params);
247
+ case "set_timezone":
248
+ await this.ensureConnected();
249
+ return this.setTimezone(params);
250
+ case "emulate_network":
251
+ await this.ensureConnected();
252
+ return this.emulateNetworkConditions(params);
253
+ case "print_pdf":
254
+ await this.ensureConnected();
255
+ return this.printPDF(params);
256
+ case "highlight_elements":
257
+ await this.ensureConnected();
258
+ return this.highlightElements(params);
259
+ case "verify_ui_state":
260
+ await this.ensureConnected();
261
+ return this.verifyUIState(params);
262
+ case "get_dom_storage":
263
+ await this.ensureConnected();
264
+ return this.getDOMStorage(params);
265
+ case "get_network_traffic":
266
+ await this.ensureConnected();
267
+ return this.getNetworkTraffic(params);
268
+ case "get_network_response":
269
+ await this.ensureConnected();
270
+ return this.getNetworkResponse(params);
271
+ case "mock_network_request":
272
+ await this.ensureConnected();
273
+ return this.mockNetworkRequest(params);
274
+ case "get_computed_style":
275
+ await this.ensureConnected();
276
+ return this.getComputedStyle(params);
277
+ case "get_event_listeners":
278
+ await this.ensureConnected();
279
+ return this.getEventListeners(params);
280
+ case "get_screencast_frames":
281
+ await this.ensureConnected();
282
+ return this.getScreencastFrames(params);
283
+ case "upload_file":
284
+ await this.ensureConnected();
285
+ await this.ensureNoCaptcha("upload_file");
286
+ return this.uploadFile(params);
287
+ case "configure":
288
+ await this.ensureConnected();
289
+ return this.configureBrowser(params);
290
+ // ==================== AGENT-CENTRIC APIs ====================
291
+ case "agent_action":
292
+ await this.ensureConnected();
293
+ await this.ensureNoCaptcha("agent_action");
294
+ return this.agentAction(params);
295
+ case "smart_navigate":
296
+ await this.ensureConnected();
297
+ return this.smartNavigate(params);
298
+ case "observe_and_act":
299
+ await this.ensureConnected();
300
+ await this.ensureNoCaptcha("observe_and_act");
301
+ return this.observeAndAct(params);
302
+ case "agent_form_fill":
303
+ await this.ensureConnected();
304
+ await this.ensureNoCaptcha("agent_form_fill");
305
+ return this.agentFormFill(params);
306
+ case "page_snapshot":
307
+ await this.ensureConnected();
308
+ return this.pageSnapshot(params);
309
+ default:
310
+ await this.ensureConnected();
311
+ // Try as raw CDP command
312
+ return this.client.sendCommand(method, params);
313
+ }
314
+ }
315
+ async connect(params) {
316
+ const port = params.port || 9222;
317
+ await this.client.connect(port);
318
+ return "Connected to browser";
319
+ }
320
+ async browserStatus(params) {
321
+ const connected = this.client.isConnected();
322
+ const activeTarget = this.client.getActiveTarget();
323
+ let targets;
324
+ if (connected && params.includeTargets) {
325
+ targets = await this.client.getTabs().catch(() => []);
326
+ }
327
+ return {
328
+ connected,
329
+ activeTarget: activeTarget ? {
330
+ id: activeTarget.id,
331
+ type: activeTarget.type,
332
+ title: activeTarget.title,
333
+ url: activeTarget.url
334
+ } : null,
335
+ targets
336
+ };
337
+ }
338
+ async snapshotCompact(params) {
339
+ return this.snapshotCache.compact({
340
+ maxElements: params.maxElements ?? 30,
341
+ includeText: params.includeText !== false,
342
+ });
343
+ }
344
+ async listInteractiveElements(params) {
345
+ const maxElements = Math.max(0, Math.min(Number(params.maxElements ?? 50), 200));
346
+ const snapshot = await this.snapshotCache.compact({
347
+ maxElements,
348
+ includeText: true,
349
+ withOverlay: !!params.withOverlay,
350
+ });
351
+ return {
352
+ count: snapshot.elements.length,
353
+ cache: snapshot.cache,
354
+ elements: snapshot.elements
355
+ };
356
+ }
357
+ async getCompactElements(maxElements, includeText, withOverlay) {
358
+ const result = await this.client.sendCommand("Runtime.evaluate", {
359
+ expression: `
360
+ (function() {
361
+ const max = ${JSON.stringify(maxElements)};
362
+ const includeText = ${JSON.stringify(includeText)};
363
+ const selectors = [
364
+ 'a[href]', 'button', 'input:not([type="hidden"])', 'select', 'textarea',
365
+ '[onclick]', '[role="button"]', '[role="link"]', '[role="checkbox"]',
366
+ '[tabindex]:not([tabindex="-1"])', 'label', 'summary'
367
+ ].join(', ');
368
+
369
+ function cssPath(el) {
370
+ if (el.id) return '#' + CSS.escape(el.id);
371
+ const path = [];
372
+ while (el && el.nodeType === Node.ELEMENT_NODE && el !== document.body) {
373
+ let selector = el.nodeName.toLowerCase();
374
+ if (el.classList && el.classList.length) {
375
+ selector += '.' + Array.from(el.classList).slice(0, 2).map(c => CSS.escape(c)).join('.');
376
+ }
377
+ const parent = el.parentElement;
378
+ if (parent) {
379
+ const siblings = Array.from(parent.children).filter(child => child.nodeName === el.nodeName);
380
+ if (siblings.length > 1) selector += ':nth-of-type(' + (siblings.indexOf(el) + 1) + ')';
381
+ }
382
+ path.unshift(selector);
383
+ el = parent;
384
+ }
385
+ return path.length ? path.join(' > ') : '';
386
+ }
387
+
388
+ return Array.from(document.querySelectorAll(selectors)).map((el, index) => {
389
+ const rect = el.getBoundingClientRect();
390
+ const computed = window.getComputedStyle(el);
391
+ const visible = computed.display !== 'none' && computed.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
392
+ if (!visible) return null;
393
+ const selector = cssPath(el);
394
+ if (!selector) return null;
395
+ const text = ((el.innerText || el.textContent || el.getAttribute('aria-label') || el.getAttribute('placeholder') || '') + '').trim().replace(/\\s+/g, ' ').substring(0, 120);
396
+ return {
397
+ ref: 'css:' + selector,
398
+ index: index + 1,
399
+ tag: el.tagName.toLowerCase(),
400
+ role: el.getAttribute('role') || '',
401
+ type: el.getAttribute('type') || '',
402
+ name: el.getAttribute('name') || '',
403
+ text: includeText ? text : undefined,
404
+ bounds: {
405
+ x: Math.round(rect.left),
406
+ y: Math.round(rect.top),
407
+ width: Math.round(rect.width),
408
+ height: Math.round(rect.height)
409
+ }
410
+ };
411
+ }).filter(Boolean).slice(0, max);
412
+ })()
413
+ `,
414
+ returnByValue: true,
415
+ awaitPromise: true
416
+ });
417
+ const elements = result.result?.value || [];
418
+ if (withOverlay && elements.length > 0) {
419
+ await this.client.getInteractiveElements(true).catch(() => ({ elements: [], somInjected: false }));
420
+ }
421
+ return elements;
422
+ }
423
+ async clickByRef(params) {
424
+ const ref = String(params.ref || "");
425
+ if (!ref)
426
+ throw new Error("ref required");
427
+ if (ref.startsWith("css:")) {
428
+ return this.clickBySelector({ selector: ref.slice(4), timeout: params.timeout });
429
+ }
430
+ if (ref.startsWith("point:")) {
431
+ const [x, y] = ref.slice(6).split(",").map(Number);
432
+ if (!Number.isFinite(x) || !Number.isFinite(y))
433
+ throw new Error(`Invalid point ref: ${ref}`);
434
+ const before = await this.captureActionFacts();
435
+ await this.client.click(x, y);
436
+ this.snapshotCache.invalidate("click_by_ref");
437
+ const after = await this.captureActionFacts();
438
+ return { success: true, ref, facts: this.diffActionFacts(before, after) };
439
+ }
440
+ if (ref.startsWith("@") || ref.startsWith("som:")) {
441
+ const id = ref.replace(/^som:/, "").replace(/^@/, "");
442
+ await this.clickElement({ id });
443
+ return { success: true, ref };
444
+ }
445
+ throw new Error(`Unsupported element ref: ${ref}`);
446
+ }
447
+ async clickBySelector(params) {
448
+ const selector = params.selector;
449
+ if (!selector)
450
+ throw new Error("selector required");
451
+ const found = await this.client.waitForSelector(selector, params.timeout || 5000, { visible: params.visible !== false, stable: params.stable === true });
452
+ if (!found)
453
+ return { success: false, selector, message: "Selector not found before timeout" };
454
+ const before = await this.captureActionFacts();
455
+ await this.clickElementBySelector({ selector });
456
+ this.snapshotCache.invalidate("click_by_selector");
457
+ const after = await this.captureActionFacts(selector);
458
+ return { success: true, selector, facts: this.diffActionFacts(before, after) };
459
+ }
460
+ async fillBySelector(params) {
461
+ const selector = params.selector;
462
+ const value = params.value ?? "";
463
+ if (!selector)
464
+ throw new Error("selector required");
465
+ const found = await this.client.waitForSelector(selector, params.timeout || 5000, { visible: params.visible !== false, stable: params.stable === true });
466
+ if (!found)
467
+ return { success: false, selector, message: "Selector not found before timeout" };
468
+ await this.client.moveMouseToSelector(selector).catch(() => { });
469
+ const focused = await this.client.evaluate(`
470
+ (function() {
471
+ const el = document.querySelector(${JSON.stringify(selector)});
472
+ if (!el) return false;
473
+ el.focus();
474
+ if ('value' in el) {
475
+ el.value = '';
476
+ el.dispatchEvent(new Event('input', { bubbles: true }));
477
+ el.dispatchEvent(new Event('change', { bubbles: true }));
478
+ }
479
+ return true;
480
+ })()
481
+ `);
482
+ if (!focused)
483
+ return { success: false, selector, message: "Selector could not be focused" };
484
+ const before = await this.captureActionFacts(selector);
485
+ await this.client.typeText(String(value));
486
+ this.snapshotCache.invalidate("fill_by_selector");
487
+ const after = await this.captureActionFacts(selector);
488
+ return { success: true, selector, length: String(value).length, facts: this.diffActionFacts(before, after) };
489
+ }
490
+ async waitForSelectorCompact(params) {
491
+ const selector = params.selector;
492
+ if (!selector)
493
+ throw new Error("selector required");
494
+ const found = await this.client.waitForSelector(selector, params.timeout || 5000, { visible: params.visible === true, stable: params.stable === true });
495
+ return { success: found, selector };
496
+ }
497
+ async getLogs(params) {
498
+ const limit = Math.max(1, Math.min(Number(params.limit ?? 50), 100));
499
+ const logs = await this.client.getConsoleLogs(limit);
500
+ return { count: logs.length, logs };
501
+ }
502
+ async pressKey(params) {
503
+ const key = String(params.key || params.value || "");
504
+ if (!key)
505
+ throw new Error("key required");
506
+ const modifiers = Array.isArray(params.modifiers) ? params.modifiers.map(String) : [];
507
+ const before = await this.captureActionFacts();
508
+ await this.client.pressKey(key, modifiers);
509
+ this.snapshotCache.invalidate("press_key");
510
+ const after = await this.captureActionFacts();
511
+ return { success: true, key, modifiers, facts: this.diffActionFacts(before, after) };
512
+ }
513
+ async clickText(params) {
514
+ const resolved = await this.locator.resolve({
515
+ target: params.text || params.value || params.target,
516
+ role: params.role,
517
+ timeout: params.timeout || 5000,
518
+ includeCandidates: params.includeCandidates
519
+ });
520
+ if (!resolved.success)
521
+ return resolved;
522
+ const before = await this.captureActionFacts();
523
+ await this.clickResolvedLocator(resolved.candidate);
524
+ this.snapshotCache.invalidate("click_text");
525
+ const after = await this.captureActionFacts(resolved.selector);
526
+ return { success: true, selector: resolved.selector, ref: resolved.ref, matchedBy: resolved.matchedBy, confidence: resolved.confidence, facts: this.diffActionFacts(before, after) };
527
+ }
528
+ async clickRole(params) {
529
+ const resolved = await this.locator.resolve({
530
+ target: params.name || params.text || params.target || "",
531
+ role: params.role,
532
+ timeout: params.timeout || 5000,
533
+ includeCandidates: params.includeCandidates
534
+ });
535
+ if (!resolved.success)
536
+ return resolved;
537
+ const before = await this.captureActionFacts();
538
+ await this.clickResolvedLocator(resolved.candidate);
539
+ this.snapshotCache.invalidate("click_role");
540
+ const after = await this.captureActionFacts(resolved.selector);
541
+ return { success: true, selector: resolved.selector, ref: resolved.ref, matchedBy: resolved.matchedBy, confidence: resolved.confidence, facts: this.diffActionFacts(before, after) };
542
+ }
543
+ async fillLabel(params) {
544
+ const resolved = await this.locator.resolve({
545
+ target: params.label || params.target,
546
+ role: params.role || "textbox",
547
+ timeout: params.timeout || 5000,
548
+ includeCandidates: params.includeCandidates
549
+ });
550
+ if (!resolved.success)
551
+ return resolved;
552
+ if (resolved.candidate?.scope === "document" && resolved.candidate.framePath.length === 0 && resolved.candidate.shadowDepth === 0) {
553
+ return this.fillBySelector({ selector: resolved.selector, value: params.value ?? "", timeout: params.timeout || 5000 });
554
+ }
555
+ const before = await this.captureActionFacts();
556
+ await this.locator.focusAndClear(resolved.candidate);
557
+ await this.client.typeText(String(params.value ?? ""));
558
+ this.snapshotCache.invalidate("fill_label");
559
+ const after = await this.captureActionFacts();
560
+ return { success: true, selector: resolved.selector, ref: resolved.ref, matchedBy: resolved.matchedBy, confidence: resolved.confidence, length: String(params.value ?? "").length, facts: this.diffActionFacts(before, after) };
561
+ }
562
+ async clickResolvedLocator(candidate) {
563
+ if (!candidate)
564
+ throw new Error("Resolved locator missing candidate details");
565
+ if (candidate.scope === "document" && candidate.framePath.length === 0 && candidate.shadowDepth === 0 && candidate.selector) {
566
+ await this.clickElementBySelector({ selector: candidate.selector });
567
+ return;
568
+ }
569
+ await this.locator.click(candidate);
570
+ }
571
+ async elementAtPoint(params) {
572
+ const x = Number(params.x ?? String(params.coordinate || "").split(",")[0]);
573
+ const y = Number(params.y ?? String(params.coordinate || "").split(",")[1]);
574
+ if (!Number.isFinite(x) || !Number.isFinite(y))
575
+ throw new Error("x/y or coordinate required");
576
+ return await this.client.evaluate(`
577
+ (function() {
578
+ const el = document.elementFromPoint(${JSON.stringify(x)}, ${JSON.stringify(y)});
579
+ if (!el) return { found: false };
580
+ const rect = el.getBoundingClientRect();
581
+ function cssPath(node) {
582
+ if (node.id) return '#' + CSS.escape(node.id);
583
+ const path = [];
584
+ while (node && node.nodeType === Node.ELEMENT_NODE && node !== document.body) {
585
+ let selector = node.nodeName.toLowerCase();
586
+ if (node.classList && node.classList.length) {
587
+ selector += '.' + Array.from(node.classList).slice(0, 2).map(c => CSS.escape(c)).join('.');
588
+ }
589
+ const parent = node.parentElement;
590
+ if (parent) {
591
+ const siblings = Array.from(parent.children).filter(child => child.nodeName === node.nodeName);
592
+ if (siblings.length > 1) selector += ':nth-of-type(' + (siblings.indexOf(node) + 1) + ')';
593
+ }
594
+ path.unshift(selector);
595
+ node = parent;
596
+ }
597
+ return path.join(' > ');
598
+ }
599
+ return {
600
+ found: true,
601
+ selector: cssPath(el),
602
+ tag: el.tagName.toLowerCase(),
603
+ text: String(el.innerText || el.textContent || el.getAttribute('aria-label') || '').trim().replace(/\\s+/g, ' ').substring(0, 160),
604
+ role: el.getAttribute('role') || '',
605
+ bounds: { x: Math.round(rect.left), y: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height) }
606
+ };
607
+ })()
608
+ `);
609
+ }
610
+ async waitForText(params) {
611
+ const text = String(params.text || "");
612
+ if (!text)
613
+ throw new Error("text required");
614
+ const timeout = params.timeout || 5000;
615
+ const start = Date.now();
616
+ while (Date.now() - start < timeout) {
617
+ const found = await this.client.evaluate(`
618
+ (document.body && document.body.innerText || '').includes(${JSON.stringify(text)})
619
+ `).catch(() => false);
620
+ if (found)
621
+ return { success: true, text };
622
+ await new Promise(r => setTimeout(r, 200));
623
+ }
624
+ return { success: false, text, message: "Text not found before timeout" };
625
+ }
626
+ async getNetworkErrors(params) {
627
+ const limit = Math.max(1, Math.min(Number(params.limit ?? 20), 100));
628
+ const errors = (await this.client.getNetworkTraffic())
629
+ .filter((entry) => entry.type === "error" || entry.status >= 400)
630
+ .slice(-limit);
631
+ return {
632
+ count: errors.length,
633
+ errors
634
+ };
635
+ }
636
+ async detectCaptcha() {
637
+ return await this.client.evaluate(`
638
+ (function() {
639
+ const selectors = [
640
+ 'iframe[src*="recaptcha"]',
641
+ 'iframe[src*="hcaptcha"]',
642
+ 'iframe[src*="challenges.cloudflare.com"]',
643
+ 'iframe[src*="arkoselabs"]',
644
+ 'iframe[src*="funcaptcha"]',
645
+ '[class*="g-recaptcha"]',
646
+ '[class*="h-captcha"]',
647
+ '[data-sitekey]',
648
+ '[id*="captcha" i]',
649
+ '[class*="captcha" i]',
650
+ '[aria-label*="captcha" i]'
651
+ ];
652
+ const textPatterns = [
653
+ /captcha/i,
654
+ /i am not a robot/i,
655
+ /verify you are human/i,
656
+ /verify that you are human/i,
657
+ /security check/i,
658
+ /human verification/i,
659
+ /cloudflare.*verify/i
660
+ ];
661
+
662
+ function visible(el) {
663
+ const rect = el.getBoundingClientRect();
664
+ const style = window.getComputedStyle(el);
665
+ return style.display !== 'none' &&
666
+ style.visibility !== 'hidden' &&
667
+ rect.width > 0 &&
668
+ rect.height > 0;
669
+ }
670
+
671
+ const selectorMatches = selectors.flatMap((selector) =>
672
+ Array.from(document.querySelectorAll(selector))
673
+ .filter(visible)
674
+ .slice(0, 5)
675
+ .map((el) => {
676
+ const rect = el.getBoundingClientRect();
677
+ return {
678
+ selector,
679
+ tag: el.tagName.toLowerCase(),
680
+ text: String(el.innerText || el.textContent || el.getAttribute('aria-label') || '').trim().replace(/\\s+/g, ' ').substring(0, 160),
681
+ src: el.getAttribute('src') || '',
682
+ bounds: { x: Math.round(rect.left), y: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height) }
683
+ };
684
+ })
685
+ );
686
+
687
+ const bodyText = String(document.body?.innerText || '').replace(/\\s+/g, ' ').substring(0, 5000);
688
+ const textMatches = textPatterns
689
+ .filter((pattern) => pattern.test(bodyText))
690
+ .map((pattern) => pattern.toString());
691
+
692
+ const detected = selectorMatches.length > 0 || textMatches.length > 0;
693
+ return {
694
+ detected,
695
+ captchaRequired: detected,
696
+ message: detected ? 'CAPTCHA detected. Manual solve required before continuing.' : 'No CAPTCHA detected.',
697
+ matches: selectorMatches,
698
+ textMatches,
699
+ url: window.location.href,
700
+ title: document.title
701
+ };
702
+ })()
703
+ `).catch((error) => ({
704
+ detected: false,
705
+ captchaRequired: false,
706
+ message: `CAPTCHA detection failed: ${error.message}`
707
+ }));
708
+ }
709
+ async ensureNoCaptcha(action) {
710
+ const result = await this.detectCaptcha();
711
+ if (result.detected) {
712
+ const error = new Error("CAPTCHA detected. Manual solve required before continuing.");
713
+ error.captcha = {
714
+ ...result,
715
+ blockedAction: action
716
+ };
717
+ throw error;
718
+ }
719
+ }
720
+ async solveCaptchaAction(params) {
721
+ const pageUrl = params.pageUrl || await this.client.evaluate("window.location.href").catch(() => "");
722
+ const opts = {
723
+ useService: params.useService,
724
+ service: params.service,
725
+ apiKey: params.apiKey,
726
+ timeout: params.timeout,
727
+ pollInterval: params.pollInterval,
728
+ waitAfterClick: params.waitAfterClick,
729
+ };
730
+ const evaluate = (script) => this.client.evaluate(script);
731
+ const sendCommand = (method, p) => this.client.sendCommand(method, p);
732
+ const mouse = this.client.getMousePosition();
733
+ return await (0, captcha_solver_1.detectAndSolve)(evaluate, sendCommand, pageUrl, mouse, opts);
734
+ }
735
+ async browserIntent(params) {
736
+ const intent = String(params.intent || "").toLowerCase();
737
+ const timeout = params.timeout || 7000;
738
+ if (intent === "navigate") {
739
+ const url = params.value || params.target;
740
+ if (!url)
741
+ throw new Error("value or target required for navigate intent");
742
+ await this.navigate({ url: String(url), timeout });
743
+ return this.intentResult(true, intent, undefined, { url });
744
+ }
745
+ if (intent === "inspect") {
746
+ const snapshot = await this.snapshotCompact({ maxElements: params.maxElements ?? 30, includeText: true });
747
+ return this.intentResult(true, intent, undefined, snapshot);
748
+ }
749
+ if (intent === "wait_for") {
750
+ const expected = params.value || params.target;
751
+ if (!expected)
752
+ throw new Error("value or target required for wait_for intent");
753
+ const result = await this.waitForText({ text: expected, timeout });
754
+ return this.intentResult(result.success, intent, undefined, result);
755
+ }
756
+ const resolved = await this.locator.resolve({
757
+ target: params.target,
758
+ role: params.role,
759
+ timeout,
760
+ includeCandidates: params.includeCandidates
761
+ });
762
+ if (!resolved.success) {
763
+ return this.intentResult(false, intent, undefined, {
764
+ message: resolved.message,
765
+ candidates: params.includeCandidates ? resolved.candidates : undefined
766
+ });
767
+ }
768
+ const selector = resolved.selector;
769
+ if (intent === "click") {
770
+ await this.clickResolvedLocator(resolved.candidate);
771
+ }
772
+ else if (intent === "fill") {
773
+ if (resolved.candidate?.scope === "document" && resolved.candidate.framePath.length === 0 && resolved.candidate.shadowDepth === 0) {
774
+ await this.fillBySelector({ selector, value: params.value ?? "", timeout });
775
+ }
776
+ else {
777
+ await this.locator.focusAndClear(resolved.candidate);
778
+ await this.client.typeText(String(params.value ?? ""));
779
+ }
780
+ }
781
+ else if (intent === "select") {
782
+ await this.selectOption({ selector, value: params.value ?? "" });
783
+ }
784
+ else if (intent === "check") {
785
+ await this.checkElement({ selector });
786
+ }
787
+ else {
788
+ throw new Error(`Unsupported browser intent: ${intent}`);
789
+ }
790
+ this.snapshotCache.invalidate(`browser_intent:${intent}`);
791
+ let verification = undefined;
792
+ if (params.verify) {
793
+ verification = await this.waitForText({ text: params.verify, timeout }).catch((error) => ({
794
+ success: false,
795
+ error: error.message
796
+ }));
797
+ }
798
+ return this.intentResult(true, intent, resolved, {
799
+ selector,
800
+ ref: resolved.ref || (selector ? `css:${selector}` : undefined),
801
+ verification,
802
+ candidates: params.includeCandidates ? resolved.candidates : undefined
803
+ });
804
+ }
805
+ intentResult(success, intent, resolved, extra = {}) {
806
+ return {
807
+ success,
808
+ intent,
809
+ target: resolved?.target,
810
+ matchedBy: resolved?.matchedBy,
811
+ confidence: resolved?.confidence,
812
+ ...extra
813
+ };
814
+ }
815
+ async captureActionFacts(selector) {
816
+ return await this.client.evaluate(`
817
+ (function() {
818
+ const selector = ${JSON.stringify(selector || "")};
819
+ const active = document.activeElement;
820
+ const target = selector ? document.querySelector(selector) : active;
821
+ const visibleErrors = Array.from(document.querySelectorAll('[role="alert"], .error, .errors, [aria-invalid="true"]'))
822
+ .filter((el) => {
823
+ const rect = el.getBoundingClientRect();
824
+ const style = window.getComputedStyle(el);
825
+ return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
826
+ })
827
+ .map((el) => String(el.innerText || el.textContent || el.getAttribute('aria-label') || '').trim().replace(/\\s+/g, ' '))
828
+ .filter(Boolean)
829
+ .slice(0, 5);
830
+
831
+ function describe(el) {
832
+ if (!el) return null;
833
+ return {
834
+ tag: el.tagName.toLowerCase(),
835
+ id: el.id || '',
836
+ name: el.getAttribute('name') || '',
837
+ role: el.getAttribute('role') || '',
838
+ type: el.getAttribute('type') || '',
839
+ value: 'value' in el ? String(el.value || '') : '',
840
+ checked: 'checked' in el ? !!el.checked : undefined,
841
+ selectedIndex: 'selectedIndex' in el ? el.selectedIndex : undefined,
842
+ text: String(el.innerText || el.textContent || '').trim().replace(/\\s+/g, ' ').substring(0, 160)
843
+ };
844
+ }
845
+
846
+ return {
847
+ url: window.location.href,
848
+ title: document.title,
849
+ readyState: document.readyState,
850
+ focused: describe(active),
851
+ target: describe(target),
852
+ visibleErrors
853
+ };
854
+ })()
855
+ `).catch(() => ({}));
856
+ }
857
+ diffActionFacts(before, after) {
858
+ return {
859
+ urlChanged: before?.url !== after?.url,
860
+ titleChanged: before?.title !== after?.title,
861
+ focused: after?.focused,
862
+ target: after?.target,
863
+ valueChanged: before?.target?.value !== after?.target?.value,
864
+ checkedChanged: before?.target?.checked !== after?.target?.checked,
865
+ selectedIndexChanged: before?.target?.selectedIndex !== after?.target?.selectedIndex,
866
+ visibleErrors: after?.visibleErrors || []
867
+ };
868
+ }
869
+ async resolveNaturalTarget(params) {
870
+ const target = String(params.target || "").trim();
871
+ const role = params.role ? String(params.role).toLowerCase() : "";
872
+ const timeout = params.timeout || 7000;
873
+ const start = Date.now();
874
+ if (!target && !role) {
875
+ return { success: false, message: "target or role required" };
876
+ }
877
+ while (Date.now() - start < timeout) {
878
+ const result = await this.client.sendCommand("Runtime.evaluate", {
879
+ expression: `
880
+ (function() {
881
+ const target = ${JSON.stringify(target)};
882
+ const targetLower = target.toLowerCase();
883
+ const roleHint = ${JSON.stringify(role)};
884
+ const selectors = [
885
+ 'a[href]', 'button', 'input:not([type="hidden"])', 'select', 'textarea',
886
+ '[onclick]', '[role]', '[tabindex]:not([tabindex="-1"])', 'label', 'summary'
887
+ ].join(', ');
888
+
889
+ function cssPath(el) {
890
+ if (el.id) return '#' + CSS.escape(el.id);
891
+ const path = [];
892
+ while (el && el.nodeType === Node.ELEMENT_NODE && el !== document.body) {
893
+ let selector = el.nodeName.toLowerCase();
894
+ if (el.classList && el.classList.length) {
895
+ selector += '.' + Array.from(el.classList).slice(0, 2).map(c => CSS.escape(c)).join('.');
896
+ }
897
+ const parent = el.parentElement;
898
+ if (parent) {
899
+ const siblings = Array.from(parent.children).filter(child => child.nodeName === el.nodeName);
900
+ if (siblings.length > 1) selector += ':nth-of-type(' + (siblings.indexOf(el) + 1) + ')';
901
+ }
902
+ path.unshift(selector);
903
+ el = parent;
904
+ }
905
+ return path.length ? path.join(' > ') : '';
906
+ }
907
+
908
+ function visible(el) {
909
+ const rect = el.getBoundingClientRect();
910
+ const computed = window.getComputedStyle(el);
911
+ return computed.display !== 'none' &&
912
+ computed.visibility !== 'hidden' &&
913
+ computed.opacity !== '0' &&
914
+ rect.width > 0 &&
915
+ rect.height > 0;
916
+ }
917
+
918
+ function inferRole(el) {
919
+ const explicit = (el.getAttribute('role') || '').toLowerCase();
920
+ if (explicit) return explicit;
921
+ const tag = el.tagName.toLowerCase();
922
+ const type = (el.getAttribute('type') || '').toLowerCase();
923
+ if (tag === 'button' || type === 'button' || type === 'submit') return 'button';
924
+ if (tag === 'a') return 'link';
925
+ if (tag === 'textarea') return 'textbox';
926
+ if (tag === 'select') return 'combobox';
927
+ if (tag === 'input' && ['checkbox', 'radio'].includes(type)) return type;
928
+ if (tag === 'input') return 'textbox';
929
+ return tag;
930
+ }
931
+
932
+ function labelFor(el) {
933
+ if (el.id) {
934
+ const label = document.querySelector('label[for="' + CSS.escape(el.id) + '"]');
935
+ if (label) return label.innerText || label.textContent || '';
936
+ }
937
+ const wrappingLabel = el.closest('label');
938
+ return wrappingLabel ? (wrappingLabel.innerText || wrappingLabel.textContent || '') : '';
939
+ }
940
+
941
+ function norm(value) {
942
+ return String(value || '').trim().replace(/\\s+/g, ' ');
943
+ }
944
+
945
+ function scoreField(value, weightExact, weightIncludes) {
946
+ const text = norm(value);
947
+ const lower = text.toLowerCase();
948
+ if (!targetLower) return { score: 0, by: '' };
949
+ if (lower === targetLower) return { score: weightExact, by: 'exact' };
950
+ if (lower.includes(targetLower)) return { score: weightIncludes, by: 'contains' };
951
+ if (targetLower.includes(lower) && lower.length >= 3) return { score: Math.max(1, weightIncludes - 1), by: 'contained_by_target' };
952
+ return { score: 0, by: '' };
953
+ }
954
+
955
+ const candidates = Array.from(document.querySelectorAll(selectors)).map((el) => {
956
+ if (!visible(el)) return null;
957
+ const selector = cssPath(el);
958
+ if (!selector) return null;
959
+
960
+ const inferredRole = inferRole(el);
961
+ const fields = [
962
+ ['selector', selector, 12, 10],
963
+ ['aria-label', el.getAttribute('aria-label'), 11, 9],
964
+ ['label', labelFor(el), 11, 9],
965
+ ['placeholder', el.getAttribute('placeholder'), 10, 8],
966
+ ['name', el.getAttribute('name'), 9, 7],
967
+ ['text', el.innerText || el.textContent, 8, 6],
968
+ ['value', el.getAttribute('value'), 7, 5],
969
+ ['title', el.getAttribute('title'), 6, 4]
970
+ ];
971
+
972
+ let score = 0;
973
+ let matchedBy = '';
974
+ for (const [field, value, exact, includes] of fields) {
975
+ const match = scoreField(value, exact, includes);
976
+ if (match.score > score) {
977
+ score = match.score;
978
+ matchedBy = field + ':' + match.by;
979
+ }
980
+ }
981
+
982
+ if (roleHint) {
983
+ if (inferredRole === roleHint) score += 4;
984
+ else if (roleHint === 'textbox' && ['input', 'textarea'].includes(el.tagName.toLowerCase())) score += 3;
985
+ else score -= 2;
986
+ }
987
+
988
+ const rect = el.getBoundingClientRect();
989
+ return {
990
+ selector,
991
+ role: inferredRole,
992
+ tag: el.tagName.toLowerCase(),
993
+ type: el.getAttribute('type') || '',
994
+ text: norm(el.innerText || el.textContent || el.getAttribute('aria-label') || el.getAttribute('placeholder')).substring(0, 120),
995
+ matchedBy,
996
+ score,
997
+ bounds: {
998
+ x: Math.round(rect.left),
999
+ y: Math.round(rect.top),
1000
+ width: Math.round(rect.width),
1001
+ height: Math.round(rect.height)
1002
+ }
1003
+ };
1004
+ }).filter(Boolean).sort((a, b) => b.score - a.score);
1005
+
1006
+ return candidates;
1007
+ })()
1008
+ `,
1009
+ returnByValue: true,
1010
+ awaitPromise: true
1011
+ });
1012
+ const candidates = result.result?.value || [];
1013
+ const best = candidates[0];
1014
+ if (best && best.score > 0) {
1015
+ return {
1016
+ success: true,
1017
+ target,
1018
+ selector: best.selector,
1019
+ matchedBy: best.matchedBy,
1020
+ confidence: Math.min(1, best.score / 16),
1021
+ candidates: params.includeCandidates ? candidates.slice(0, 10) : undefined
1022
+ };
1023
+ }
1024
+ await new Promise(r => setTimeout(r, 200));
1025
+ }
1026
+ return { success: false, target, message: "No matching visible element found" };
1027
+ }
1028
+ async getState(params) {
1029
+ const includeScreenshot = params.screenshot === true;
1030
+ const includeDomSnapshot = params.domSnapshot === true || params.includeDOMSnapshot === true;
1031
+ const includeElements = params.elements !== false;
1032
+ const includeSoM = params.som === true || params.withOverlay === true;
1033
+ const includeTabs = params.tabs === true;
1034
+ const compact = includeElements
1035
+ ? await this.snapshotCache.compact({ maxElements: 200, includeText: true, withOverlay: includeSoM })
1036
+ : await this.snapshotCache.compact({ maxElements: 0, includeText: false });
1037
+ const [screenshot, domSnapshot, tabs] = await Promise.all([
1038
+ includeScreenshot ? this.client.screenshot(params.format, params.quality).catch(() => null) : Promise.resolve(null),
1039
+ includeDomSnapshot ? this.client.getDOMSnapshot().catch(() => null) : Promise.resolve(null),
1040
+ includeTabs ? this.client.getTabs().catch(() => []) : Promise.resolve([]),
1041
+ ]);
1042
+ if (includeSoM) {
1043
+ await this.client.removeSoMOverlay().catch(() => { });
1044
+ }
1045
+ return {
1046
+ title: compact.title,
1047
+ url: compact.url,
1048
+ screenshot,
1049
+ domSnapshot,
1050
+ elements: includeElements ? compact.elements : [],
1051
+ somInjected: includeSoM,
1052
+ cache: compact.cache,
1053
+ tabs,
1054
+ };
1055
+ }
1056
+ async navigate(params) {
1057
+ await this.client.navigateAndWait(params.url, params.timeout || 10000);
1058
+ this.snapshotCache.invalidate("navigate");
1059
+ return "Navigated";
1060
+ }
1061
+ async waitForPageSettled(timeout = 10000) {
1062
+ try {
1063
+ await this.client.waitForNavigation(Math.min(timeout, 10000));
1064
+ }
1065
+ catch {
1066
+ await this.client.waitForNetworkIdle(300, Math.min(timeout, 2500)).catch(() => { });
1067
+ }
1068
+ }
1069
+ async click(params) {
1070
+ const x = params.x || params.coordinate?.split(',')[0] || 100;
1071
+ const y = params.y || params.coordinate?.split(',')[1] || 100;
1072
+ await this.client.click(x, y);
1073
+ this.snapshotCache.invalidate("click");
1074
+ return "Clicked";
1075
+ }
1076
+ async clickElement(params) {
1077
+ // Click by element ID (from SoM) - resolves ID to coordinates
1078
+ if (params.id !== undefined) {
1079
+ const result = await this.client.evaluate(`
1080
+ (function() {
1081
+ const targetId = Number(${JSON.stringify(String(params.id).replace(/@/g, ''))});
1082
+ const selectors = [
1083
+ 'a[href]', 'button', 'input:not([type="hidden"])', 'select', 'textarea',
1084
+ '[onclick]', '[role="button"]', '[role="link"]', '[role="checkbox"]',
1085
+ '[tabindex]:not([tabindex="-1"])', 'label', 'summary'
1086
+ ].join(', ');
1087
+ const elements = Array.from(document.querySelectorAll(selectors)).filter((el) => {
1088
+ const rect = el.getBoundingClientRect();
1089
+ const computed = window.getComputedStyle(el);
1090
+ return computed.display !== 'none' &&
1091
+ computed.visibility !== 'hidden' &&
1092
+ rect.width > 0 &&
1093
+ rect.height > 0;
1094
+ });
1095
+ const el = elements[targetId - 1];
1096
+ if (el) {
1097
+ el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
1098
+ const rect = el.getBoundingClientRect();
1099
+ return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2, w: rect.width };
1100
+ }
1101
+ return null;
1102
+ })()
1103
+ `);
1104
+ if (result) {
1105
+ await this.client.click(result.x, result.y, params.button, result.w);
1106
+ this.snapshotCache.invalidate("click_element");
1107
+ return `Clicked element @${params.id}`;
1108
+ }
1109
+ // Fallback: try to find element by selector or text
1110
+ if (params.selector) {
1111
+ return this.clickElementBySelector({ selector: params.selector });
1112
+ }
1113
+ if (params.text) {
1114
+ return this.clickElementByText({ text: params.text });
1115
+ }
1116
+ }
1117
+ // Fallback to coordinate click
1118
+ if (params.x !== undefined && params.y !== undefined) {
1119
+ await this.client.click(params.x, params.y);
1120
+ this.snapshotCache.invalidate("click_element");
1121
+ return "Clicked at coordinates";
1122
+ }
1123
+ throw new Error("Element not found: no valid id, selector, text, or coordinates provided");
1124
+ }
1125
+ async clickElementByText(params) {
1126
+ const result = await this.locator.resolve({ target: params.text, timeout: params.timeout || 5000 });
1127
+ if (result.success && result.candidate) {
1128
+ await this.clickResolvedLocator(result.candidate);
1129
+ this.snapshotCache.invalidate("click_element_by_text");
1130
+ return `Clicked element with text: ${params.text}`;
1131
+ }
1132
+ throw new Error(`Element with text not found: ${params.text}`);
1133
+ }
1134
+ async clickElementBySelector(params) {
1135
+ const selector = params.selector;
1136
+ if (!selector)
1137
+ throw new Error("Selector required");
1138
+ if (String(selector).startsWith("point:")) {
1139
+ const [x, y] = String(selector).slice(6).split(",").map(Number);
1140
+ if (!Number.isFinite(x) || !Number.isFinite(y))
1141
+ throw new Error(`Invalid point selector: ${selector}`);
1142
+ await this.client.click(x, y);
1143
+ this.snapshotCache.invalidate("click_element_by_selector");
1144
+ return "Clicked element by point";
1145
+ }
1146
+ // Get element bounds via CDP
1147
+ const result = await this.client.sendCommand("Runtime.evaluate", {
1148
+ expression: `
1149
+ (function() {
1150
+ const el = document.querySelector(${JSON.stringify(selector)});
1151
+ if (!el) return null;
1152
+ el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
1153
+ const rect = el.getBoundingClientRect();
1154
+ const computed = window.getComputedStyle(el);
1155
+ if (computed.display === 'none' || computed.visibility === 'hidden' || rect.width === 0 || rect.height === 0) return null;
1156
+ return { x: rect.left + rect.width/2, y: rect.top + rect.height/2, w: rect.width };
1157
+ })()
1158
+ `,
1159
+ returnByValue: true,
1160
+ });
1161
+ if (result.result?.value) {
1162
+ const { x, y, w } = result.result.value;
1163
+ await this.client.click(x, y, params.button, w);
1164
+ this.snapshotCache.invalidate("click_element_by_selector");
1165
+ return "Clicked element by selector";
1166
+ }
1167
+ throw new Error(`Element not found: ${selector}`);
1168
+ }
1169
+ async type(params) {
1170
+ const text = params.text || params.value || "";
1171
+ await this.client.typeText(text);
1172
+ return "Typed text";
1173
+ }
1174
+ async evaluate(params) {
1175
+ return await this.client.evaluate(params.script);
1176
+ }
1177
+ async screenshot(params) {
1178
+ const format = params.format === "png" ? "png" : "jpeg";
1179
+ const quality = params.quality || 80;
1180
+ if (params.x !== undefined) {
1181
+ // Region screenshot - use CDP clipping
1182
+ const result = await this.client.sendCommand("Page.captureScreenshot", {
1183
+ format,
1184
+ quality,
1185
+ clip: {
1186
+ x: params.x,
1187
+ y: params.y,
1188
+ width: params.width || 100,
1189
+ height: params.height || 100,
1190
+ scale: 1,
1191
+ },
1192
+ });
1193
+ return result.data;
1194
+ }
1195
+ return await this.client.screenshot(format, quality);
1196
+ }
1197
+ async scroll(params) {
1198
+ const x = params.x || 0;
1199
+ const y = params.y || 0;
1200
+ const originX = params.originX ?? params.mouseX ?? params.options?.originX;
1201
+ const originY = params.originY ?? params.mouseY ?? params.options?.originY;
1202
+ await this.client.wheel(x, y, originX, originY);
1203
+ return "Scrolled";
1204
+ }
1205
+ async wait(params) {
1206
+ const ms = params.ms || params.timeout || 1000;
1207
+ await new Promise((r) => setTimeout(r, ms));
1208
+ return "Waited";
1209
+ }
1210
+ async cdpCommand(params) {
1211
+ return await this.client.sendCommand(params.command, params.args || {});
1212
+ }
1213
+ async getDomSnapshot(params) {
1214
+ return await this.client.getDOMSnapshot();
1215
+ }
1216
+ // ==================== AGENT-CENTRIC APIs ====================
1217
+ async agentAction(params) {
1218
+ const { action, target, verify, waitFor, timeout } = params;
1219
+ const timeoutMs = timeout || 10000;
1220
+ try {
1221
+ // Execute action with proper element resolution
1222
+ switch (action) {
1223
+ case "click":
1224
+ if (target.id) {
1225
+ await this.clickElement({ id: target.id, button: target.button });
1226
+ }
1227
+ else if (target.selector) {
1228
+ // Wait for selector if needed
1229
+ await this.client.waitForSelector(target.selector, 5000);
1230
+ await this.clickElementBySelector({ selector: target.selector, button: target.button });
1231
+ }
1232
+ else if (target.x !== undefined) {
1233
+ await this.client.click(target.x, target.y, target.button);
1234
+ }
1235
+ break;
1236
+ case "type":
1237
+ if (target.selector) {
1238
+ await this.client.waitForSelector(target.selector, 5000);
1239
+ await this.client.moveMouseToSelector(target.selector).catch(() => { });
1240
+ await this.client.evaluate(`
1241
+ (function() {
1242
+ const el = document.querySelector(${JSON.stringify(target.selector)});
1243
+ if (el) { el.value = ''; el.focus(); }
1244
+ })()
1245
+ `);
1246
+ }
1247
+ await this.client.typeText(target.text || target.value || "");
1248
+ break;
1249
+ case "scroll":
1250
+ await this.client.sendCommand("Input.dispatchMouseWheel", {
1251
+ x: target.x || 0, y: target.y || 0,
1252
+ deltaX: target.deltaX || 0, deltaY: target.deltaY || target.y || 0
1253
+ });
1254
+ break;
1255
+ case "key_press":
1256
+ await this.client.sendCommand("Input.dispatchKeyEvent", {
1257
+ type: "keyDown", text: target.key, key: target.key
1258
+ });
1259
+ await this.client.sendCommand("Input.dispatchKeyEvent", {
1260
+ type: "keyUp", text: target.key, key: target.key
1261
+ });
1262
+ break;
1263
+ case "hover":
1264
+ await this.client.moveMouse(target.x || 0, target.y || 0);
1265
+ break;
1266
+ case "drag":
1267
+ const sx = target.startX || target.x || 0;
1268
+ const sy = target.startY || target.y || 0;
1269
+ const ex = target.endX || sx + 100;
1270
+ const ey = target.endY || sy + 100;
1271
+ await this.client.moveMouse(sx, sy);
1272
+ await this.client.sendCommand("Input.dispatchMouseEvent", { type: "mousePressed", x: sx, y: sy, button: "left", clickCount: 1 });
1273
+ await this.client.moveMouse(ex, ey);
1274
+ await this.client.sendCommand("Input.dispatchMouseEvent", { type: "mouseReleased", x: ex, y: ey, button: "left", clickCount: 1 });
1275
+ break;
1276
+ }
1277
+ // Wait for condition with proper waiting mechanisms
1278
+ if (waitFor) {
1279
+ if (waitFor.type === "network_idle") {
1280
+ await this.client.waitForNetworkIdle(500, waitFor.timeout || 3000);
1281
+ }
1282
+ else if (waitFor.type === "element") {
1283
+ if (waitFor.selector) {
1284
+ await this.client.waitForSelector(waitFor.selector, waitFor.timeout || 5000);
1285
+ }
1286
+ else {
1287
+ await new Promise(r => setTimeout(r, waitFor.timeout || 3000));
1288
+ }
1289
+ }
1290
+ else if (waitFor.type === "navigation") {
1291
+ try {
1292
+ await this.client.waitForNavigation(waitFor.timeout || 10000);
1293
+ }
1294
+ catch {
1295
+ // Navigation might have already completed
1296
+ }
1297
+ }
1298
+ }
1299
+ else {
1300
+ // Default wait for stability
1301
+ await new Promise(r => setTimeout(r, 500));
1302
+ }
1303
+ // Verify if requested
1304
+ let verification = null;
1305
+ if (verify) {
1306
+ if (verify.type === "element_exists") {
1307
+ const res = await this.client.evaluate(`
1308
+ (function() {
1309
+ const el = document.querySelector(${JSON.stringify(verify.selector)});
1310
+ return { success: !!el, exists: !!el, message: el ? "Element exists" : "Element not found" };
1311
+ })()
1312
+ `);
1313
+ verification = res;
1314
+ }
1315
+ else if (verify.type === "element_contains_text") {
1316
+ const res = await this.client.evaluate(`
1317
+ (function() {
1318
+ const el = document.querySelector(${JSON.stringify(verify.selector)});
1319
+ if (!el) return { success: false, message: "Element not found" };
1320
+ const text = (el.innerText || el.textContent || "").trim();
1321
+ const matches = text.includes(${JSON.stringify(verify.expectedText || verify.text || "")});
1322
+ return { success: matches, text, message: matches ? "Verified" : "Text mismatch" };
1323
+ })()
1324
+ `);
1325
+ verification = res;
1326
+ }
1327
+ else if (verify.selector) {
1328
+ // Simple existence check
1329
+ const res = await this.client.evaluate(`
1330
+ (function() {
1331
+ return !!document.querySelector(${JSON.stringify(verify.selector)});
1332
+ })()
1333
+ `);
1334
+ verification = { success: !!res, selector: verify.selector };
1335
+ }
1336
+ }
1337
+ // Get screenshot
1338
+ const screenshot = params.screenshot === true ? await this.client.screenshot("jpeg", 70).catch(() => null) : null;
1339
+ const url = await this.client.evaluate("window.location.href").catch(() => "Unknown");
1340
+ const title = await this.client.evaluate("document.title").catch(() => "Unknown");
1341
+ return {
1342
+ success: true,
1343
+ action: `${action} completed`,
1344
+ verification,
1345
+ screenshot,
1346
+ url,
1347
+ title
1348
+ };
1349
+ }
1350
+ catch (e) {
1351
+ return { success: false, error: e.message };
1352
+ }
1353
+ }
1354
+ async smartNavigate(params) {
1355
+ const { url, waitFor, dismissPopups, screenshot, timeout } = params;
1356
+ const timeoutMs = timeout || 30000;
1357
+ try {
1358
+ await this.client.navigateAndWait(url, timeoutMs);
1359
+ // Dismiss popups if requested
1360
+ if (dismissPopups !== false) {
1361
+ await this.client.evaluate(`
1362
+ (function() {
1363
+ // Try to find and click common close buttons
1364
+ const selectors = [
1365
+ '[aria-label*="close" i]', '[aria-label*="dismiss" i]',
1366
+ '.close', '.dismiss', '.modal-close',
1367
+ 'button[class*="close"]', '[data-dismiss="modal"]'
1368
+ ];
1369
+ for (const sel of selectors) {
1370
+ const el = document.querySelector(sel);
1371
+ if (el && el.offsetParent !== null) {
1372
+ el.click();
1373
+ return true;
1374
+ }
1375
+ }
1376
+ return false;
1377
+ })()
1378
+ `).catch(() => { });
1379
+ }
1380
+ // Wait for specific condition
1381
+ if (waitFor) {
1382
+ if (waitFor.type === "network_idle") {
1383
+ await this.client.waitForNetworkIdle(500, waitFor.timeout || 3000);
1384
+ }
1385
+ else if (waitFor.type === "element" && waitFor.selector) {
1386
+ await this.client.waitForSelector(waitFor.selector, waitFor.timeout || 5000);
1387
+ }
1388
+ }
1389
+ const currentUrl = await this.client.evaluate("window.location.href").catch(() => url);
1390
+ const title = await this.client.evaluate("document.title").catch(() => "Unknown");
1391
+ const screenshotData = screenshot === true ? await this.client.screenshot("jpeg", 70).catch(() => null) : null;
1392
+ return {
1393
+ success: true,
1394
+ url: currentUrl,
1395
+ title,
1396
+ screenshot: screenshotData
1397
+ };
1398
+ }
1399
+ catch (e) {
1400
+ return { success: false, error: e.message };
1401
+ }
1402
+ }
1403
+ async observeAndAct(params) {
1404
+ const { action, observe, returnScreenshot } = params;
1405
+ try {
1406
+ const [beforeFacts, beforeScreenshot] = await Promise.all([
1407
+ this.captureActionFacts(action.selector),
1408
+ returnScreenshot === true ? this.client.screenshot("jpeg", 70).catch(() => null) : Promise.resolve(null),
1409
+ ]);
1410
+ if (action.type === "click" && action.selector) {
1411
+ await this.client.waitForSelector(action.selector, 5000, { visible: true, stable: true });
1412
+ await this.clickElementBySelector({ selector: action.selector });
1413
+ }
1414
+ else if (action.type === "type" && action.text) {
1415
+ if (action.selector) {
1416
+ await this.fillBySelector({ selector: action.selector, value: action.text, timeout: 5000 });
1417
+ }
1418
+ else {
1419
+ await this.client.typeText(action.text);
1420
+ }
1421
+ }
1422
+ if (observe?.type === "dom_change") {
1423
+ await this.client.waitForNetworkIdle(300, 3000).catch(() => { });
1424
+ }
1425
+ else if (observe?.type === "network_response") {
1426
+ await this.client.waitForNetworkIdle(500, 5000).catch(() => { });
1427
+ }
1428
+ else {
1429
+ await new Promise(r => setTimeout(r, 300));
1430
+ }
1431
+ const [afterFacts, afterScreenshot] = await Promise.all([
1432
+ this.captureActionFacts(action.selector),
1433
+ returnScreenshot === true ? this.client.screenshot("jpeg", 70).catch(() => null) : Promise.resolve(null),
1434
+ ]);
1435
+ const facts = this.diffActionFacts(beforeFacts, afterFacts);
1436
+ const changesDetected = facts.urlChanged || facts.titleChanged || facts.valueChanged || facts.checkedChanged || facts.selectedIndexChanged;
1437
+ return {
1438
+ success: true,
1439
+ before: { facts: beforeFacts, screenshot: beforeScreenshot },
1440
+ after: { facts: afterFacts, screenshot: afterScreenshot },
1441
+ changesDetected,
1442
+ facts,
1443
+ navigationOccurred: facts.urlChanged,
1444
+ };
1445
+ }
1446
+ catch (e) {
1447
+ return { success: false, error: e.message };
1448
+ }
1449
+ }
1450
+ simpleDiff(before, after) {
1451
+ // Simple Levenshtein distance for change detection
1452
+ if (before === after)
1453
+ return 0;
1454
+ const len = Math.max(before.length, after.length);
1455
+ let diff = 0;
1456
+ for (let i = 0; i < len; i++) {
1457
+ if (before[i] !== after[i])
1458
+ diff++;
1459
+ }
1460
+ return diff;
1461
+ }
1462
+ async agentFormFill(params) {
1463
+ const { fields, submitAfterFill, submitSelector } = params;
1464
+ const results = [];
1465
+ try {
1466
+ for (const field of fields) {
1467
+ const selector = field.selector || (field.id ? `#${field.id}` : undefined);
1468
+ if (!selector && field.type !== "file") {
1469
+ results.push({ field: field.id || field.selector, success: false, error: "Missing selector or id" });
1470
+ continue;
1471
+ }
1472
+ if (["text", "email", "password", "textarea"].includes(field.type) || !field.type) {
1473
+ await this.fillBySelector({ selector, value: field.value ?? "", timeout: field.timeout || 5000 });
1474
+ }
1475
+ else if (field.type === "select") {
1476
+ await this.selectOption({ selector, value: field.value ?? "" });
1477
+ }
1478
+ else if (field.type === "checkbox" || field.type === "radio") {
1479
+ if (field.checked === false) {
1480
+ await this.setChecked({ selector, checked: false });
1481
+ }
1482
+ else {
1483
+ await this.checkElement({ selector });
1484
+ }
1485
+ }
1486
+ else if (field.type === "file") {
1487
+ await this.uploadFile({ selector, files: field.files || [] });
1488
+ }
1489
+ else {
1490
+ await this.fillBySelector({ selector, value: field.value ?? "", timeout: field.timeout || 5000 });
1491
+ }
1492
+ results.push({ field: field.id || field.selector, selector, success: true });
1493
+ }
1494
+ if (submitAfterFill && submitSelector) {
1495
+ await this.clickElementBySelector({ selector: submitSelector });
1496
+ }
1497
+ return { success: true, fieldsFilled: results.length, results };
1498
+ }
1499
+ catch (e) {
1500
+ return { success: false, error: e.message, results };
1501
+ }
1502
+ }
1503
+ async pageSnapshot(params) {
1504
+ try {
1505
+ const includeScreenshot = params.screenshot === true;
1506
+ const includeCookies = params.cookies === true;
1507
+ const includeAccessibilityTree = params.accessibilityTree === true;
1508
+ const [title, url, screenshot, domSnapshot, elements, forms, cookies, axTree] = await Promise.all([
1509
+ this.client.evaluate("document.title").catch(() => "Unknown"),
1510
+ this.client.evaluate("window.location.href").catch(() => "Unknown"),
1511
+ includeScreenshot ? this.client.screenshot(params.fullPage ? "jpeg" : "jpeg", 70).catch(() => null) : Promise.resolve(null),
1512
+ params.includeDOMSnapshot ? this.client.getDOMSnapshot().catch(() => null) : Promise.resolve(undefined),
1513
+ this.client.getInteractiveElements(false).catch(() => ({ elements: [] })),
1514
+ this.client.evaluate(`
1515
+ (function() {
1516
+ const forms = Array.from(document.querySelectorAll('form'));
1517
+ return forms.map((form, idx) => {
1518
+ const inputs = Array.from(form.querySelectorAll('input, select, textarea'));
1519
+ return {
1520
+ id: 'form-' + idx,
1521
+ action: form.action,
1522
+ method: form.method,
1523
+ inputs: inputs.map(input => ({
1524
+ type: input.type || 'text',
1525
+ name: input.name || '',
1526
+ id: input.id || '',
1527
+ required: input.required,
1528
+ placeholder: input.placeholder || ''
1529
+ }))
1530
+ };
1531
+ });
1532
+ })()
1533
+ `).catch(() => ({ value: [] })),
1534
+ includeCookies ? this.client.sendCommand("Network.getCookies", {}).catch(() => ({ cookies: [] })) : Promise.resolve({ cookies: [] }),
1535
+ includeAccessibilityTree ? this.client.getSimplifiedAccessibilityTree().catch(() => []) : Promise.resolve([]),
1536
+ ]);
1537
+ return {
1538
+ title,
1539
+ url,
1540
+ screenshot,
1541
+ elements: elements.elements,
1542
+ accessibilityTree: axTree,
1543
+ forms: forms || [],
1544
+ cookies: cookies.cookies || [],
1545
+ domSnapshot: params.includeDOMSnapshot ? domSnapshot : undefined,
1546
+ metadata: {
1547
+ timestamp: Date.now(),
1548
+ elementCount: elements.elements.length,
1549
+ }
1550
+ };
1551
+ }
1552
+ catch (e) {
1553
+ return { success: false, error: e.message };
1554
+ }
1555
+ }
1556
+ // ==================== SELF-HEALING SELECTOR RESOLUTION ====================
1557
+ async resolveSelector(params) {
1558
+ const { originalSelector, text, fuzzyMatch } = params;
1559
+ // Try original selector first
1560
+ if (originalSelector) {
1561
+ const exists = await this.client.evaluate(`
1562
+ !!document.querySelector(${JSON.stringify(originalSelector)})
1563
+ `);
1564
+ if (exists) {
1565
+ return { selector: originalSelector, method: "exact", confidence: 1.0 };
1566
+ }
1567
+ }
1568
+ const resolved = await this.locator.resolve({
1569
+ target: text,
1570
+ timeout: params.timeout || 1500,
1571
+ includeCandidates: false,
1572
+ }).catch(() => null);
1573
+ if (resolved?.success && (resolved.selector || resolved.ref)) {
1574
+ return {
1575
+ selector: resolved.candidate?.scope === "document" && resolved.candidate.framePath.length === 0 && resolved.candidate.shadowDepth === 0
1576
+ ? resolved.selector || ""
1577
+ : resolved.ref || "",
1578
+ method: resolved.matchedBy || "locator",
1579
+ confidence: resolved.confidence || 0.7,
1580
+ };
1581
+ }
1582
+ // Try fuzzy text matching if enabled
1583
+ if (fuzzyMatch !== false && text) {
1584
+ const result = await this.client.evaluate(`
1585
+ (function() {
1586
+ const searchText = ${JSON.stringify(text)};
1587
+ const searchLower = String(searchText || '').trim().toLowerCase();
1588
+ const elements = Array.from(document.querySelectorAll('a[href], button, input:not([type="hidden"]), select, textarea, [role="button"], [role="link"], [onclick]')).filter((el) => {
1589
+ const rect = el.getBoundingClientRect();
1590
+ const computed = window.getComputedStyle(el);
1591
+ return computed.display !== 'none' && computed.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
1592
+ });
1593
+
1594
+ function cssPath(el) {
1595
+ if (el.id) return '#' + CSS.escape(el.id);
1596
+ const path = [];
1597
+ while (el && el.nodeType === Node.ELEMENT_NODE && el !== document.body) {
1598
+ let selector = el.nodeName.toLowerCase();
1599
+ if (el.classList && el.classList.length) {
1600
+ selector += '.' + Array.from(el.classList).slice(0, 2).map(c => CSS.escape(c)).join('.');
1601
+ }
1602
+ const parent = el.parentElement;
1603
+ if (parent) {
1604
+ const siblings = Array.from(parent.children).filter(child => child.nodeName === el.nodeName);
1605
+ if (siblings.length > 1) selector += ':nth-of-type(' + (siblings.indexOf(el) + 1) + ')';
1606
+ }
1607
+ path.unshift(selector);
1608
+ el = parent;
1609
+ }
1610
+ return path.length ? path.join(' > ') : '';
1611
+ }
1612
+
1613
+ function textFor(el) {
1614
+ return String(
1615
+ el.innerText ||
1616
+ el.textContent ||
1617
+ el.getAttribute('aria-label') ||
1618
+ el.getAttribute('placeholder') ||
1619
+ el.getAttribute('name') ||
1620
+ ''
1621
+ ).trim();
1622
+ }
1623
+
1624
+ // Exact match first
1625
+ let best = elements.find(el => textFor(el).toLowerCase() === searchLower);
1626
+ if (best) return { selector: cssPath(best), confidence: 1.0 };
1627
+
1628
+ // Partial match
1629
+ best = elements.find(el => {
1630
+ const value = textFor(el).toLowerCase();
1631
+ return searchLower.length >= 3 && value.includes(searchLower);
1632
+ });
1633
+ if (best) return { selector: cssPath(best), confidence: 0.8 };
1634
+
1635
+ // Fuzzy match (Levenshtein distance)
1636
+ let minDist = Infinity;
1637
+ let bestEl = null;
1638
+ elements.forEach(el => {
1639
+ const elText = textFor(el);
1640
+ if (!elText || Math.abs(elText.length - searchText.length) > 8) return;
1641
+ const dist = levenshteinDistance(searchText, elText);
1642
+ if (dist < minDist && dist <= 3) {
1643
+ minDist = dist;
1644
+ bestEl = el;
1645
+ }
1646
+ });
1647
+
1648
+ if (bestEl) return { selector: cssPath(bestEl), confidence: 0.6 };
1649
+
1650
+ return null;
1651
+
1652
+ function levenshteinDistance(a, b) {
1653
+ if (a.length === 0) return b.length;
1654
+ if (b.length === 0) return a.length;
1655
+ const matrix = [];
1656
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
1657
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
1658
+ for (let i = 1; i <= b.length; i++) {
1659
+ for (let j = 1; j <= a.length; j++) {
1660
+ matrix[i][j] = b.charAt(i-1) === a.charAt(j-1) ? matrix[i-1][j-1] :
1661
+ Math.min(matrix[i-1][j-1] + 1, matrix[i][j-1] + 1, matrix[i-1][j] + 1);
1662
+ }
1663
+ }
1664
+ return matrix[b.length][a.length];
1665
+ }
1666
+ })()
1667
+ `);
1668
+ if (result) {
1669
+ return { selector: result.selector, method: "fuzzy", confidence: result.confidence };
1670
+ }
1671
+ }
1672
+ throw new Error(`Could not resolve selector. Original: ${originalSelector}, Text: ${text}`);
1673
+ }
1674
+ async getTabs(params) {
1675
+ const result = await this.client.sendCommand("Target.getTargets", {});
1676
+ return result.targetInfos || [];
1677
+ }
1678
+ async newTab(params) {
1679
+ const result = await this.client.sendCommand("Target.createTarget", {
1680
+ url: params.url || "about:blank",
1681
+ });
1682
+ return result.targetId ? `Created new tab: ${result.targetId}` : "Created new tab";
1683
+ }
1684
+ async switchTab(params) {
1685
+ if (!params.targetId)
1686
+ throw new Error("targetId required to switch tabs");
1687
+ await this.client.sendCommand("Target.activateTarget", { targetId: params.targetId });
1688
+ await this.client.switchToTarget(params.targetId, params.port || 9222);
1689
+ return `Switched to tab ${params.targetId}`;
1690
+ }
1691
+ async closeTab(params) {
1692
+ await this.client.sendCommand("Target.closeTarget", {
1693
+ targetId: params.targetId,
1694
+ });
1695
+ return "Closed tab";
1696
+ }
1697
+ async startScreencast(params) {
1698
+ this.screencastFrames = [];
1699
+ if (!this.screencastFrameListener) {
1700
+ this.screencastFrameListener = async (event) => {
1701
+ this.screencastFrames.push(event.data);
1702
+ const maxFrames = params.maxFrames || 100;
1703
+ if (this.screencastFrames.length > maxFrames) {
1704
+ this.screencastFrames.splice(0, this.screencastFrames.length - maxFrames);
1705
+ }
1706
+ try {
1707
+ await this.client.sendCommand("Page.screencastFrameAck", { sessionId: event.sessionId });
1708
+ }
1709
+ catch { }
1710
+ };
1711
+ this.client.on("Page.screencastFrame", this.screencastFrameListener);
1712
+ }
1713
+ await this.client.sendCommand("Page.startScreencast", {
1714
+ format: params.format || "jpeg",
1715
+ quality: params.quality || 80,
1716
+ everyNthFrame: params.everyNthFrame || 1
1717
+ });
1718
+ return "Started screencast";
1719
+ }
1720
+ async stopScreencast(params) {
1721
+ await this.client.sendCommand("Page.stopScreencast", {});
1722
+ if (this.screencastFrameListener) {
1723
+ this.client.removeEventListener("Page.screencastFrame", this.screencastFrameListener);
1724
+ this.screencastFrameListener = null;
1725
+ }
1726
+ return "Stopped screencast";
1727
+ }
1728
+ async recordSession(params) {
1729
+ const duration = params.duration || 5000;
1730
+ const frames = [];
1731
+ const onFrame = async (event) => {
1732
+ frames.push(event.data);
1733
+ try {
1734
+ await this.client.sendCommand("Page.screencastFrameAck", { sessionId: event.sessionId });
1735
+ }
1736
+ catch { }
1737
+ };
1738
+ this.client.on("Page.screencastFrame", onFrame);
1739
+ await this.startScreencast(params);
1740
+ await new Promise(r => setTimeout(r, duration));
1741
+ await this.stopScreencast(params);
1742
+ this.client.removeEventListener("Page.screencastFrame", onFrame);
1743
+ return {
1744
+ frames,
1745
+ frameCount: frames.length,
1746
+ duration
1747
+ };
1748
+ }
1749
+ async sampleVisualFrames(params) {
1750
+ const duration = Math.max(250, Math.min(Number(params.duration ?? 1500), 10000));
1751
+ const maxFrames = Math.max(1, Math.min(Number(params.maxFrames ?? 4), 12));
1752
+ const frames = [];
1753
+ const timestamps = [];
1754
+ const onFrame = async (event) => {
1755
+ if (frames.length < maxFrames) {
1756
+ frames.push(event.data);
1757
+ timestamps.push(new Date().toISOString());
1758
+ }
1759
+ await this.client.sendCommand("Page.screencastFrameAck", { sessionId: event.sessionId }).catch(() => { });
1760
+ };
1761
+ this.client.on("Page.screencastFrame", onFrame);
1762
+ await this.client.sendCommand("Page.startScreencast", {
1763
+ format: params.format || "jpeg",
1764
+ quality: Math.max(20, Math.min(Number(params.quality ?? 45), 80)),
1765
+ maxWidth: Math.max(320, Math.min(Number(params.maxWidth ?? 800), 1280)),
1766
+ maxHeight: Math.max(240, Math.min(Number(params.maxHeight ?? 600), 900)),
1767
+ everyNthFrame: Math.max(1, Math.min(Number(params.everyNthFrame ?? 3), 10))
1768
+ });
1769
+ try {
1770
+ await new Promise(r => setTimeout(r, duration));
1771
+ }
1772
+ finally {
1773
+ await this.client.sendCommand("Page.stopScreencast", {}).catch(() => { });
1774
+ this.client.removeEventListener("Page.screencastFrame", onFrame);
1775
+ }
1776
+ return {
1777
+ success: true,
1778
+ frameCount: frames.length,
1779
+ duration,
1780
+ format: params.format || "jpeg",
1781
+ timestamps,
1782
+ frames
1783
+ };
1784
+ }
1785
+ async startTracing(params) {
1786
+ await this.client.sendCommand("Tracing.start", { categories: params.categories || "devtools.timeline" });
1787
+ return "Started tracing";
1788
+ }
1789
+ async stopTracing(params) {
1790
+ await this.client.sendCommand("Tracing.end", {});
1791
+ return "Stopped tracing";
1792
+ }
1793
+ async getPerformanceMetrics(params) {
1794
+ await this.client.sendCommand("Performance.enable", {});
1795
+ const result = await this.client.sendCommand("Performance.getMetrics", {});
1796
+ return result.metrics;
1797
+ }
1798
+ async hover(params) {
1799
+ const x = params.x || (params.coordinate ? Number(params.coordinate.split(',')[0]) : 100);
1800
+ const y = params.y || (params.coordinate ? Number(params.coordinate.split(',')[1]) : 100);
1801
+ await this.client.moveMouse(x, y);
1802
+ return "Hovered";
1803
+ }
1804
+ async dragAndDrop(params) {
1805
+ const startX = params.startX || 0;
1806
+ const startY = params.startY || 0;
1807
+ const endX = params.endX || 0;
1808
+ const endY = params.endY || 0;
1809
+ await this.client.moveMouse(startX, startY);
1810
+ await this.client.sendCommand("Input.dispatchMouseEvent", { type: "mousePressed", x: startX, y: startY, button: "left", clickCount: 1 });
1811
+ await this.client.moveMouse(endX, endY);
1812
+ await this.client.sendCommand("Input.dispatchMouseEvent", { type: "mouseReleased", x: endX, y: endY, button: "left", clickCount: 1 });
1813
+ return "Dragged and dropped";
1814
+ }
1815
+ // ==================== MISSING ACT TOOL ACTION IMPLEMENTATIONS ====================
1816
+ async fillInput(params) {
1817
+ const selector = params.selector;
1818
+ const text = params.value || params.text || "";
1819
+ if (selector) {
1820
+ // Wait for element and clear it first
1821
+ await this.client.waitForSelector(selector);
1822
+ await this.client.moveMouseToSelector(selector).catch(() => { });
1823
+ await this.client.evaluate(`
1824
+ (function() {
1825
+ const el = document.querySelector(${JSON.stringify(selector)});
1826
+ if (el) {
1827
+ el.value = '';
1828
+ el.focus();
1829
+ return true;
1830
+ }
1831
+ return false;
1832
+ })()
1833
+ `);
1834
+ }
1835
+ await this.client.typeText(text);
1836
+ return `Filled with: ${text}`;
1837
+ }
1838
+ async selectOption(params) {
1839
+ const selector = params.selector;
1840
+ const value = params.value || "";
1841
+ if (!selector)
1842
+ throw new Error("Selector required for select action");
1843
+ await this.client.waitForSelector(selector);
1844
+ const selectInfo = await this.client.evaluate(`
1845
+ (function() {
1846
+ const select = document.querySelector(${JSON.stringify(selector)});
1847
+ if (!select) return { success: false, error: "Element not found" };
1848
+ if (select.tagName.toLowerCase() !== 'select') return { success: false, error: "Element is not a select" };
1849
+ if (select.disabled) return { success: false, error: "Element is disabled" };
1850
+ const wanted = ${JSON.stringify(value)};
1851
+ const options = Array.from(select.options || []);
1852
+ const index = options.findIndex((option) =>
1853
+ option.value === wanted ||
1854
+ option.text === wanted ||
1855
+ option.label === wanted
1856
+ );
1857
+ return {
1858
+ success: true,
1859
+ selectedValue: select.value,
1860
+ index,
1861
+ optionCount: options.length,
1862
+ wantedValue: index >= 0 ? options[index].value : wanted,
1863
+ };
1864
+ })()
1865
+ `);
1866
+ if (!selectInfo?.success)
1867
+ throw new Error(selectInfo?.error || "Failed to inspect select");
1868
+ if (selectInfo.selectedValue === selectInfo.wantedValue) {
1869
+ return `Selected option: ${value}`;
1870
+ }
1871
+ if (selectInfo.index >= 0 && selectInfo.index <= 40) {
1872
+ try {
1873
+ await this.clickElementBySelector({ selector });
1874
+ await this.client.pressKey("Home");
1875
+ for (let i = 0; i < selectInfo.index; i++) {
1876
+ await this.client.pressKey("ArrowDown");
1877
+ }
1878
+ await this.client.pressKey("Enter");
1879
+ const verified = await this.client.evaluate(`
1880
+ (function() {
1881
+ const select = document.querySelector(${JSON.stringify(selector)});
1882
+ return select ? select.value : null;
1883
+ })()
1884
+ `);
1885
+ if (verified === selectInfo.wantedValue) {
1886
+ return `Selected option: ${value}`;
1887
+ }
1888
+ }
1889
+ catch {
1890
+ // Fall back to direct value setting below for reliability.
1891
+ }
1892
+ }
1893
+ const result = await this.client.evaluate(`
1894
+ (function() {
1895
+ const select = document.querySelector(${JSON.stringify(selector)});
1896
+ if (!select) return { success: false, error: "Element not found" };
1897
+ select.value = ${JSON.stringify(selectInfo.wantedValue)};
1898
+ select.dispatchEvent(new Event('input', { bubbles: true }));
1899
+ select.dispatchEvent(new Event('change', { bubbles: true }));
1900
+ return { success: true, selectedValue: select.value };
1901
+ })()
1902
+ `);
1903
+ if (result?.success)
1904
+ return `Selected option: ${value}`;
1905
+ throw new Error(result?.error || "Failed to select option");
1906
+ }
1907
+ async checkElement(params) {
1908
+ const selector = params.selector;
1909
+ if (!selector)
1910
+ throw new Error("Selector required for check action");
1911
+ return this.setChecked({ selector, checked: true });
1912
+ }
1913
+ async setChecked(params) {
1914
+ const selector = params.selector;
1915
+ if (!selector)
1916
+ throw new Error("Selector required for checked state");
1917
+ await this.client.waitForSelector(selector);
1918
+ const before = await this.client.evaluate(`
1919
+ (function() {
1920
+ const el = document.querySelector(${JSON.stringify(selector)});
1921
+ if (!el) return { success: false, error: "Element not found" };
1922
+ if (el.type !== 'checkbox' && el.type !== 'radio') return { success: false, error: "Element is not a checkbox or radio" };
1923
+ if (el.disabled) return { success: false, error: "Element is disabled" };
1924
+ return { success: true, checked: !!el.checked, type: el.type };
1925
+ })()
1926
+ `);
1927
+ if (!before?.success)
1928
+ throw new Error(before?.error || "Failed to inspect checked state");
1929
+ const wanted = !!params.checked;
1930
+ if (before.checked === wanted)
1931
+ return `Checked state set to ${wanted}`;
1932
+ if (!(before.type === "radio" && !wanted)) {
1933
+ try {
1934
+ await this.clickElementBySelector({ selector });
1935
+ const afterClick = await this.client.evaluate(`
1936
+ (function() {
1937
+ const el = document.querySelector(${JSON.stringify(selector)});
1938
+ return el ? !!el.checked : null;
1939
+ })()
1940
+ `);
1941
+ if (afterClick === wanted)
1942
+ return `Checked state set to ${wanted}`;
1943
+ }
1944
+ catch {
1945
+ // Fall back to direct state setting below for reliability.
1946
+ }
1947
+ }
1948
+ const result = await this.client.evaluate(`
1949
+ (function() {
1950
+ const el = document.querySelector(${JSON.stringify(selector)});
1951
+ if (!el) return { success: false, error: "Element not found" };
1952
+ if (el.type !== 'checkbox' && el.type !== 'radio') return { success: false, error: "Element is not a checkbox or radio" };
1953
+ el.checked = ${JSON.stringify(!!params.checked)};
1954
+ el.dispatchEvent(new Event('input', { bubbles: true }));
1955
+ el.dispatchEvent(new Event('change', { bubbles: true }));
1956
+ return { success: true, checked: el.checked };
1957
+ })()
1958
+ `);
1959
+ if (result?.success)
1960
+ return `Checked state set to ${!!params.checked}`;
1961
+ throw new Error(result?.error || "Failed to set checked state");
1962
+ }
1963
+ async getAccessibilityTree(params) {
1964
+ return await this.client.getSimplifiedAccessibilityTree();
1965
+ }
1966
+ async getDOMTree(params) {
1967
+ const result = await this.client.getDOMSnapshot();
1968
+ return result;
1969
+ }
1970
+ async assertCondition(params) {
1971
+ const assertionType = params.assertionType || "element_exists";
1972
+ const selector = params.selector;
1973
+ const expectedText = params.expectedText || params.value || "";
1974
+ const result = await this.client.evaluate(`
1975
+ (function() {
1976
+ const selector = ${JSON.stringify(selector)};
1977
+ const type = ${JSON.stringify(assertionType)};
1978
+ const expectedText = ${JSON.stringify(expectedText)};
1979
+
1980
+ const el = selector ? document.querySelector(selector) : null;
1981
+
1982
+ switch(type) {
1983
+ case 'element_exists':
1984
+ return { success: !!el, message: el ? 'Element exists' : 'Element not found' };
1985
+ case 'element_not_exists':
1986
+ return { success: !el, message: !el ? 'Element does not exist' : 'Element found' };
1987
+ case 'element_contains_text':
1988
+ if (!el) return { success: false, message: 'Element not found' };
1989
+ const text = (el.innerText || el.textContent || '').trim();
1990
+ const matches = text.includes(expectedText);
1991
+ return { success: matches, message: matches ? 'Text matches' : 'Text does not match', actualText: text };
1992
+ case 'url_contains':
1993
+ const urlMatches = window.location.href.includes(expectedText);
1994
+ return { success: urlMatches, message: urlMatches ? 'URL contains text' : 'URL does not contain text' };
1995
+ default:
1996
+ return { success: false, message: 'Unknown assertion type' };
1997
+ }
1998
+ })()
1999
+ `);
2000
+ return result || { success: false, message: "Assertion failed" };
2001
+ }
2002
+ async getCookies(params) {
2003
+ const result = await this.client.sendCommand("Network.getCookies", {
2004
+ urls: [await this.client.evaluate("window.location.href").catch(() => "*") || "*"],
2005
+ });
2006
+ return result.cookies || [];
2007
+ }
2008
+ async setCookie(params) {
2009
+ const cookies = [{
2010
+ name: params.cookieName || params.name,
2011
+ value: params.cookieValue || params.value,
2012
+ url: params.url || await this.client.evaluate("window.location.href").catch(() => undefined),
2013
+ domain: params.domain,
2014
+ path: params.path || "/",
2015
+ secure: params.secure || false,
2016
+ httpOnly: params.httpOnly || false,
2017
+ }];
2018
+ await this.client.sendCommand("Network.setCookies", { cookies });
2019
+ return "Cookie set";
2020
+ }
2021
+ async clearCache(params) {
2022
+ await this.client.sendCommand("Network.clearBrowserCache", {});
2023
+ await this.client.sendCommand("Network.clearBrowserCookies", {});
2024
+ return "Cache cleared";
2025
+ }
2026
+ async setGeolocation(params) {
2027
+ await this.client.sendCommand("Emulation.setGeolocationOverride", {
2028
+ latitude: params.latitude,
2029
+ longitude: params.longitude,
2030
+ accuracy: params.accuracy || 100,
2031
+ });
2032
+ return `Geolocation set to ${params.latitude}, ${params.longitude}`;
2033
+ }
2034
+ async setTimezone(params) {
2035
+ await this.client.sendCommand("Emulation.setTimezoneOverride", {
2036
+ timezoneId: params.timezoneId,
2037
+ });
2038
+ return `Timezone set to ${params.timezoneId}`;
2039
+ }
2040
+ async emulateNetworkConditions(params) {
2041
+ await this.client.sendCommand("Network.emulateNetworkConditions", {
2042
+ offline: params.offline || false,
2043
+ latency: params.latency || 0,
2044
+ downloadThroughput: params.downloadThroughput || 0,
2045
+ uploadThroughput: params.uploadThroughput || 0,
2046
+ });
2047
+ return "Network conditions emulated";
2048
+ }
2049
+ async printPDF(params) {
2050
+ const result = await this.client.sendCommand("Page.printToPDF", {
2051
+ landscape: params.landscape || false,
2052
+ printBackground: params.printBackground || false,
2053
+ ...params.options,
2054
+ });
2055
+ return result.data; // base64 PDF
2056
+ }
2057
+ async highlightElements(params) {
2058
+ const result = await this.client.getInteractiveElements(true);
2059
+ return {
2060
+ success: true,
2061
+ elements: result.elements,
2062
+ message: `Highlighted ${result.elements.length} elements`,
2063
+ };
2064
+ }
2065
+ async verifyUIState(params) {
2066
+ const selector = params.selector;
2067
+ const expectedText = params.expectedText || params.value || "";
2068
+ const result = await this.client.evaluate(`
2069
+ (function() {
2070
+ const el = document.querySelector(${JSON.stringify(selector)});
2071
+ if (!el) return { exists: false, visible: false };
2072
+
2073
+ const rect = el.getBoundingClientRect();
2074
+ const computed = window.getComputedStyle(el);
2075
+ const visible = rect.width > 0 && rect.height > 0 &&
2076
+ computed.display !== 'none' &&
2077
+ computed.visibility !== 'hidden' &&
2078
+ computed.opacity !== '0';
2079
+
2080
+ return {
2081
+ exists: true,
2082
+ visible,
2083
+ text: (el.innerText || el.textContent || '').trim().substring(0, 200),
2084
+ bounds: { x: rect.left, y: rect.top, width: rect.width, height: rect.height }
2085
+ };
2086
+ })()
2087
+ `);
2088
+ return result || { exists: false, visible: false };
2089
+ }
2090
+ async getDOMStorage(params) {
2091
+ await this.client.sendCommand("DOMStorage.enable", {});
2092
+ const origin = params.origin || await this.client.evaluate("window.location.origin").catch(() => "");
2093
+ const result = await this.client.sendCommand("DOMStorage.getDOMStorageItems", {
2094
+ storageId: { securityOrigin: origin, isLocalStorage: params.type !== 'session' },
2095
+ });
2096
+ return result.entries || [];
2097
+ }
2098
+ async getNetworkTraffic(params) {
2099
+ return await this.client.getNetworkTraffic();
2100
+ }
2101
+ async getNetworkResponse(params) {
2102
+ const requestId = params.requestId;
2103
+ if (!requestId)
2104
+ throw new Error("requestId required");
2105
+ const result = await this.client.sendCommand("Network.getResponseBody", { requestId });
2106
+ return result;
2107
+ }
2108
+ async mockNetworkRequest(params) {
2109
+ const urlPattern = params.urlPattern;
2110
+ const mockResponse = params.mockResponse;
2111
+ if (!urlPattern)
2112
+ throw new Error("urlPattern required");
2113
+ this.mockRoutes.push({
2114
+ pattern: urlPattern,
2115
+ response: mockResponse || "{}"
2116
+ });
2117
+ await this.client.sendCommand("Fetch.enable", {
2118
+ patterns: this.mockRoutes.map(route => ({ urlPattern: route.pattern }))
2119
+ });
2120
+ if (!this.mockRouteListener) {
2121
+ this.mockRouteListener = async (event) => {
2122
+ const route = this.mockRoutes.find(item => this.matchesUrlPattern(event.request.url, item.pattern));
2123
+ if (!route) {
2124
+ await this.client.sendCommand("Fetch.continueRequest", { requestId: event.requestId }).catch(() => { });
2125
+ return;
2126
+ }
2127
+ await this.client.sendCommand("Fetch.fulfillRequest", {
2128
+ requestId: event.requestId,
2129
+ responseCode: 200,
2130
+ responseHeaders: [
2131
+ { name: "Content-Type", value: "application/json" },
2132
+ { name: "Access-Control-Allow-Origin", value: "*" }
2133
+ ],
2134
+ body: Buffer.from(route.response).toString("base64")
2135
+ }).catch(() => { });
2136
+ };
2137
+ this.client.on("Fetch.requestPaused", this.mockRouteListener);
2138
+ }
2139
+ return `Mocking enabled for pattern: ${urlPattern}`;
2140
+ }
2141
+ matchesUrlPattern(url, pattern) {
2142
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
2143
+ return new RegExp(`^${escaped}$`).test(url);
2144
+ }
2145
+ async getComputedStyle(params) {
2146
+ const selector = params.selector;
2147
+ const property = params.property;
2148
+ if (!selector)
2149
+ throw new Error("Selector required");
2150
+ const result = await this.client.evaluate(`
2151
+ (function() {
2152
+ const el = document.querySelector(${JSON.stringify(selector)});
2153
+ if (!el) return null;
2154
+ const style = window.getComputedStyle(el);
2155
+ ${property ? `return style.getPropertyValue(${JSON.stringify(property)});` : `return JSON.parse(JSON.stringify(style));`}
2156
+ })()
2157
+ `);
2158
+ return result;
2159
+ }
2160
+ async getEventListeners(params) {
2161
+ const selector = params.selector;
2162
+ if (!selector)
2163
+ throw new Error("Selector required");
2164
+ const result = await this.client.evaluate(`
2165
+ (function() {
2166
+ const el = document.querySelector(${JSON.stringify(selector)});
2167
+ if (!el) return null;
2168
+ // getEventListeners is Chrome DevTools specific, not available in page context
2169
+ return { message: "getEventListeners requires DevTools protocol, not available in page context" };
2170
+ })()
2171
+ `);
2172
+ return result;
2173
+ }
2174
+ screencastFrames = [];
2175
+ screencastFrameListener = null;
2176
+ mockRoutes = [];
2177
+ mockRouteListener = null;
2178
+ async getScreencastFrames(params) {
2179
+ const maxFrames = params.maxFrames || this.screencastFrames.length;
2180
+ const frames = this.screencastFrames.slice(-maxFrames);
2181
+ return {
2182
+ frameCount: frames.length,
2183
+ frames,
2184
+ message: `Retrieved ${frames.length} frames`
2185
+ };
2186
+ }
2187
+ async uploadFile(params) {
2188
+ const selector = params.selector;
2189
+ const files = params.files || [];
2190
+ if (!selector)
2191
+ throw new Error("Selector required for upload_file action");
2192
+ if (!files.length)
2193
+ throw new Error("No files specified");
2194
+ // Click the file input to activate it
2195
+ const result = await this.client.evaluate(`
2196
+ (function() {
2197
+ const el = document.querySelector(${JSON.stringify(selector)});
2198
+ if (!el) return { success: false, error: "Element not found" };
2199
+ if (el.type !== 'file') return { success: false, error: "Element is not a file input" };
2200
+ el.style.display = 'block'; // Make sure it's visible
2201
+ el.click();
2202
+ return { success: true };
2203
+ })()
2204
+ `);
2205
+ if (!result?.success) {
2206
+ throw new Error(result?.error || "Failed to activate file input");
2207
+ }
2208
+ const documentResult = await this.client.sendCommand("DOM.getDocument", {});
2209
+ const queryResult = await this.client.sendCommand("DOM.querySelector", {
2210
+ nodeId: documentResult.root.nodeId,
2211
+ selector
2212
+ });
2213
+ if (!queryResult.nodeId) {
2214
+ throw new Error(`File input not found: ${selector}`);
2215
+ }
2216
+ await this.client.sendCommand("DOM.setFileInputFiles", {
2217
+ files,
2218
+ nodeId: queryResult.nodeId
2219
+ });
2220
+ return "File upload completed";
2221
+ }
2222
+ async configureBrowser(params) {
2223
+ const { network, emulation, script } = params;
2224
+ const results = [];
2225
+ // Configure network settings
2226
+ if (network) {
2227
+ if (network.blockImages) {
2228
+ await this.client.sendCommand("Network.setBlockedURLs", {
2229
+ urls: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.webp", "*.svg"]
2230
+ });
2231
+ results.push("Blocked images");
2232
+ }
2233
+ if (network.blockCSS) {
2234
+ await this.client.sendCommand("Network.setBlockedURLs", {
2235
+ urls: ["*.css"]
2236
+ });
2237
+ results.push("Blocked CSS");
2238
+ }
2239
+ if (network.blockAds) {
2240
+ await this.client.sendCommand("Network.setBlockedURLs", {
2241
+ urls: ["*doubleclick.net*", "*googlesyndication.com*", "*adservice.*"]
2242
+ });
2243
+ results.push("Blocked ads");
2244
+ }
2245
+ }
2246
+ // Configure emulation settings
2247
+ if (emulation) {
2248
+ if (emulation.width && emulation.height) {
2249
+ await this.client.sendCommand("Emulation.setDeviceMetricsOverride", {
2250
+ width: emulation.width,
2251
+ height: emulation.height,
2252
+ deviceScaleFactor: emulation.scale || 1,
2253
+ mobile: emulation.mobile || false,
2254
+ });
2255
+ results.push(`Emulated device: ${emulation.width}x${emulation.height}`);
2256
+ }
2257
+ if (emulation.userAgent) {
2258
+ await this.client.sendCommand("Emulation.setUserAgentOverride", {
2259
+ userAgent: emulation.userAgent,
2260
+ });
2261
+ results.push("Set custom user agent");
2262
+ }
2263
+ }
2264
+ // Configure scripts
2265
+ if (script?.onLoad) {
2266
+ await this.client.sendCommand("Page.addScriptToEvaluateOnNewDocument", {
2267
+ source: script.onLoad,
2268
+ });
2269
+ results.push("Added script to run on new documents");
2270
+ }
2271
+ return {
2272
+ success: true,
2273
+ configured: results,
2274
+ message: results.join(", ") || "No configuration applied"
2275
+ };
2276
+ }
2277
+ async launchBrowser(options) {
2278
+ const client = (0, cdp_client_1.getCdpClient)();
2279
+ await client.launchAuto({
2280
+ browser: options?.browser,
2281
+ headless: options?.headless,
2282
+ port: options?.port,
2283
+ profile: options?.profile,
2284
+ profileDirectory: options?.profileDirectory,
2285
+ userDataDir: options?.userDataDir,
2286
+ });
2287
+ const profileLabel = options?.profile || options?.profileDirectory;
2288
+ return profileLabel
2289
+ ? `Browser launched successfully with profile "${profileLabel}"`
2290
+ : "Browser launched successfully";
2291
+ }
2292
+ async killBrowser() {
2293
+ const client = (0, cdp_client_1.getCdpClient)();
2294
+ await client.killBrowser();
2295
+ return "Browser killed";
2296
+ }
2297
+ async listBrowsers() {
2298
+ const client = (0, cdp_client_1.getCdpClient)();
2299
+ return await client.listAvailableBrowsers();
2300
+ }
2301
+ async listBrowserProfiles(browser) {
2302
+ const client = (0, cdp_client_1.getCdpClient)();
2303
+ return await client.listBrowserProfiles(browser || "brave");
2304
+ }
2305
+ async getTargetsViaHttp() {
2306
+ // This is a simplified version - in reality we'd need to know the port
2307
+ return [];
2308
+ }
2309
+ }
2310
+ exports.CdpBridge = CdpBridge;
2311
+ let bridgeInstance = null;
2312
+ function getCdpBridge() {
2313
+ if (!bridgeInstance) {
2314
+ bridgeInstance = new CdpBridge();
2315
+ }
2316
+ return bridgeInstance;
2317
+ }