@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
package/dist/cdp/walker.js
CHANGED
|
@@ -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]
|
|
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
|
|
313
|
-
var
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/cli/plugin.d.ts
CHANGED
|
@@ -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>;
|