@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
@@ -1,5 +1,7 @@
1
1
  import { logDebug } from "../audit/logger.js";
2
2
  import { SessionSecretTracker } from "../security/secrets.js";
3
+ import { getStoredRef, getStoredProps } from "../state/delta.js";
4
+ import { getRawCachedActionMap } from "../state/cache.js";
3
5
  // ─── Public types ─────────────────────────────────────────────────────────────
4
6
  export const WT_REGISTRY_INIT_EXPR = `
5
7
  window.__weavetab = window.__weavetab || {};
@@ -162,7 +164,7 @@ export async function buildActionMap(session, options = {}, targetId = "default"
162
164
  try {
163
165
  const parsed = JSON.parse(liveIndexResult.value);
164
166
  if (parsed.source === 'live-index' && parsed.elements && Array.isArray(parsed.elements)) {
165
- logDebug(`[Walker] Successfully read ${parsed.elements.length} elements from extension live index`);
167
+ logDebug(`[Walker] read ${parsed.elements.length} elements from extension live index`);
166
168
  extensionMap = parsed.elements;
167
169
  }
168
170
  }
@@ -309,8 +311,16 @@ export async function buildActionMap(session, options = {}, targetId = "default"
309
311
  else if (role === 'input' && (el.type === 'text' || el.type === 'search')) role = 'textbox';
310
312
  var frameUrl = '';
311
313
  try { if (el.ownerDocument !== document) frameUrl = el.ownerDocument.defaultView.location.href; } catch(e) { frameUrl = 'cross-origin-' + (_coIdx++); }
312
- var isSecret = el.hasAttribute('data-wt-secret') || (el.tagName === 'INPUT' && el.type === 'password');
313
- var labelStr = el.getAttribute('aria-label') || el.innerText || el.value || el.placeholder || '';
314
+ var secretRegex = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha/i;
315
+ var isSecret = el.hasAttribute('data-wt-secret') ||
316
+ (el.tagName === 'INPUT' && (
317
+ el.type === 'password' ||
318
+ secretRegex.test(el.getAttribute('autocomplete') || '') ||
319
+ secretRegex.test(el.getAttribute('name') || '') ||
320
+ secretRegex.test(el.getAttribute('id') || '') ||
321
+ secretRegex.test(el.getAttribute('aria-label') || '')
322
+ ));
323
+ var labelStr = el.getAttribute('aria-label') || el.innerText || (isSecret ? '' : el.value) || el.placeholder || '';
314
324
  // For radio/checkbox, serialize checked state as 'true'/'false' so the agent
315
325
  // can see which option is selected without having to scrape the DOM separately.
316
326
  var elType = (el.type || '').toLowerCase();
@@ -345,10 +355,59 @@ export async function buildActionMap(session, options = {}, targetId = "default"
345
355
  break;
346
356
  }
347
357
  }
358
+
359
+ // ── Container context binding ─────────────────────────────────────
360
+ // Walk up the DOM to find a meaningful card/row/list-item heading that
361
+ // describes this element's entity context, e.g. "Pro Plan ($29/mo)".
362
+ var containerCtx = '';
363
+ try {
364
+ var CARD_SEL = '[class*="card"],[class*="row"],[class*="item"],[class*="product"],[class*="plan"],[class*="entry"],[class*="result"],[class*="tile"],[class*="option"],[data-entity]';
365
+ var ancestor = el.parentElement;
366
+ var depth3 = 0;
367
+ while (ancestor && depth3 < 8) {
368
+ if (ancestor.matches && ancestor.matches(CARD_SEL)) {
369
+ var heading = ancestor.querySelector('h1,h2,h3,h4,h5,h6,[class*="title"],[class*="name"],[class*="heading"],[class*="label"]');
370
+ if (heading) {
371
+ var hText = (heading.innerText || heading.textContent || '').trim().slice(0, 60);
372
+ if (hText && hText !== labelStr) { containerCtx = hText; break; }
373
+ }
374
+ var allText = (ancestor.innerText || ancestor.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 60);
375
+ if (allText && allText !== labelStr) { containerCtx = allText; break; }
376
+ }
377
+ ancestor = ancestor.parentElement;
378
+ depth3++;
379
+ }
380
+ } catch(e) {}
381
+
382
+ // ── Modal detection ───────────────────────────────────────────────
383
+ var inModal = false;
384
+ var modalTitleStr = '';
385
+ try {
386
+ var MODAL_SEL = 'dialog[open],[role="dialog"],[aria-modal="true"],[class*="modal"],[class*="overlay"],[class*="drawer"]';
387
+ var modalAnc = el.parentElement;
388
+ var md = 0;
389
+ while (modalAnc && md < 15) {
390
+ if (modalAnc.matches && modalAnc.matches(MODAL_SEL)) {
391
+ inModal = true;
392
+ var mh = modalAnc.querySelector('h1,h2,h3,h4,[class*="title"]');
393
+ if (mh) {
394
+ var lbId = modalAnc.getAttribute('aria-labelledby');
395
+ var lbEl = lbId ? document.getElementById(lbId) : null;
396
+ modalTitleStr = ((lbEl || mh).innerText || (lbEl || mh).textContent || '').trim().slice(0, 60);
397
+ }
398
+ break;
399
+ }
400
+ modalAnc = modalAnc.parentElement;
401
+ md++;
402
+ }
403
+ } catch(e) {}
404
+
348
405
  results.push({
349
406
  id: String(id),
350
407
  role: el.getAttribute('role') || role,
408
+ type: elType || undefined,
351
409
  label: labelStr,
410
+ placeholder: el.placeholder || undefined,
352
411
  value: valStr,
353
412
  x: Math.round(rect.left + rect.width / 2),
354
413
  y: Math.round(rect.top + rect.height / 2),
@@ -357,7 +416,12 @@ export async function buildActionMap(session, options = {}, targetId = "default"
357
416
  textContent: txtContent,
358
417
  className: el.className || '',
359
418
  frameUrl: frameUrl,
360
- structuralIdiom: idiom || undefined
419
+ structuralIdiom: idiom || undefined,
420
+ containerContext: containerCtx || undefined,
421
+ isModal: inModal || undefined,
422
+ modalTitle: modalTitleStr || undefined,
423
+ disabled: (el.disabled || el.getAttribute('aria-disabled') === 'true') || undefined,
424
+ checked: isCheckable ? el.checked : undefined,
361
425
  });
362
426
  } catch (e) {
363
427
  // Silently skip elements that cause errors (cross-origin SVG, detached nodes, etc.)
@@ -387,6 +451,56 @@ export async function buildActionMap(session, options = {}, targetId = "default"
387
451
  }
388
452
  }
389
453
  }
454
+ // Merge elements from child webviews and iframes managed by OmniBroker
455
+ try {
456
+ const { getOmniSessions } = await import("./omni_broker.js");
457
+ const childSessions = getOmniSessions(targetId);
458
+ if (childSessions && childSessions.size > 0) {
459
+ for (const [, childSession] of childSessions.entries()) {
460
+ try {
461
+ const { result: childResult } = await childSession.Runtime.evaluate({
462
+ expression: `(function() {
463
+ var INTERACTIVE_SEL = 'a, button, input, textarea, select, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="radio"], [role="combobox"], [role="searchbox"], [role="menuitem"], [role="tab"], [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
464
+ var els = document.querySelectorAll(INTERACTIVE_SEL);
465
+ var results = [];
466
+ for (var i = 0; i < els.length; i++) {
467
+ var el = els[i];
468
+ var rect = el.getBoundingClientRect();
469
+ if (rect.width === 0 || rect.height === 0 || rect.bottom < 0 || rect.top > window.innerHeight) continue;
470
+ var id = el.getAttribute('data-wt-id');
471
+ if (!id) {
472
+ id = 'wv_' + Math.random().toString(36).slice(2, 9);
473
+ el.setAttribute('data-wt-id', id);
474
+ }
475
+ results.push({
476
+ id: id,
477
+ role: el.getAttribute('role') || el.tagName.toLowerCase(),
478
+ label: el.getAttribute('aria-label') || el.innerText || el.value || el.placeholder || '',
479
+ value: el.value,
480
+ x: Math.round(rect.left + rect.width / 2),
481
+ y: Math.round(rect.top + rect.height / 2),
482
+ selector: '[data-wt-id="' + id + '"]',
483
+ frameUrl: window.location.href,
484
+ });
485
+ }
486
+ return JSON.stringify(results);
487
+ })()`,
488
+ returnByValue: true,
489
+ });
490
+ if (childResult && childResult.value) {
491
+ const childEls = JSON.parse(childResult.value);
492
+ if (Array.isArray(childEls) && childEls.length > 0) {
493
+ if (!Array.isArray(extensionMap))
494
+ extensionMap = [];
495
+ extensionMap = extensionMap.concat(childEls);
496
+ }
497
+ }
498
+ }
499
+ catch { }
500
+ }
501
+ }
502
+ }
503
+ catch { }
390
504
  if (!extensionMap || !Array.isArray(extensionMap)) {
391
505
  return { url, title, elements: [], targetId };
392
506
  }
@@ -396,6 +510,8 @@ export async function buildActionMap(session, options = {}, targetId = "default"
396
510
  role: el.role || el.tag || 'generic',
397
511
  label: el.label || el.text || el.placeholder || '',
398
512
  value: el.value,
513
+ placeholder: el.placeholder || undefined,
514
+ type: el.type || undefined,
399
515
  group: 'main_content',
400
516
  fingerprint: `${el.role || el.tag || 'generic'}:${el.label || el.text || ''}:${el.id}`,
401
517
  depth: 0,
@@ -409,6 +525,10 @@ export async function buildActionMap(session, options = {}, targetId = "default"
409
525
  className: el.className || el.attributes?.class || '',
410
526
  structuralIdiom: el.structuralIdiom || undefined,
411
527
  frameUrl: el.frameUrl || undefined,
528
+ containerContext: el.containerContext || undefined,
529
+ isModal: el.isModal || undefined,
530
+ modalTitle: el.modalTitle || undefined,
531
+ disabled: el.disabled || undefined,
412
532
  }));
413
533
  // Post-mapping sanitizer — catch any w:undefined/w:null and reassign.
414
534
  // Uses a stable hash derived from the element's fingerprint, coordinates, and
@@ -540,7 +660,15 @@ export async function buildActionMap(session, options = {}, targetId = "default"
540
660
  if (options.limit && filtered.length > options.limit) {
541
661
  filtered = filtered.slice(0, options.limit);
542
662
  }
543
- return { url, title, elements: filtered, targetId };
663
+ // Apply session secret redaction over all element labels, values, placeholders
664
+ const redactedElements = filtered.map(el => ({
665
+ ...el,
666
+ label: targetId ? SessionSecretTracker.redact(targetId, el.label) : el.label,
667
+ value: el.value !== undefined ? (targetId ? SessionSecretTracker.redact(targetId, el.value) : el.value) : undefined,
668
+ placeholder: el.placeholder ? (targetId ? SessionSecretTracker.redact(targetId, el.placeholder) : el.placeholder) : undefined,
669
+ textContent: el.textContent ? (targetId ? SessionSecretTracker.redact(targetId, el.textContent) : el.textContent) : undefined,
670
+ }));
671
+ return { url, title, elements: redactedElements, targetId };
544
672
  }
545
673
  // ─── toRefEntries — converts ActionElements to RefEntry for delta tracking ───
546
674
  export function toRefEntries(elements) {
@@ -550,7 +678,7 @@ export function toRefEntries(elements) {
550
678
  label: el.label,
551
679
  type: el.type,
552
680
  href: el.href,
553
- disabled: el.disabled,
681
+ disabled: el.disabled ?? false,
554
682
  value: el.value,
555
683
  fingerprint: el.fingerprint,
556
684
  }));
@@ -838,11 +966,17 @@ export async function buildAXTreeMap(session, _options = {}, targetId = "default
838
966
  else {
839
967
  value = node.value?.value !== undefined ? String(node.value.value) : undefined;
840
968
  }
841
- const isSecretRole = role === 'password' || node.name?.value?.toLowerCase().includes('password');
969
+ const SECRET_TERMS = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha|пароль|密碼|密码/i;
970
+ const isSecretRole = role === 'password' || SECRET_TERMS.test(node.name?.value || '') || SECRET_TERMS.test(node.description?.value || '');
842
971
  if (isSecretRole || (node.backendDOMNodeId !== undefined && SessionSecretTracker.isNodeSecret(targetId, node.backendDOMNodeId))) {
843
972
  label = '[REDACTED]';
844
973
  value = '[REDACTED]';
845
974
  }
975
+ else {
976
+ label = targetId ? SessionSecretTracker.redact(targetId, label) : label;
977
+ if (value !== undefined)
978
+ value = targetId ? SessionSecretTracker.redact(targetId, value) : value;
979
+ }
846
980
  // Use globally-unique sequential ID (Bug #5 fix: was local idCounter = 1).
847
981
  const id = dedupeRefId(`w:${axIdCursor++}`);
848
982
  elements.push({
@@ -894,3 +1028,216 @@ export async function buildAXTreeMap(session, _options = {}, targetId = "default
894
1028
  axTreeCacheSet(url, map);
895
1029
  return map;
896
1030
  }
1031
+ /**
1032
+ * Sanitizes element IDs in an ActionElement list, replacing empty or malformed
1033
+ * IDs with sequential fallback IDs.
1034
+ */
1035
+ export function sanitizeElementIds(els, counter) {
1036
+ let fixed = 0;
1037
+ for (const el of els) {
1038
+ if (!el.id || el.id === "w:undefined" || el.id === "w:null" || el.id === "w:") {
1039
+ el.id = `w:${counter.value++}`;
1040
+ fixed++;
1041
+ }
1042
+ }
1043
+ return fixed;
1044
+ }
1045
+ /**
1046
+ * Recovers a detached or invalidated w:NN ref on reactive SPAs.
1047
+ * When hydration or DOM re-renders detach an element, this helper executes a sub-5ms
1048
+ * multi-tier recovery heuristic (connected node verification, stored CSS selector,
1049
+ * exact label/aria, placeholder/name match, and fuzzy substring) and re-stamps
1050
+ * data-wt-id="NN" onto the live DOM node so subsequent interaction succeeds.
1051
+ */
1052
+ export async function recoverDetachedRef(session, targetId, ref, preferredCandidate) {
1053
+ const refNumericId = ref.replace(/^w:/, '');
1054
+ // Extract known metadata for this ref
1055
+ const storedRef = getStoredRef(targetId, ref);
1056
+ const storedProps = getStoredProps(targetId, ref);
1057
+ const rawMap = getRawCachedActionMap(targetId);
1058
+ const cachedEl = rawMap?.elements?.find(e => e.id === ref || e.id === `w:${refNumericId}`);
1059
+ const candidateLabel = preferredCandidate?.label || storedProps?.label || storedRef?.label || cachedEl?.label || '';
1060
+ const candidatePlaceholder = preferredCandidate?.placeholder || storedProps?.placeholder || cachedEl?.placeholder || '';
1061
+ const candidateRole = preferredCandidate?.role || storedProps?.role || storedRef?.role || cachedEl?.role || '';
1062
+ const candidateSelector = preferredCandidate?.selector || cachedEl?.selector || (storedProps?.className ? `.${storedProps.className.split(' ')[0]}` : undefined);
1063
+ const candidateText = preferredCandidate?.textContent || storedProps?.textContent || cachedEl?.textContent || '';
1064
+ const meta = {
1065
+ label: candidateLabel === '[REDACTED]' ? '' : candidateLabel,
1066
+ placeholder: candidatePlaceholder === '[REDACTED]' ? '' : candidatePlaceholder,
1067
+ role: candidateRole,
1068
+ selector: candidateSelector,
1069
+ textContent: candidateText === '[REDACTED]' ? '' : candidateText,
1070
+ };
1071
+ try {
1072
+ const { result } = await session.Runtime.evaluate({
1073
+ expression: `(function(refId, meta) {
1074
+ // 1. Check if node with data-wt-id=refId is already in DOM, connected, and visible
1075
+ const existing = document.querySelector('[data-wt-id="' + refId + '"]');
1076
+ if (existing && existing.isConnected) {
1077
+ const r = existing.getBoundingClientRect();
1078
+ if (r.width > 0 && r.height > 0) {
1079
+ return {
1080
+ success: true,
1081
+ strategy: 'connected_existing',
1082
+ x: Math.round(r.left + r.width / 2),
1083
+ y: Math.round(r.top + r.height / 2),
1084
+ label: existing.getAttribute('aria-label') || (existing.innerText || existing.textContent || '').trim() || existing.placeholder || '',
1085
+ role: existing.getAttribute('role') || existing.tagName.toLowerCase()
1086
+ };
1087
+ }
1088
+ }
1089
+
1090
+ function isValidCandidate(el) {
1091
+ if (!el || !el.isConnected) return false;
1092
+ const r = el.getBoundingClientRect();
1093
+ if (r.width <= 0 || r.height <= 0) return false;
1094
+ try {
1095
+ const style = window.getComputedStyle(el);
1096
+ if (style.visibility === 'hidden' || style.display === 'none') return false;
1097
+ } catch(e) {}
1098
+ return true;
1099
+ }
1100
+
1101
+ // 2. Strategy: Stored Selector (if stable CSS selector recorded)
1102
+ if (meta.selector && !meta.selector.startsWith('[data-wt-id')) {
1103
+ try {
1104
+ const el = document.querySelector(meta.selector);
1105
+ if (isValidCandidate(el)) {
1106
+ el.setAttribute('data-wt-id', refId);
1107
+ const r = el.getBoundingClientRect();
1108
+ return {
1109
+ success: true,
1110
+ strategy: 'stored_selector',
1111
+ x: Math.round(r.left + r.width / 2),
1112
+ y: Math.round(r.top + r.height / 2),
1113
+ label: el.getAttribute('aria-label') || (el.innerText || el.textContent || '').trim() || el.placeholder || '',
1114
+ role: el.getAttribute('role') || el.tagName.toLowerCase()
1115
+ };
1116
+ }
1117
+ } catch(e) {}
1118
+ }
1119
+
1120
+ const normLabel = (meta.label || '').toLowerCase().trim();
1121
+ const normPlaceholder = (meta.placeholder || '').toLowerCase().trim();
1122
+
1123
+ const candidates = Array.from(document.querySelectorAll(
1124
+ 'button, a, input, select, textarea, [role="button"], [role="link"], [role="textbox"], [role="checkbox"], [role="tab"], [tabindex="0"]'
1125
+ ));
1126
+
1127
+ // 3. Strategy: Exact Label / Text / Aria match
1128
+ if (normLabel) {
1129
+ for (const c of candidates) {
1130
+ if (!isValidCandidate(c)) continue;
1131
+ const aria = (c.getAttribute('aria-label') || '').toLowerCase().trim();
1132
+ const text = ((c.innerText || c.textContent || '')).toLowerCase().trim();
1133
+ const val = (c.value || '').toLowerCase().trim();
1134
+ const placeholder = (c.placeholder || '').toLowerCase().trim();
1135
+ const name = (c.getAttribute('name') || '').toLowerCase().trim();
1136
+
1137
+ if (aria === normLabel || text === normLabel || placeholder === normLabel || name === normLabel || (val && val === normLabel)) {
1138
+ c.setAttribute('data-wt-id', refId);
1139
+ const r = c.getBoundingClientRect();
1140
+ return {
1141
+ success: true,
1142
+ strategy: 'exact_label',
1143
+ x: Math.round(r.left + r.width / 2),
1144
+ y: Math.round(r.top + r.height / 2),
1145
+ label: c.getAttribute('aria-label') || (c.innerText || c.textContent || '').trim() || c.placeholder || '',
1146
+ role: c.getAttribute('role') || c.tagName.toLowerCase()
1147
+ };
1148
+ }
1149
+ }
1150
+ }
1151
+
1152
+ // 4. Strategy: Placeholder or Name match
1153
+ if (normPlaceholder) {
1154
+ for (const c of candidates) {
1155
+ if (!isValidCandidate(c)) continue;
1156
+ const placeholder = (c.placeholder || '').toLowerCase().trim();
1157
+ const name = (c.getAttribute('name') || '').toLowerCase().trim();
1158
+ if (placeholder === normPlaceholder || name === normPlaceholder) {
1159
+ c.setAttribute('data-wt-id', refId);
1160
+ const r = c.getBoundingClientRect();
1161
+ return {
1162
+ success: true,
1163
+ strategy: 'placeholder_or_name',
1164
+ x: Math.round(r.left + r.width / 2),
1165
+ y: Math.round(r.top + r.height / 2),
1166
+ label: c.placeholder || c.getAttribute('name') || '',
1167
+ role: c.getAttribute('role') || c.tagName.toLowerCase()
1168
+ };
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ // 5. Strategy: Fuzzy Substring Match (if label length >= 3)
1174
+ if (normLabel && normLabel.length >= 3) {
1175
+ let bestMatch = null;
1176
+ let minDiff = Infinity;
1177
+ for (const c of candidates) {
1178
+ if (!isValidCandidate(c)) continue;
1179
+ const text = ((c.innerText || c.textContent || '')).toLowerCase().trim();
1180
+ const aria = (c.getAttribute('aria-label') || '').toLowerCase().trim();
1181
+ const candStr = aria || text;
1182
+ if (candStr && candStr.includes(normLabel)) {
1183
+ const diff = candStr.length - normLabel.length;
1184
+ if (diff >= 0 && diff < minDiff) {
1185
+ minDiff = diff;
1186
+ bestMatch = c;
1187
+ }
1188
+ }
1189
+ }
1190
+ if (bestMatch) {
1191
+ bestMatch.setAttribute('data-wt-id', refId);
1192
+ const r = bestMatch.getBoundingClientRect();
1193
+ return {
1194
+ success: true,
1195
+ strategy: 'fuzzy_label',
1196
+ x: Math.round(r.left + r.width / 2),
1197
+ y: Math.round(r.top + r.height / 2),
1198
+ label: bestMatch.getAttribute('aria-label') || (bestMatch.innerText || bestMatch.textContent || '').trim() || bestMatch.placeholder || '',
1199
+ role: bestMatch.getAttribute('role') || bestMatch.tagName.toLowerCase()
1200
+ };
1201
+ }
1202
+ }
1203
+
1204
+ return { success: false };
1205
+ })(${JSON.stringify(refNumericId)}, ${JSON.stringify(meta)})`,
1206
+ returnByValue: true,
1207
+ });
1208
+ const res = result?.value;
1209
+ if (res && res.success) {
1210
+ const recoveredElement = {
1211
+ id: ref.startsWith('w:') ? ref : `w:${ref}`,
1212
+ role: res.role || candidateRole || 'button',
1213
+ label: res.label || candidateLabel || 'recovered-element',
1214
+ x: res.x,
1215
+ y: res.y,
1216
+ selector: `[data-wt-id="${refNumericId}"]`,
1217
+ group: "utility",
1218
+ depth: 0,
1219
+ fingerprint: `${res.role || candidateRole}:${res.label || candidateLabel}`,
1220
+ };
1221
+ // Patch the raw cache if present so subsequent lookups hit
1222
+ if (rawMap && rawMap.elements) {
1223
+ const existingIdx = rawMap.elements.findIndex(e => e.id === recoveredElement.id);
1224
+ if (existingIdx !== -1) {
1225
+ rawMap.elements[existingIdx] = recoveredElement;
1226
+ }
1227
+ else {
1228
+ rawMap.elements.push(recoveredElement);
1229
+ }
1230
+ }
1231
+ return {
1232
+ recovered: true,
1233
+ element: recoveredElement,
1234
+ strategy: res.strategy,
1235
+ warning: `(Auto-recovered detached ref '${ref}' via ${res.strategy}: '${recoveredElement.label}')`,
1236
+ };
1237
+ }
1238
+ }
1239
+ catch (err) {
1240
+ logDebug(`[Walker] Detached ref recovery failed for ${ref}: ${err}`);
1241
+ }
1242
+ return { recovered: false };
1243
+ }
@@ -4,6 +4,4 @@ export declare function runPluginSearch(query?: string): Promise<void>;
4
4
  export declare function runPluginBrowse(): Promise<void>;
5
5
  export declare function runPluginRemove(pkgName: string, args?: string[]): Promise<void>;
6
6
  export declare function runPluginVerify(pkgName: string): Promise<void>;
7
- export declare function runPluginInit(args?: string[]): Promise<void>;
8
- export declare function runPluginBuild(args?: string[]): Promise<void>;
9
7
  export declare function runPluginCli(args: string[]): Promise<void>;