@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.
Files changed (156) hide show
  1. package/AGENTS.md +2 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +1 -1
  4. package/README.md +164 -802
  5. package/SECURITY.md +33 -22
  6. package/TROUBLESHOOTING.md +3 -3
  7. package/dist/cdp/bridge.d.ts +10 -0
  8. package/dist/cdp/bridge.js +285 -78
  9. package/dist/cdp/connector.d.ts +7 -3
  10. package/dist/cdp/connector.js +196 -75
  11. package/dist/cdp/helpers.d.ts +4 -0
  12. package/dist/cdp/helpers.js +181 -0
  13. package/dist/cdp/lock.d.ts +9 -6
  14. package/dist/cdp/lock.js +72 -59
  15. package/dist/cdp/omni_broker.d.ts +2 -1
  16. package/dist/cdp/omni_broker.js +13 -7
  17. package/dist/cdp/profiles.d.ts +10 -0
  18. package/dist/cdp/profiles.js +65 -0
  19. package/dist/cdp/walker.d.ts +25 -1
  20. package/dist/cdp/walker.js +354 -7
  21. package/dist/cli/plugin.d.ts +0 -2
  22. package/dist/cli/plugin.js +51 -696
  23. package/dist/cli/updater.d.ts +1 -1
  24. package/dist/cli/updater.js +27 -7
  25. package/dist/config/loader.d.ts +33 -6
  26. package/dist/config/loader.js +174 -22
  27. package/dist/desktop/discovery.d.ts +44 -0
  28. package/dist/desktop/discovery.js +364 -0
  29. package/dist/index.js +140 -64
  30. package/dist/intelligence/marks.d.ts +36 -0
  31. package/dist/intelligence/marks.js +30 -0
  32. package/dist/intelligence/patternCompiler.d.ts +118 -0
  33. package/dist/intelligence/patternCompiler.js +259 -0
  34. package/dist/intelligence/sessionMemory.d.ts +2 -0
  35. package/dist/intelligence/sessionMemory.js +18 -8
  36. package/dist/overlay/ghost.js +2 -2
  37. package/dist/overlay/injector.js +3 -5
  38. package/dist/plugin/extension_bridge.d.ts +40 -0
  39. package/dist/plugin/extension_bridge.js +158 -0
  40. package/dist/plugin/interface.d.ts +151 -15
  41. package/dist/plugin/loader.d.ts +19 -1
  42. package/dist/plugin/loader.js +331 -63
  43. package/dist/plugin/pipeline.d.ts +75 -0
  44. package/dist/plugin/pipeline.js +183 -0
  45. package/dist/plugin/registry.js +12 -1
  46. package/dist/plugin/storage.d.ts +51 -0
  47. package/dist/plugin/storage.js +164 -0
  48. package/dist/security/secrets.d.ts +1 -0
  49. package/dist/security/secrets.js +13 -0
  50. package/dist/sensors/actionability.js +1 -2
  51. package/dist/sensors/guard.js +4 -10
  52. package/dist/sensors/mutations.js +12 -0
  53. package/dist/sensors/operation_monitor.js +1 -0
  54. package/dist/server.d.ts +1 -1
  55. package/dist/server.js +600 -236
  56. package/dist/state/agent.d.ts +1 -0
  57. package/dist/state/agent.js +3 -0
  58. package/dist/state/delta.d.ts +12 -4
  59. package/dist/state/delta.js +67 -14
  60. package/dist/state/session.d.ts +8 -2
  61. package/dist/state/session.js +60 -13
  62. package/dist/tools/browser_detect.d.ts +16 -2
  63. package/dist/tools/browser_detect.js +54 -6
  64. package/dist/tools/burst.d.ts +17 -7
  65. package/dist/tools/burst.js +240 -83
  66. package/dist/tools/checkpoint.d.ts +38 -0
  67. package/dist/tools/checkpoint.js +271 -0
  68. package/dist/tools/click.d.ts +1 -0
  69. package/dist/tools/click.js +97 -98
  70. package/dist/tools/clipboard.js +31 -24
  71. package/dist/tools/design_extract.js +1 -1
  72. package/dist/tools/eval.js +1 -1
  73. package/dist/tools/fill.d.ts +20 -1
  74. package/dist/tools/fill.js +199 -40
  75. package/dist/tools/find.d.ts +16 -0
  76. package/dist/tools/find.js +67 -44
  77. package/dist/tools/github/index.d.ts +19 -4
  78. package/dist/tools/github/index.js +40 -4
  79. package/dist/tools/highlight.d.ts +6 -2
  80. package/dist/tools/highlight.js +158 -72
  81. package/dist/tools/inspect.js +1 -1
  82. package/dist/tools/key.js +65 -56
  83. package/dist/tools/navigate.d.ts +1 -1
  84. package/dist/tools/navigate.js +57 -44
  85. package/dist/tools/pdf.js +21 -13
  86. package/dist/tools/pointer.d.ts +18 -3
  87. package/dist/tools/pointer.js +89 -1
  88. package/dist/tools/read.d.ts +28 -8
  89. package/dist/tools/read.js +230 -56
  90. package/dist/tools/scrape.js +67 -13
  91. package/dist/tools/screenshot.js +21 -16
  92. package/dist/tools/scroll.js +6 -11
  93. package/dist/tools/select.js +8 -2
  94. package/dist/tools/snapshot.js +3 -34
  95. package/dist/tools/tabs.d.ts +10 -10
  96. package/dist/tools/tabs.js +55 -39
  97. package/dist/tools/thought.d.ts +2 -0
  98. package/dist/tools/thought.js +13 -14
  99. package/dist/tools/type.d.ts +2 -0
  100. package/dist/tools/type.js +109 -60
  101. package/dist/tools/type_secret.js +42 -76
  102. package/dist/tools/upload.js +1 -1
  103. package/dist/tools/video_compiler.d.ts +1 -1
  104. package/dist/tools/video_compiler.js +11 -12
  105. package/dist/version.d.ts +5 -0
  106. package/dist/version.js +5 -0
  107. package/docs/DOCUMENTATION.md +82 -36
  108. package/docs/PLUGINS.md +82 -499
  109. package/docs/TOOLS.md +51 -99
  110. package/docs/architecture/blind-injection.md +83 -0
  111. package/docs/architecture/dual-injection.md +32 -0
  112. package/docs/architecture/overview.md +49 -0
  113. package/docs/architecture/security.md +35 -0
  114. package/docs/architecture/transport.md +37 -0
  115. package/docs/captcha-stealth/captcha-solver.md +75 -0
  116. package/docs/captcha-stealth/stealth-physics.md +60 -0
  117. package/docs/cli/cli-commands.md +44 -0
  118. package/docs/cli/cli-flags.md +20 -0
  119. package/docs/cli/cli-introduction.md +20 -0
  120. package/docs/cli/cli-scripting.md +32 -0
  121. package/docs/cli/g-ide/ide-antigravity.md +24 -0
  122. package/docs/cli/g-ide/ide-claude-code.md +24 -0
  123. package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
  124. package/docs/cli/g-ide/ide-cursor.md +31 -0
  125. package/docs/cli/g-ide/ide-generic.md +34 -0
  126. package/docs/cli/g-ide/ide-opencode.md +24 -0
  127. package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
  128. package/docs/cli/g-ide/ide-windsurf.md +24 -0
  129. package/docs/configuration/config-locations.md +24 -0
  130. package/docs/configuration/configuration.md +73 -0
  131. package/docs/configuration/env-variables.md +19 -0
  132. package/docs/configuration/folders.md +55 -0
  133. package/docs/desktop-automation/electron-apps.md +217 -0
  134. package/docs/get-started/installation.md +47 -0
  135. package/docs/get-started/introduction.md +60 -0
  136. package/docs/get-started/quickstart.md +54 -0
  137. package/docs/get-started/setup-wizard.md +26 -0
  138. package/docs/intelligence/site-profiles.md +151 -0
  139. package/docs/skills-ecosystem/skills-overview.md +35 -0
  140. package/docs/skills-ecosystem/skills-recipes.md +36 -0
  141. package/docs/tools/tools.md +631 -0
  142. package/docs/troubleshooting/trouble-browser.md +27 -0
  143. package/docs/troubleshooting/trouble-config.md +20 -0
  144. package/docs/troubleshooting/trouble-loop.md +31 -0
  145. package/ext-dist/background.js +10 -62
  146. package/ext-dist/content.js +333 -921
  147. package/ext-dist/hud/hud.js +9 -9
  148. package/ext-dist/manifest.json +2 -6
  149. package/ext-dist/styles.css +6 -1
  150. package/mcp.json +108 -72
  151. package/package.json +14 -9
  152. package/server.json +3 -3
  153. package/docs/DEV_MODE.md +0 -68
  154. package/docs/dev_plugin.md +0 -310
  155. package/ext-dist/sidepanel/sidepanel.js +0 -25
  156. 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: `Design extracted successfully to ${targetDir}. Saved tokens.json, template.html, styles.css, and README.md.`
