@weavetab/mcp 2.5.0-beta.1 → 2.5.0-beta.4
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/AGENTS.md +2 -1
- package/ARCHITECTURE.md +6 -6
- package/LICENSE +1 -1
- package/README.md +164 -802
- package/SECURITY.md +33 -22
- package/TROUBLESHOOTING.md +3 -3
- package/dist/cdp/bridge.d.ts +10 -0
- package/dist/cdp/bridge.js +285 -78
- package/dist/cdp/connector.d.ts +7 -3
- package/dist/cdp/connector.js +196 -75
- package/dist/cdp/helpers.d.ts +4 -0
- package/dist/cdp/helpers.js +181 -0
- package/dist/cdp/lock.d.ts +9 -6
- package/dist/cdp/lock.js +72 -59
- package/dist/cdp/omni_broker.d.ts +2 -1
- package/dist/cdp/omni_broker.js +13 -7
- package/dist/cdp/profiles.d.ts +10 -0
- package/dist/cdp/profiles.js +65 -0
- package/dist/cdp/walker.d.ts +25 -1
- package/dist/cdp/walker.js +354 -7
- package/dist/cli/plugin.d.ts +0 -2
- package/dist/cli/plugin.js +51 -696
- package/dist/cli/updater.d.ts +1 -1
- package/dist/cli/updater.js +27 -7
- package/dist/config/loader.d.ts +33 -6
- package/dist/config/loader.js +174 -22
- package/dist/desktop/discovery.d.ts +44 -0
- package/dist/desktop/discovery.js +364 -0
- package/dist/index.js +140 -64
- package/dist/intelligence/marks.d.ts +36 -0
- package/dist/intelligence/marks.js +30 -0
- package/dist/intelligence/patternCompiler.d.ts +118 -0
- package/dist/intelligence/patternCompiler.js +259 -0
- package/dist/intelligence/sessionMemory.d.ts +2 -0
- package/dist/intelligence/sessionMemory.js +18 -8
- package/dist/overlay/ghost.js +2 -2
- package/dist/overlay/injector.js +3 -5
- package/dist/plugin/extension_bridge.d.ts +40 -0
- package/dist/plugin/extension_bridge.js +158 -0
- package/dist/plugin/interface.d.ts +151 -15
- package/dist/plugin/loader.d.ts +19 -1
- package/dist/plugin/loader.js +331 -63
- package/dist/plugin/pipeline.d.ts +75 -0
- package/dist/plugin/pipeline.js +183 -0
- package/dist/plugin/registry.js +12 -1
- package/dist/plugin/storage.d.ts +51 -0
- package/dist/plugin/storage.js +164 -0
- package/dist/security/secrets.d.ts +1 -0
- package/dist/security/secrets.js +13 -0
- package/dist/sensors/actionability.js +1 -2
- package/dist/sensors/guard.js +4 -10
- package/dist/sensors/mutations.js +12 -0
- package/dist/sensors/operation_monitor.js +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +600 -236
- package/dist/state/agent.d.ts +1 -0
- package/dist/state/agent.js +3 -0
- package/dist/state/delta.d.ts +12 -4
- package/dist/state/delta.js +67 -14
- package/dist/state/session.d.ts +8 -2
- package/dist/state/session.js +60 -13
- package/dist/tools/browser_detect.d.ts +16 -2
- package/dist/tools/browser_detect.js +54 -6
- package/dist/tools/burst.d.ts +17 -7
- package/dist/tools/burst.js +240 -83
- package/dist/tools/checkpoint.d.ts +38 -0
- package/dist/tools/checkpoint.js +271 -0
- package/dist/tools/click.d.ts +1 -0
- package/dist/tools/click.js +97 -98
- package/dist/tools/clipboard.js +31 -24
- package/dist/tools/design_extract.js +1 -1
- package/dist/tools/eval.js +1 -1
- package/dist/tools/fill.d.ts +20 -1
- package/dist/tools/fill.js +199 -40
- package/dist/tools/find.d.ts +16 -0
- package/dist/tools/find.js +67 -44
- package/dist/tools/github/index.d.ts +19 -4
- package/dist/tools/github/index.js +40 -4
- package/dist/tools/highlight.d.ts +6 -2
- package/dist/tools/highlight.js +158 -72
- package/dist/tools/inspect.js +1 -1
- package/dist/tools/key.js +65 -56
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +57 -44
- package/dist/tools/pdf.js +21 -13
- package/dist/tools/pointer.d.ts +18 -3
- package/dist/tools/pointer.js +89 -1
- package/dist/tools/read.d.ts +28 -8
- package/dist/tools/read.js +230 -56
- package/dist/tools/scrape.js +67 -13
- package/dist/tools/screenshot.js +21 -16
- package/dist/tools/scroll.js +6 -11
- package/dist/tools/select.js +8 -2
- package/dist/tools/snapshot.js +3 -34
- package/dist/tools/tabs.d.ts +10 -10
- package/dist/tools/tabs.js +55 -39
- package/dist/tools/thought.d.ts +2 -0
- package/dist/tools/thought.js +13 -14
- package/dist/tools/type.d.ts +2 -0
- package/dist/tools/type.js +109 -60
- package/dist/tools/type_secret.js +42 -76
- package/dist/tools/upload.js +1 -1
- package/dist/tools/video_compiler.d.ts +1 -1
- package/dist/tools/video_compiler.js +11 -12
- package/dist/version.d.ts +5 -0
- package/dist/version.js +5 -0
- package/docs/DOCUMENTATION.md +82 -36
- package/docs/PLUGINS.md +82 -499
- package/docs/TOOLS.md +51 -99
- package/docs/architecture/blind-injection.md +83 -0
- package/docs/architecture/dual-injection.md +32 -0
- package/docs/architecture/overview.md +49 -0
- package/docs/architecture/security.md +35 -0
- package/docs/architecture/transport.md +37 -0
- package/docs/captcha-stealth/captcha-solver.md +75 -0
- package/docs/captcha-stealth/stealth-physics.md +60 -0
- package/docs/cli/cli-commands.md +44 -0
- package/docs/cli/cli-flags.md +20 -0
- package/docs/cli/cli-introduction.md +20 -0
- package/docs/cli/cli-scripting.md +32 -0
- package/docs/cli/g-ide/ide-antigravity.md +24 -0
- package/docs/cli/g-ide/ide-claude-code.md +24 -0
- package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
- package/docs/cli/g-ide/ide-cursor.md +31 -0
- package/docs/cli/g-ide/ide-generic.md +34 -0
- package/docs/cli/g-ide/ide-opencode.md +24 -0
- package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
- package/docs/cli/g-ide/ide-windsurf.md +24 -0
- package/docs/configuration/config-locations.md +24 -0
- package/docs/configuration/configuration.md +73 -0
- package/docs/configuration/env-variables.md +19 -0
- package/docs/configuration/folders.md +55 -0
- package/docs/desktop-automation/electron-apps.md +217 -0
- package/docs/get-started/installation.md +47 -0
- package/docs/get-started/introduction.md +60 -0
- package/docs/get-started/quickstart.md +54 -0
- package/docs/get-started/setup-wizard.md +26 -0
- package/docs/intelligence/site-profiles.md +151 -0
- package/docs/skills-ecosystem/skills-overview.md +35 -0
- package/docs/skills-ecosystem/skills-recipes.md +36 -0
- package/docs/tools/tools.md +631 -0
- package/docs/troubleshooting/trouble-browser.md +27 -0
- package/docs/troubleshooting/trouble-config.md +20 -0
- package/docs/troubleshooting/trouble-loop.md +31 -0
- package/ext-dist/background.js +10 -62
- package/ext-dist/content.js +333 -921
- package/ext-dist/hud/hud.js +9 -9
- package/ext-dist/manifest.json +2 -6
- package/ext-dist/styles.css +6 -1
- package/mcp.json +108 -72
- package/package.json +14 -9
- package/server.json +3 -3
- package/docs/DEV_MODE.md +0 -68
- package/docs/dev_plugin.md +0 -310
- package/ext-dist/sidepanel/sidepanel.js +0 -25
- package/ext-dist/sidepanel.html +0 -547
|
@@ -630,7 +630,7 @@ ${tailwindPath ? "4. `tailwind.theme.json` — Tailwind CSS `theme.extend` confi
|
|
|
630
630
|
htmlBytes: beautifulHtml.length
|
|
631
631
|
}
|
|
632
632
|
},
|
|
633
|
-
message: `
|
|
633
|
+
message: `design saved to ${targetDir}. Saved tokens.json, template.html, styles.css, and README.md.`
|
|
634
634
|
};
|
|
635
635
|
}
|
|
636
636
|
finally {
|
package/dist/tools/eval.js
CHANGED
|
@@ -77,7 +77,7 @@ export async function browserEval(session, options, _config) {
|
|
|
77
77
|
// result.value is undefined and result.description is unhelpfully a type string.
|
|
78
78
|
// Return a clear message so agents know the eval succeeded — it just returned no value.
|
|
79
79
|
const rawResult = result.type === 'undefined'
|
|
80
|
-
? "(
|
|
80
|
+
? "(eval returned undefined. append ; 'ok' or return a string explicitly.)"
|
|
81
81
|
: result.value !== undefined ? result.value : result.description;
|
|
82
82
|
const redactedResult = typeof rawResult === 'string'
|
|
83
83
|
? SessionSecretTracker.redact(targetId, rawResult)
|
package/dist/tools/fill.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import CDP from "chrome-remote-interface";
|
|
2
2
|
import type { Config } from "../config/loader.js";
|
|
3
3
|
export interface FillField {
|
|
4
|
-
ref
|
|
4
|
+
ref?: string;
|
|
5
|
+
selector?: string;
|
|
6
|
+
target?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
placeholder?: string;
|
|
5
10
|
value: string;
|
|
6
11
|
}
|
|
7
12
|
/** A single cell write for Google Sheets / Canvas grid filling. */
|
|
@@ -15,6 +20,8 @@ export interface FillOptions {
|
|
|
15
20
|
fields?: FillField[];
|
|
16
21
|
submit?: string;
|
|
17
22
|
scan_only?: boolean;
|
|
23
|
+
scan?: boolean;
|
|
24
|
+
inputs?: boolean;
|
|
18
25
|
blast_mode?: boolean;
|
|
19
26
|
/** Grid-fill mode for Google Sheets and other canvas-rendered spreadsheets.
|
|
20
27
|
* Provide cell addresses in A1 notation with their values.
|
|
@@ -27,6 +34,18 @@ export interface FillResult {
|
|
|
27
34
|
succeeded: number;
|
|
28
35
|
failed: number;
|
|
29
36
|
submitted: boolean;
|
|
37
|
+
inputs?: Array<{
|
|
38
|
+
ref: string;
|
|
39
|
+
tag: string;
|
|
40
|
+
type: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
label?: string;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
value: string;
|
|
45
|
+
focused?: boolean;
|
|
46
|
+
disabled?: boolean;
|
|
47
|
+
visible?: boolean;
|
|
48
|
+
}>;
|
|
30
49
|
results: {
|
|
31
50
|
ref: string;
|
|
32
51
|
field_type: string;
|
package/dist/tools/fill.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
|
+
import { getConfigAgentId } from "../config/loader.js";
|
|
1
2
|
import { logAction, logDebug } from "../audit/logger.js";
|
|
2
3
|
import { recordAction, detectStuckLoop, detectCrossToolLoop, trackToolCall } from "../intelligence/trail.js";
|
|
4
|
+
import { registerAndHardenSecretField } from "../cdp/helpers.js";
|
|
3
5
|
import { confirmChange } from "../cdp/confirm.js";
|
|
4
6
|
import { verifyActionability } from "../sensors/actionability.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
import { sessionRegistry } from "../state/session.js";
|
|
8
|
+
import { recoverDetachedRef } from "../cdp/walker.js";
|
|
9
|
+
function toSelector(fieldOrRef) {
|
|
10
|
+
if (typeof fieldOrRef === "string") {
|
|
11
|
+
const refId = fieldOrRef.startsWith("w:") ? fieldOrRef.split(":")[1] : null;
|
|
12
|
+
return refId ? `[data-wt-id="${refId}"]` : fieldOrRef;
|
|
13
|
+
}
|
|
14
|
+
const f = fieldOrRef;
|
|
15
|
+
if (f.ref) {
|
|
16
|
+
const refId = f.ref.startsWith("w:") ? f.ref.split(":")[1] : null;
|
|
17
|
+
return refId ? `[data-wt-id="${refId}"]` : f.ref;
|
|
18
|
+
}
|
|
19
|
+
if (f.selector)
|
|
20
|
+
return f.selector;
|
|
21
|
+
if (f.target)
|
|
22
|
+
return f.target;
|
|
23
|
+
if (f.name)
|
|
24
|
+
return `[name="${f.name}"]`;
|
|
25
|
+
if (f.placeholder)
|
|
26
|
+
return `[placeholder="${f.placeholder}"]`;
|
|
27
|
+
return "";
|
|
8
28
|
}
|
|
9
29
|
function esc(s) {
|
|
10
30
|
return JSON.stringify(s);
|
|
@@ -48,7 +68,15 @@ async function detectFieldType(session, selector) {
|
|
|
48
68
|
if (type === 'checkbox' || type === 'radio' || type === 'custom-checkbox' || type === 'custom-radio') {
|
|
49
69
|
fillable = !el.disabled && ariaDisabled !== 'true';
|
|
50
70
|
}
|
|
51
|
-
var
|
|
71
|
+
var secretRegex = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha/i;
|
|
72
|
+
var isSecret = el.hasAttribute('data-wt-secret') ||
|
|
73
|
+
(el.tagName === 'INPUT' && (
|
|
74
|
+
t === 'password' ||
|
|
75
|
+
secretRegex.test(el.getAttribute('autocomplete') || '') ||
|
|
76
|
+
secretRegex.test(el.getAttribute('name') || '') ||
|
|
77
|
+
secretRegex.test(el.getAttribute('id') || '') ||
|
|
78
|
+
secretRegex.test(el.getAttribute('aria-label') || '')
|
|
79
|
+
));
|
|
52
80
|
return JSON.stringify({ exists: true, type: type, fillable: fillable, isSecret: isSecret });
|
|
53
81
|
})()`,
|
|
54
82
|
returnByValue: true,
|
|
@@ -72,11 +100,20 @@ async function getFieldState(session, selector, type) {
|
|
|
72
100
|
var inner = el.querySelector('input, textarea, select');
|
|
73
101
|
if (inner) el = inner;
|
|
74
102
|
}
|
|
75
|
-
var
|
|
103
|
+
var secretRegex = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha/i;
|
|
104
|
+
var isSecret = el.hasAttribute('data-wt-secret') ||
|
|
105
|
+
(el.tagName === 'INPUT' && (
|
|
106
|
+
(el.type || '').toLowerCase() === 'password' ||
|
|
107
|
+
secretRegex.test(el.getAttribute('autocomplete') || '') ||
|
|
108
|
+
secretRegex.test(el.getAttribute('name') || '') ||
|
|
109
|
+
secretRegex.test(el.getAttribute('id') || '') ||
|
|
110
|
+
secretRegex.test(el.getAttribute('aria-label') || '')
|
|
111
|
+
));
|
|
112
|
+
var s = { exists: "true", isSecret: isSecret ? "true" : "false" };
|
|
76
113
|
if (el.tagName === 'SELECT') {
|
|
77
114
|
var idx = el.selectedIndex;
|
|
78
115
|
s.selectedIndex = String(idx);
|
|
79
|
-
s.value = el.value;
|
|
116
|
+
s.value = isSecret ? '[REDACTED]' : el.value;
|
|
80
117
|
s.text = idx >= 0 ? el.options[idx].text : "";
|
|
81
118
|
} else if (el.type === 'checkbox' || el.type === 'radio' || type === 'custom-checkbox' || type === 'custom-radio') {
|
|
82
119
|
// Native checkbox/radio uses .checked; ARIA elements use aria-checked
|
|
@@ -85,9 +122,9 @@ async function getFieldState(session, selector, type) {
|
|
|
85
122
|
s.checked = isChecked ? "true" : "false";
|
|
86
123
|
if (ariaChecked !== null) s.ariaChecked = ariaChecked;
|
|
87
124
|
} else if (el.isContentEditable) {
|
|
88
|
-
s.text = el.textContent || "";
|
|
125
|
+
s.text = isSecret ? '[REDACTED]' : (el.textContent || "");
|
|
89
126
|
} else {
|
|
90
|
-
s.value = el.value;
|
|
127
|
+
s.value = isSecret ? '[REDACTED]' : el.value;
|
|
91
128
|
}
|
|
92
129
|
return JSON.stringify(s);
|
|
93
130
|
})(${esc(type)})`,
|
|
@@ -395,13 +432,102 @@ export async function browserFill(session, options, config) {
|
|
|
395
432
|
}
|
|
396
433
|
const startMs = Date.now();
|
|
397
434
|
const results = [];
|
|
435
|
+
// ── Input discovery mode: scan and return all inputs on the page ──
|
|
436
|
+
if (options.scan || options.inputs || (!options.fields || options.fields.length === 0)) {
|
|
437
|
+
try {
|
|
438
|
+
const scanRes = await session.Runtime.evaluate({
|
|
439
|
+
expression: `(function() {
|
|
440
|
+
const all = Array.from(document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="image"]), textarea, select, [role="textbox"], [role="searchbox"], [role="combobox"], [role="checkbox"], [role="radio"], [role="switch"], [contenteditable="true"]'));
|
|
441
|
+
const activeEl = document.activeElement;
|
|
442
|
+
|
|
443
|
+
return JSON.stringify(all.map((el, i) => {
|
|
444
|
+
const rect = el.getBoundingClientRect();
|
|
445
|
+
const isVisible = rect.width > 0 && rect.height > 0 && rect.top < window.innerHeight && rect.bottom > 0;
|
|
446
|
+
const tag = el.tagName.toLowerCase();
|
|
447
|
+
const type = (el.getAttribute('type') || (tag === 'textarea' ? 'textarea' : tag === 'select' ? 'select' : 'text')).toLowerCase();
|
|
448
|
+
const role = el.getAttribute('role') || '';
|
|
449
|
+
|
|
450
|
+
let label = el.getAttribute('aria-label') || el.getAttribute('placeholder') || el.getAttribute('title') || '';
|
|
451
|
+
if (!label && el.id) {
|
|
452
|
+
const lbl = document.querySelector('label[for="' + el.id + '"]');
|
|
453
|
+
if (lbl) label = lbl.innerText.trim();
|
|
454
|
+
}
|
|
455
|
+
if (!label && el.closest('label')) {
|
|
456
|
+
label = el.closest('label').innerText.trim();
|
|
457
|
+
}
|
|
458
|
+
if (!label) {
|
|
459
|
+
label = el.getAttribute('name') || '';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
var secretRegex = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha/i;
|
|
463
|
+
var isSecret = el.hasAttribute('data-wt-secret') ||
|
|
464
|
+
(el.tagName === 'INPUT' && (
|
|
465
|
+
type === 'password' ||
|
|
466
|
+
secretRegex.test(el.getAttribute('autocomplete') || '') ||
|
|
467
|
+
secretRegex.test(el.getAttribute('name') || '') ||
|
|
468
|
+
secretRegex.test(el.getAttribute('id') || '') ||
|
|
469
|
+
secretRegex.test(el.getAttribute('aria-label') || '')
|
|
470
|
+
));
|
|
471
|
+
|
|
472
|
+
let val = '';
|
|
473
|
+
if (isSecret) {
|
|
474
|
+
val = '[REDACTED]';
|
|
475
|
+
} else if (type === 'checkbox' || type === 'radio' || role === 'checkbox' || role === 'radio' || role === 'switch') {
|
|
476
|
+
val = el.checked !== undefined ? String(el.checked) : (el.getAttribute('aria-checked') || 'false');
|
|
477
|
+
} else if (tag === 'select') {
|
|
478
|
+
val = el.options && el.selectedIndex >= 0 && el.options[el.selectedIndex] ? el.options[el.selectedIndex].text : (el.value || '');
|
|
479
|
+
} else if (el.isContentEditable) {
|
|
480
|
+
val = (el.textContent || '').trim().slice(0, 40);
|
|
481
|
+
} else {
|
|
482
|
+
val = el.value !== undefined ? String(el.value).slice(0, 40) : '';
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const wtId = el.getAttribute('data-wt-id');
|
|
486
|
+
const ref = wtId ? ('w:' + wtId) : (el.id ? ('#' + el.id) : (el.name ? ('[name="' + el.name + '"]') : ('input:' + i)));
|
|
487
|
+
const isFocused = el === activeEl || (activeEl && el.contains(activeEl));
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
ref,
|
|
491
|
+
tag,
|
|
492
|
+
type: role || type,
|
|
493
|
+
name: el.getAttribute('name') || undefined,
|
|
494
|
+
label: label.slice(0, 50) || undefined,
|
|
495
|
+
placeholder: el.getAttribute('placeholder') || undefined,
|
|
496
|
+
value: val,
|
|
497
|
+
focused: isFocused ? true : undefined,
|
|
498
|
+
disabled: (el.disabled || el.getAttribute('aria-disabled') === 'true') ? true : undefined,
|
|
499
|
+
visible: isVisible
|
|
500
|
+
};
|
|
501
|
+
}));
|
|
502
|
+
})()`,
|
|
503
|
+
returnByValue: true,
|
|
504
|
+
});
|
|
505
|
+
const inputsList = JSON.parse(scanRes.result?.value || "[]");
|
|
506
|
+
return {
|
|
507
|
+
success: true,
|
|
508
|
+
total_fields: inputsList.length,
|
|
509
|
+
succeeded: inputsList.length,
|
|
510
|
+
failed: 0,
|
|
511
|
+
submitted: false,
|
|
512
|
+
inputs: inputsList,
|
|
513
|
+
results: [],
|
|
514
|
+
execution_ms: Date.now() - startMs,
|
|
515
|
+
suggestedNext: inputsList.length > 0
|
|
516
|
+
? `Discovered ${inputsList.length} inputs. Call browser_fill with { fields: [{ ref, value }] } to fill.`
|
|
517
|
+
: "No editable inputs found on this page.",
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
logDebug(`[browser_fill] Input scan failed: ${e}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
398
524
|
if (!options.fields || options.fields.length === 0) {
|
|
399
|
-
return { success: false, total_fields: 0, succeeded: 0, failed: 0, submitted: false, results: [], execution_ms: 0, suggestedNext: "No fields provided.
|
|
525
|
+
return { success: false, total_fields: 0, succeeded: 0, failed: 0, submitted: false, results: [], execution_ms: 0, suggestedNext: "No fields provided. Call browser_fill with scan:true to see all inputs." };
|
|
400
526
|
}
|
|
401
527
|
// ── scan_only: return field schema without mutating DOM ──
|
|
402
528
|
if (options.scan_only) {
|
|
403
529
|
for (const field of options.fields) {
|
|
404
|
-
const selector = toSelector(field
|
|
530
|
+
const selector = toSelector(field);
|
|
405
531
|
const detected = await detectFieldType(session, selector);
|
|
406
532
|
// Report success=false for non-fillable fields (disabled, readonly, unknown
|
|
407
533
|
// type) so scan mode doesn't give false confidence before the actual fill.
|
|
@@ -414,7 +540,7 @@ export async function browserFill(session, options, config) {
|
|
|
414
540
|
? `Detected: ${detected.type} (not fillable — disabled or readonly)`
|
|
415
541
|
: `Detected: ${detected.type}`;
|
|
416
542
|
results.push({
|
|
417
|
-
ref: field.ref,
|
|
543
|
+
ref: field.ref || field.selector || field.name || field.label || 'field',
|
|
418
544
|
field_type: detected.exists ? detected.type : "not_found",
|
|
419
545
|
success: scanSuccess,
|
|
420
546
|
message: scanMsg,
|
|
@@ -430,18 +556,34 @@ export async function browserFill(session, options, config) {
|
|
|
430
556
|
execution_ms: Date.now() - startMs,
|
|
431
557
|
};
|
|
432
558
|
}
|
|
559
|
+
let targetId = "default";
|
|
560
|
+
const active = sessionRegistry.getActive(getConfigAgentId(config));
|
|
561
|
+
if (active)
|
|
562
|
+
targetId = active.targetId;
|
|
433
563
|
// ── blast_mode: bulk fill in single CDP roundtrip ──
|
|
434
564
|
if (options.blast_mode) {
|
|
435
565
|
// blast_mode: single CDP round-trip filling all fields at once.
|
|
436
566
|
// Handles text, checkbox, radio, select, contenteditable, and ARIA variants.
|
|
437
567
|
const blastExpression = `(function(fields) {
|
|
438
568
|
var results = [];
|
|
569
|
+
var secretRegex = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha/i;
|
|
439
570
|
fields.forEach(function(f) {
|
|
440
571
|
var el = document.querySelector(f.selector);
|
|
441
|
-
if (!el) { results.push({ ref: f.ref, ok: false, msg: 'not found', type: 'unknown' }); return; }
|
|
572
|
+
if (!el) { results.push({ ref: f.ref, ok: false, msg: 'not found', type: 'unknown', isSecret: false }); return; }
|
|
442
573
|
var tag = el.tagName.toLowerCase();
|
|
443
574
|
var inputType = (el.getAttribute('type') || '').toLowerCase();
|
|
444
575
|
var role = (el.getAttribute('role') || '').toLowerCase();
|
|
576
|
+
var isSecret = el.hasAttribute('data-wt-secret') ||
|
|
577
|
+
(tag === 'input' && (
|
|
578
|
+
inputType === 'password' ||
|
|
579
|
+
secretRegex.test(el.getAttribute('autocomplete') || '') ||
|
|
580
|
+
secretRegex.test(el.getAttribute('name') || '') ||
|
|
581
|
+
secretRegex.test(el.getAttribute('id') || '') ||
|
|
582
|
+
secretRegex.test(el.getAttribute('aria-label') || '')
|
|
583
|
+
));
|
|
584
|
+
if (isSecret) {
|
|
585
|
+
try { el.setAttribute('data-wt-secret', 'true'); } catch(_) {}
|
|
586
|
+
}
|
|
445
587
|
try {
|
|
446
588
|
if (tag === 'select') {
|
|
447
589
|
// Select element
|
|
@@ -450,12 +592,12 @@ export async function browserFill(session, options, config) {
|
|
|
450
592
|
var opt = el.options[j];
|
|
451
593
|
if (opt.value === f.value || opt.text === f.value || opt.text.trim() === f.value.trim()) { foundIdx = j; break; }
|
|
452
594
|
}
|
|
453
|
-
if (foundIdx === -1) { results.push({ ref: f.ref, ok: false, msg: 'Option not found: ' + f.value, type: 'select' }); return; }
|
|
595
|
+
if (foundIdx === -1) { results.push({ ref: f.ref, ok: false, msg: 'Option not found: ' + f.value, type: 'select', isSecret: isSecret }); return; }
|
|
454
596
|
var nativeSS = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'value')?.set;
|
|
455
597
|
if (nativeSS) nativeSS.call(el, el.options[foundIdx].value); else el.selectedIndex = foundIdx;
|
|
456
598
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
457
599
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
458
|
-
results.push({ ref: f.ref, ok: true, msg: 'selected:' + el.options[foundIdx].text, type: 'select' });
|
|
600
|
+
results.push({ ref: f.ref, ok: true, msg: 'selected:' + el.options[foundIdx].text, type: 'select', isSecret: isSecret });
|
|
459
601
|
} else if (tag === 'input' && inputType === 'checkbox') {
|
|
460
602
|
// Native checkbox
|
|
461
603
|
var want = f.value === 'true' || f.value === 'yes' || f.value === 'checked' || f.value === '1' || f.value === 'on';
|
|
@@ -465,7 +607,7 @@ export async function browserFill(session, options, config) {
|
|
|
465
607
|
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
466
608
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
467
609
|
}
|
|
468
|
-
results.push({ ref: f.ref, ok: true, msg: want ? 'checked' : 'unchecked', type: 'checkbox' });
|
|
610
|
+
results.push({ ref: f.ref, ok: true, msg: want ? 'checked' : 'unchecked', type: 'checkbox', isSecret: isSecret });
|
|
469
611
|
} else if (tag === 'input' && inputType === 'radio') {
|
|
470
612
|
// Native radio
|
|
471
613
|
if (!el.checked) {
|
|
@@ -474,7 +616,7 @@ export async function browserFill(session, options, config) {
|
|
|
474
616
|
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
475
617
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
476
618
|
}
|
|
477
|
-
results.push({ ref: f.ref, ok: true, msg: 'selected', type: 'radio' });
|
|
619
|
+
results.push({ ref: f.ref, ok: true, msg: 'selected', type: 'radio', isSecret: isSecret });
|
|
478
620
|
} else if (role === 'checkbox' || role === 'switch') {
|
|
479
621
|
// ARIA custom checkbox
|
|
480
622
|
var wantAria = f.value === 'true' || f.value === 'yes' || f.value === 'checked' || f.value === '1' || f.value === 'on';
|
|
@@ -485,7 +627,7 @@ export async function browserFill(session, options, config) {
|
|
|
485
627
|
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
486
628
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
487
629
|
}
|
|
488
|
-
results.push({ ref: f.ref, ok: true, msg: wantAria ? 'checked' : 'unchecked', type: 'custom-checkbox' });
|
|
630
|
+
results.push({ ref: f.ref, ok: true, msg: wantAria ? 'checked' : 'unchecked', type: 'custom-checkbox', isSecret: isSecret });
|
|
489
631
|
} else if (role === 'radio') {
|
|
490
632
|
// ARIA custom radio
|
|
491
633
|
if (el.getAttribute('aria-checked') !== 'true') {
|
|
@@ -494,7 +636,7 @@ export async function browserFill(session, options, config) {
|
|
|
494
636
|
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
495
637
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
496
638
|
}
|
|
497
|
-
results.push({ ref: f.ref, ok: true, msg: 'selected', type: 'custom-radio' });
|
|
639
|
+
results.push({ ref: f.ref, ok: true, msg: 'selected', type: 'custom-radio', isSecret: isSecret });
|
|
498
640
|
} else if (el.isContentEditable || el.getAttribute('contenteditable') === 'true' || el.getAttribute('contenteditable') === '') {
|
|
499
641
|
// contenteditable
|
|
500
642
|
el.focus();
|
|
@@ -502,7 +644,7 @@ export async function browserFill(session, options, config) {
|
|
|
502
644
|
try { document.execCommand('selectAll', false, null); document.execCommand('insertText', false, f.value); } catch(e2) {}
|
|
503
645
|
el.dispatchEvent(new InputEvent('input', { bubbles: true, data: f.value, inputType: 'insertText' }));
|
|
504
646
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
505
|
-
results.push({ ref: f.ref, ok: true, msg: 'filled', type: 'contenteditable' });
|
|
647
|
+
results.push({ ref: f.ref, ok: true, msg: 'filled', type: 'contenteditable', isSecret: isSecret });
|
|
506
648
|
} else {
|
|
507
649
|
// text / textarea / other inputs
|
|
508
650
|
el.focus();
|
|
@@ -511,31 +653,38 @@ export async function browserFill(session, options, config) {
|
|
|
511
653
|
if (ns2) ns2.call(el, f.value); else el.value = f.value;
|
|
512
654
|
el.dispatchEvent(new InputEvent('input', { bubbles: true, data: f.value, inputType: 'insertText' }));
|
|
513
655
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
514
|
-
results.push({ ref: f.ref, ok: true, msg: 'filled', type: tag });
|
|
656
|
+
results.push({ ref: f.ref, ok: true, msg: 'filled', type: tag, isSecret: isSecret });
|
|
515
657
|
}
|
|
516
|
-
} catch(e) { results.push({ ref: f.ref, ok: false, msg: e.message || String(e), type: tag }); }
|
|
658
|
+
} catch(e) { results.push({ ref: f.ref, ok: false, msg: e.message || String(e), type: tag, isSecret: isSecret }); }
|
|
517
659
|
});
|
|
518
660
|
return JSON.stringify(results);
|
|
519
|
-
})(${JSON.stringify(options.fields.map(f => ({ ref: f.ref, selector: toSelector(f
|
|
661
|
+
})(${JSON.stringify(options.fields.map(f => ({ ref: f.ref || f.selector || f.name || f.label || 'field', selector: toSelector(f), value: f.value })))})`;
|
|
520
662
|
const { result } = await session.Runtime.evaluate({ expression: blastExpression, returnByValue: true });
|
|
521
663
|
const blastResults = JSON.parse(result?.value || '[]');
|
|
522
664
|
for (const r of blastResults) {
|
|
665
|
+
const fieldDef = options.fields.find(f => (f.ref || f.selector || f.name || f.label || 'field') === r.ref);
|
|
666
|
+
const isSecretField = r.isSecret || r.type === 'password';
|
|
667
|
+
if (isSecretField && fieldDef?.value) {
|
|
668
|
+
const fieldSel = toSelector(fieldDef);
|
|
669
|
+
await registerAndHardenSecretField(session, targetId, fieldSel, fieldDef.value);
|
|
670
|
+
}
|
|
523
671
|
results.push({
|
|
524
672
|
ref: r.ref,
|
|
525
673
|
field_type: r.type,
|
|
526
674
|
success: r.ok,
|
|
527
675
|
message: r.ok ? r.msg : `FAIL — ${r.msg}`,
|
|
528
|
-
value:
|
|
676
|
+
value: isSecretField ? "[REDACTED]" : fieldDef?.value,
|
|
529
677
|
});
|
|
530
678
|
if (!r.ok)
|
|
531
679
|
logAction("browser_fill", r.ref, `${r.type}: FAIL — ${r.msg}`);
|
|
532
680
|
else
|
|
533
|
-
logAction("browser_fill", r.ref, `${r.type}: OK — ${r.msg}`);
|
|
681
|
+
logAction("browser_fill", r.ref, isSecretField ? `${r.type}: OK — [secret filled]` : `${r.type}: OK — ${r.msg}`);
|
|
534
682
|
}
|
|
535
683
|
}
|
|
536
684
|
else {
|
|
537
685
|
for (const field of options.fields) {
|
|
538
|
-
const selector = toSelector(field
|
|
686
|
+
const selector = toSelector(field);
|
|
687
|
+
const refKey = field.ref || field.selector || field.name || field.label || selector;
|
|
539
688
|
// ── Pre-fill actionability gate (Playwright strategy) ─────────────────────
|
|
540
689
|
// Text + select fields on complex sites can be under overlays or still animating.
|
|
541
690
|
// Verify visibility, stability, and clear hit-target before writing.
|
|
@@ -546,26 +695,33 @@ export async function browserFill(session, options, config) {
|
|
|
546
695
|
if (actResult.status === 'OCCLUDED' && !actResult.occludedBy?.isWeavetabOwned) {
|
|
547
696
|
const occ = actResult.occludedBy;
|
|
548
697
|
const blockerStr = `<${occ.tag}${occ.id ? '#' + occ.id : ''}> intercepts pointer events`;
|
|
549
|
-
results.push({ ref:
|
|
550
|
-
logAction("browser_fill",
|
|
698
|
+
results.push({ ref: refKey, field_type: "unknown", success: false, message: `Field blocked by overlay: ${blockerStr}` });
|
|
699
|
+
logAction("browser_fill", refKey, `FAIL — occluded: ${blockerStr}`);
|
|
551
700
|
continue;
|
|
552
701
|
}
|
|
553
702
|
if (actResult.status === 'INVISIBLE') {
|
|
554
|
-
results.push({ ref:
|
|
555
|
-
logAction("browser_fill",
|
|
703
|
+
results.push({ ref: refKey, field_type: "unknown", success: false, message: "Field is not visible — scroll to reveal it before filling" });
|
|
704
|
+
logAction("browser_fill", refKey, "FAIL — invisible");
|
|
556
705
|
continue;
|
|
557
706
|
}
|
|
558
707
|
// 1. Detect field type
|
|
559
|
-
|
|
708
|
+
let detected = await detectFieldType(session, selector);
|
|
709
|
+
if (!detected.exists && (field.ref?.startsWith("w:") || selector.startsWith('[data-wt-id='))) {
|
|
710
|
+
const ref = field.ref || selector;
|
|
711
|
+
const rec = await recoverDetachedRef(session, targetId, ref, { label: field.label, placeholder: field.name });
|
|
712
|
+
if (rec.recovered) {
|
|
713
|
+
detected = await detectFieldType(session, selector);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
560
716
|
if (!detected.exists) {
|
|
561
|
-
results.push({ ref:
|
|
562
|
-
logAction("browser_fill",
|
|
717
|
+
results.push({ ref: refKey, field_type: "unknown", success: false, message: "Element not found" });
|
|
718
|
+
logAction("browser_fill", refKey, "unknown: FAIL — Element not found");
|
|
563
719
|
continue;
|
|
564
720
|
}
|
|
565
721
|
const type = detected.type;
|
|
566
722
|
if (type === "unknown") {
|
|
567
|
-
results.push({ ref:
|
|
568
|
-
logAction("browser_fill",
|
|
723
|
+
results.push({ ref: refKey, field_type: "unknown", success: false, message: "Unsupported field type" });
|
|
724
|
+
logAction("browser_fill", refKey, "unknown: FAIL — Unsupported field type");
|
|
569
725
|
continue;
|
|
570
726
|
}
|
|
571
727
|
// 2. Snapshot before
|
|
@@ -602,29 +758,32 @@ export async function browserFill(session, options, config) {
|
|
|
602
758
|
checkSuccess: buildCheckSuccess(type, field.value),
|
|
603
759
|
});
|
|
604
760
|
if (!actionOk) {
|
|
605
|
-
results.push({ ref:
|
|
606
|
-
logAction("browser_fill",
|
|
761
|
+
results.push({ ref: refKey, field_type: type, success: false, message: actionMsg });
|
|
762
|
+
logAction("browser_fill", refKey, `${type}: FAIL — ${actionMsg}`);
|
|
607
763
|
continue;
|
|
608
764
|
}
|
|
765
|
+
if (detected.isSecret && actionOk && field.value) {
|
|
766
|
+
await registerAndHardenSecretField(session, targetId, selector, field.value);
|
|
767
|
+
}
|
|
609
768
|
if (confirm.applied) {
|
|
610
769
|
results.push({
|
|
611
|
-
ref:
|
|
770
|
+
ref: refKey,
|
|
612
771
|
field_type: type,
|
|
613
772
|
success: true,
|
|
614
773
|
message: `${actionMsg} (verified after ${confirm.settleMs}ms)`,
|
|
615
774
|
value: detected.isSecret ? "[REDACTED]" : field.value,
|
|
616
775
|
});
|
|
617
|
-
logAction("browser_fill",
|
|
776
|
+
logAction("browser_fill", refKey, detected.isSecret ? `${type}: OK — [secret filled]` : `${type}: OK — ${actionMsg}`);
|
|
618
777
|
}
|
|
619
778
|
else {
|
|
620
779
|
// Field reverted — framework rejected the value (e.g., controlled component)
|
|
621
780
|
results.push({
|
|
622
|
-
ref:
|
|
781
|
+
ref: refKey,
|
|
623
782
|
field_type: type,
|
|
624
783
|
success: false,
|
|
625
784
|
message: `Value not retained — framework may have reverted it (before=${detected.isSecret ? '"[REDACTED]"' : JSON.stringify(before)}, after=${detected.isSecret ? '"[REDACTED]"' : JSON.stringify(confirm.after)})`,
|
|
626
785
|
});
|
|
627
|
-
logAction("browser_fill",
|
|
786
|
+
logAction("browser_fill", refKey, `${type}: FAIL — reverted by framework`);
|
|
628
787
|
}
|
|
629
788
|
}
|
|
630
789
|
}
|
package/dist/tools/find.d.ts
CHANGED
|
@@ -13,10 +13,26 @@ export interface FindOptions {
|
|
|
13
13
|
fuzzy?: boolean;
|
|
14
14
|
matchPlaceholder?: boolean;
|
|
15
15
|
matchTitle?: boolean;
|
|
16
|
+
all?: boolean;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface FindItem {
|
|
20
|
+
ref?: string;
|
|
21
|
+
selector: string;
|
|
22
|
+
label: string;
|
|
23
|
+
tag?: string;
|
|
24
|
+
rect: {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
};
|
|
16
30
|
}
|
|
17
31
|
export interface FindResult {
|
|
18
32
|
success: boolean;
|
|
19
33
|
found: boolean;
|
|
34
|
+
total_matches?: number;
|
|
35
|
+
matches?: FindItem[];
|
|
20
36
|
rect?: {
|
|
21
37
|
x: number;
|
|
22
38
|
y: number;
|
package/dist/tools/find.js
CHANGED
|
@@ -1,60 +1,83 @@
|
|
|
1
1
|
export async function browserFind(session, options, _config) {
|
|
2
|
-
const { query,
|
|
2
|
+
const { query, all = false, limit = 10 } = options;
|
|
3
3
|
if (!query)
|
|
4
4
|
throw new Error("browser_find requires a query string");
|
|
5
|
-
// Find tool must search ALL elements, not just interactive ones.
|
|
6
5
|
const { result } = await session.Runtime.evaluate({
|
|
7
|
-
expression: `(function(q) {
|
|
6
|
+
expression: `(function(q, returnAll, maxMatches) {
|
|
8
7
|
function norm(s) { return (s || '').toLowerCase().replace(/[.\\u2026\\s]+/g, ' ').trim(); }
|
|
9
8
|
var qLow = norm(q);
|
|
10
9
|
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, false);
|
|
11
|
-
var
|
|
10
|
+
var matchedNodes = [];
|
|
11
|
+
var seenElements = new Set();
|
|
12
|
+
|
|
12
13
|
while (walker.nextNode()) {
|
|
13
14
|
var node = walker.currentNode;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
var el = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
|
|
16
|
+
if (!el || seenElements.has(el)) continue;
|
|
17
|
+
|
|
18
|
+
var text = norm(node.textContent);
|
|
19
|
+
var aria = norm(el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('placeholder') || '');
|
|
20
|
+
|
|
21
|
+
if ((text && text.includes(qLow)) || (aria && aria.includes(qLow))) {
|
|
22
|
+
seenElements.add(el);
|
|
23
|
+
matchedNodes.push(el);
|
|
24
|
+
if (!returnAll && matchedNodes.length >= 1) break;
|
|
25
|
+
if (matchedNodes.length >= maxMatches) break;
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
var
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
28
|
+
|
|
29
|
+
if (matchedNodes.length === 0) return null;
|
|
30
|
+
|
|
31
|
+
var out = matchedNodes.map(function(match, i) {
|
|
32
|
+
var r = match.getBoundingClientRect();
|
|
33
|
+
var wtId = match.getAttribute('data-wt-id');
|
|
34
|
+
var sel = '';
|
|
35
|
+
if (wtId) {
|
|
36
|
+
sel = '[data-wt-id="' + wtId + '"]';
|
|
37
|
+
} else if (match.id) {
|
|
38
|
+
sel = '#' + CSS.escape(match.id);
|
|
39
|
+
} else {
|
|
40
|
+
var tmp = 'wt-find-' + i + '-' + Math.random().toString(36).slice(2,6);
|
|
41
|
+
match.setAttribute('data-wt-id', tmp);
|
|
42
|
+
sel = '[data-wt-id="' + tmp + '"]';
|
|
43
|
+
wtId = tmp;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ref: 'w:' + wtId,
|
|
47
|
+
label: match.getAttribute('aria-label') || (match.textContent||'').trim().slice(0,80) || q,
|
|
48
|
+
tag: match.tagName.toLowerCase(),
|
|
49
|
+
x: Math.round(r.left),
|
|
50
|
+
y: Math.round(r.top),
|
|
51
|
+
width: Math.round(r.width),
|
|
52
|
+
height: Math.round(r.height),
|
|
53
|
+
selector: sel
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return JSON.stringify(out);
|
|
58
|
+
})(${JSON.stringify(query)}, ${JSON.stringify(all)}, ${JSON.stringify(limit)})`,
|
|
47
59
|
returnByValue: true
|
|
48
60
|
}).catch(() => ({ result: { value: null } }));
|
|
49
61
|
if (result && result.value) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
const list = JSON.parse(result.value || '[]');
|
|
63
|
+
if (list.length > 0) {
|
|
64
|
+
const first = list[0];
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
found: true,
|
|
68
|
+
total_matches: list.length,
|
|
69
|
+
matches: list.map(m => ({
|
|
70
|
+
ref: m.ref,
|
|
71
|
+
selector: m.selector,
|
|
72
|
+
label: m.label,
|
|
73
|
+
tag: m.tag,
|
|
74
|
+
rect: { x: m.x, y: m.y, width: m.width, height: m.height }
|
|
75
|
+
})),
|
|
76
|
+
rect: { x: first.x, y: first.y, width: first.width, height: first.height },
|
|
77
|
+
selector: first.selector,
|
|
78
|
+
label: first.label
|
|
79
|
+
};
|
|
80
|
+
}
|
|
58
81
|
}
|
|
59
|
-
return { success: true, found: false };
|
|
82
|
+
return { success: true, found: false, total_matches: 0, matches: [] };
|
|
60
83
|
}
|