633
+ message: `design saved to ${targetDir}. Saved tokens.json, template.html, styles.css, and README.md.`
634
634
  };
635
635
  }
636
636
  finally {
@@ -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
- ? "(expression ran successfully — returned undefined. To get a confirmation value, append ; 'ok' or return a string explicitly.)"
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)
@@ -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: string;
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;
@@ -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
- function toSelector(ref) {
6
- const refId = ref.startsWith("w:") ? ref.split(":")[1] : null;
7
- return refId ? `[data-wt-id="${refId}"]` : ref;
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 isSecret = el.hasAttribute('data-wt-secret') || (el.tagName === 'INPUT' && el.type === 'password');
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 s = { exists: "true" };
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. Use browser_map() to get field ref IDs, then call browser_fill with fields:[{ref:'w:NN', value:'...'}]." };
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.ref);
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.ref), value: f.value })))})`;
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: options.fields.find(f => f.ref === r.ref)?.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.ref);
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: field.ref, field_type: "unknown", success: false, message: `Field blocked by overlay: ${blockerStr}` });
550
- logAction("browser_fill", field.ref, `FAIL — occluded: ${blockerStr}`);
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: field.ref, field_type: "unknown", success: false, message: "Field is not visible — scroll to reveal it before filling" });
555
- logAction("browser_fill", field.ref, "FAIL — invisible");
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
- const detected = await detectFieldType(session, selector);
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: field.ref, field_type: "unknown", success: false, message: "Element not found" });
562
- logAction("browser_fill", field.ref, "unknown: FAIL — Element not found");
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: field.ref, field_type: "unknown", success: false, message: "Unsupported field type" });
568
- logAction("browser_fill", field.ref, "unknown: FAIL — Unsupported field type");
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: field.ref, field_type: type, success: false, message: actionMsg });
606
- logAction("browser_fill", field.ref, `${type}: FAIL — ${actionMsg}`);
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: field.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", field.ref, `${type}: OK — ${actionMsg}`);
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: field.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", field.ref, `${type}: FAIL — reverted by framework`);
786
+ logAction("browser_fill", refKey, `${type}: FAIL — reverted by framework`);
628
787
  }
629
788
  }
630
789
  }
@@ -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;
@@ -1,60 +1,83 @@
1
1
  export async function browserFind(session, options, _config) {
2
- const { query, role, fuzzy = true, matchPlaceholder = true, matchTitle = true } = options;
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 matches = [];
10
+ var matchedNodes = [];
11
+ var seenElements = new Set();
12
+
12
13
  while (walker.nextNode()) {
13
14
  var node = walker.currentNode;
14
- if (node.nodeType === Node.TEXT_NODE) {
15
- var t = norm(node.textContent);
16
- if (t && t.includes(qLow)) {
17
- matches.push(node.parentElement);
18
- }
19
- } else if (node.nodeType === Node.ELEMENT_NODE) {
20
- var el = node;
21
- var aria = norm(el.getAttribute('aria-label') || el.getAttribute('title') || el.getAttribute('placeholder') || '');
22
- if (aria && aria.includes(qLow)) {
23
- matches.push(el);
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
- if (matches.length === 0) return null;
28
- var match = matches[0];
29
- var r = match.getBoundingClientRect();
30
- var sel = '';
31
- if (match.id) {
32
- sel = '#' + CSS.escape(match.id);
33
- } else {
34
- var tmp = 'wt-tmp-' + Math.random().toString(36).slice(2,8);
35
- match.setAttribute('data-wt-id', tmp);
36
- sel = '[data-wt-id="' + tmp + '"]';
37
- }
38
- return {
39
- label: match.getAttribute('aria-label') || (match.textContent||'').trim().slice(0,80) || q,
40
- x: Math.round(r.left),
41
- y: Math.round(r.top),
42
- width: Math.round(r.width),
43
- height: Math.round(r.height),
44
- selector: sel
45
- };
46
- })(${JSON.stringify(query)})`,
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 val = result.value;
51
- return {
52
- success: true,
53
- found: true,
54
- rect: { x: val.x, y: val.y, width: val.width, height: val.height },
55
- selector: val.selector,
56
- label: val.label
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
  }