@weavetab/mcp 2.5.0-beta.2 → 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 +1 -1
- package/ARCHITECTURE.md +6 -6
- package/LICENSE +1 -1
- package/README.md +158 -961
- package/SECURITY.md +33 -22
- package/TROUBLESHOOTING.md +2 -2
- package/dist/cdp/bridge.js +2 -1
- package/dist/cdp/connector.d.ts +2 -0
- package/dist/cdp/connector.js +8 -4
- package/dist/cdp/helpers.d.ts +4 -0
- package/dist/cdp/helpers.js +181 -0
- package/dist/cdp/omni_broker.d.ts +1 -1
- package/dist/cdp/omni_broker.js +1 -1
- package/dist/cdp/walker.d.ts +25 -1
- package/dist/cdp/walker.js +304 -7
- package/dist/cli/plugin.js +24 -17
- package/dist/cli/updater.d.ts +1 -1
- package/dist/cli/updater.js +27 -7
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.js +41 -2
- package/dist/desktop/discovery.d.ts +44 -0
- package/dist/desktop/discovery.js +364 -0
- package/dist/index.js +95 -15
- 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/injector.js +2 -2
- package/dist/plugin/interface.d.ts +76 -2
- package/dist/plugin/loader.d.ts +15 -1
- package/dist/plugin/loader.js +217 -70
- package/dist/plugin/pipeline.d.ts +75 -0
- package/dist/plugin/pipeline.js +183 -0
- package/dist/plugin/registry.js +3 -2
- 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 +428 -156
- 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 +6 -0
- package/dist/state/session.js +27 -0
- package/dist/tools/browser_detect.d.ts +16 -3
- package/dist/tools/browser_detect.js +50 -5
- package/dist/tools/burst.d.ts +17 -8
- package/dist/tools/burst.js +236 -77
- 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 +74 -70
- 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.js +29 -34
- 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.js +14 -6
- 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 +107 -58
- 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 +21 -32
- package/docs/PLUGINS.md +121 -0
- package/docs/TOOLS.md +56 -0
- package/docs/architecture/blind-injection.md +83 -20
- package/docs/architecture/overview.md +49 -49
- package/docs/configuration/folders.md +55 -54
- package/docs/desktop-automation/electron-apps.md +217 -162
- package/docs/get-started/introduction.md +60 -60
- package/docs/intelligence/site-profiles.md +151 -0
- package/docs/tools/tools.md +631 -293
- package/docs/troubleshooting/trouble-loop.md +31 -31
- package/ext-dist/background.js +10 -62
- package/ext-dist/content.js +333 -920
- 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 +101 -67
- package/package.json +5 -6
- package/server.json +3 -3
- 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.)
|
|
@@ -446,6 +510,8 @@ export async function buildActionMap(session, options = {}, targetId = "default"
|
|
|
446
510
|
role: el.role || el.tag || 'generic',
|
|
447
511
|
label: el.label || el.text || el.placeholder || '',
|
|
448
512
|
value: el.value,
|
|
513
|
+
placeholder: el.placeholder || undefined,
|
|
514
|
+
type: el.type || undefined,
|
|
449
515
|
group: 'main_content',
|
|
450
516
|
fingerprint: `${el.role || el.tag || 'generic'}:${el.label || el.text || ''}:${el.id}`,
|
|
451
517
|
depth: 0,
|
|
@@ -459,6 +525,10 @@ export async function buildActionMap(session, options = {}, targetId = "default"
|
|
|
459
525
|
className: el.className || el.attributes?.class || '',
|
|
460
526
|
structuralIdiom: el.structuralIdiom || undefined,
|
|
461
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,
|
|
462
532
|
}));
|
|
463
533
|
// Post-mapping sanitizer — catch any w:undefined/w:null and reassign.
|
|
464
534
|
// Uses a stable hash derived from the element's fingerprint, coordinates, and
|
|
@@ -590,7 +660,15 @@ export async function buildActionMap(session, options = {}, targetId = "default"
|
|
|
590
660
|
if (options.limit && filtered.length > options.limit) {
|
|
591
661
|
filtered = filtered.slice(0, options.limit);
|
|
592
662
|
}
|
|
593
|
-
|
|
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 };
|
|
594
672
|
}
|
|
595
673
|
// ─── toRefEntries — converts ActionElements to RefEntry for delta tracking ───
|
|
596
674
|
export function toRefEntries(elements) {
|
|
@@ -600,7 +678,7 @@ export function toRefEntries(elements) {
|
|
|
600
678
|
label: el.label,
|
|
601
679
|
type: el.type,
|
|
602
680
|
href: el.href,
|
|
603
|
-
disabled: el.disabled,
|
|
681
|
+
disabled: el.disabled ?? false,
|
|
604
682
|
value: el.value,
|
|
605
683
|
fingerprint: el.fingerprint,
|
|
606
684
|
}));
|
|
@@ -888,11 +966,17 @@ export async function buildAXTreeMap(session, _options = {}, targetId = "default
|
|
|
888
966
|
else {
|
|
889
967
|
value = node.value?.value !== undefined ? String(node.value.value) : undefined;
|
|
890
968
|
}
|
|
891
|
-
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 || '');
|
|
892
971
|
if (isSecretRole || (node.backendDOMNodeId !== undefined && SessionSecretTracker.isNodeSecret(targetId, node.backendDOMNodeId))) {
|
|
893
972
|
label = '[REDACTED]';
|
|
894
973
|
value = '[REDACTED]';
|
|
895
974
|
}
|
|
975
|
+
else {
|
|
976
|
+
label = targetId ? SessionSecretTracker.redact(targetId, label) : label;
|
|
977
|
+
if (value !== undefined)
|
|
978
|
+
value = targetId ? SessionSecretTracker.redact(targetId, value) : value;
|
|
979
|
+
}
|
|
896
980
|
// Use globally-unique sequential ID (Bug #5 fix: was local idCounter = 1).
|
|
897
981
|
const id = dedupeRefId(`w:${axIdCursor++}`);
|
|
898
982
|
elements.push({
|
|
@@ -944,3 +1028,216 @@ export async function buildAXTreeMap(session, _options = {}, targetId = "default
|
|
|
944
1028
|
axTreeCacheSet(url, map);
|
|
945
1029
|
return map;
|
|
946
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.js
CHANGED
|
@@ -5,17 +5,18 @@ import ora from "ora";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
|
+
import { WEAVETAB_VERSION } from "../version.js";
|
|
8
9
|
const _require = createRequire(import.meta.url);
|
|
9
10
|
function getVersion() {
|
|
10
11
|
try {
|
|
11
|
-
return _require("../../package.json").version ||
|
|
12
|
+
return _require("../../package.json").version || WEAVETAB_VERSION;
|
|
12
13
|
}
|
|
13
14
|
catch {
|
|
14
|
-
return
|
|
15
|
+
return WEAVETAB_VERSION;
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
import { getVerifiedPackages, resolvePluginTrust, resolvePluginCandidates, REGISTRY_URL, } from "../plugin/registry.js";
|
|
18
|
-
import { listInstalledPlugins, inspectInstalledPackage } from "../plugin/loader.js";
|
|
18
|
+
import { getVerifiedPackages, resolvePluginTrust, resolvePluginCandidates, invalidateRegistryCache, REGISTRY_URL, } from "../plugin/registry.js";
|
|
19
|
+
import { listInstalledPlugins, inspectInstalledPackage, normalizePermissions } from "../plugin/loader.js";
|
|
19
20
|
// Helper to run child process safely
|
|
20
21
|
function runCommand(cmd, cmdArgs, cwd = process.cwd()) {
|
|
21
22
|
return new Promise((resolve) => {
|
|
@@ -23,7 +24,7 @@ function runCommand(cmd, cmdArgs, cwd = process.cwd()) {
|
|
|
23
24
|
const executable = isWin && !cmd.endsWith(".cmd") && !cmd.endsWith(".exe") ? `${cmd}.cmd` : cmd;
|
|
24
25
|
const child = spawn(executable, cmdArgs, {
|
|
25
26
|
cwd,
|
|
26
|
-
shell:
|
|
27
|
+
shell: false,
|
|
27
28
|
stdio: ["ignore", "pipe", "pipe"],
|
|
28
29
|
});
|
|
29
30
|
let stdout = "";
|
|
@@ -167,14 +168,14 @@ export async function runPluginAdd(inputName, args = []) {
|
|
|
167
168
|
}
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
|
-
spinner.succeed(pc.green(`
|
|
171
|
+
spinner.succeed(pc.green(`installed ${pc.bold(selectedPkg)}`));
|
|
171
172
|
// Inspect installed package for weavetab.json
|
|
172
173
|
const targetDir = path.join(process.cwd(), "node_modules", selectedPkg);
|
|
173
174
|
const installedInfo = inspectInstalledPackage(targetDir);
|
|
174
175
|
if (installedInfo && installedInfo.manifest) {
|
|
175
176
|
const manifest = installedInfo.manifest;
|
|
176
177
|
const tools = manifest.tools || [];
|
|
177
|
-
const permissions = manifest.permissions
|
|
178
|
+
const permissions = normalizePermissions(manifest.permissions);
|
|
178
179
|
const infoLines = [
|
|
179
180
|
`${pc.bold("Plugin Details:")}`,
|
|
180
181
|
` ${pc.dim("Name:")} ${manifest.name || selectedPkg}`,
|
|
@@ -387,7 +388,7 @@ export async function runPluginRemove(pkgName, args = []) {
|
|
|
387
388
|
console.error(pc.dim(result.stderr));
|
|
388
389
|
return;
|
|
389
390
|
}
|
|
390
|
-
spinner.succeed(pc.green(`
|
|
391
|
+
spinner.succeed(pc.green(`removed ${pc.bold(pkgName)}`));
|
|
391
392
|
console.log(pc.dim("The plugin and its tools have been uninstalled.\n"));
|
|
392
393
|
}
|
|
393
394
|
// ---------------------------------------------------------------------------
|
|
@@ -419,7 +420,12 @@ export async function runPluginVerify(pkgName) {
|
|
|
419
420
|
// 7. MAIN CLI ROUTER FOR PLUGINS (User / Operations Mode)
|
|
420
421
|
// ---------------------------------------------------------------------------
|
|
421
422
|
export async function runPluginCli(args) {
|
|
422
|
-
|
|
423
|
+
if (args.includes("--refresh")) {
|
|
424
|
+
invalidateRegistryCache();
|
|
425
|
+
console.log(pc.green("[OK] Plugin registry cache refreshed.\n"));
|
|
426
|
+
}
|
|
427
|
+
const cleanArgs = args.filter((a) => a !== "--refresh");
|
|
428
|
+
const subcmd = cleanArgs[0]?.toLowerCase();
|
|
423
429
|
if (!subcmd || subcmd === "--help" || subcmd === "-h" || subcmd === "help") {
|
|
424
430
|
console.log();
|
|
425
431
|
console.log(boxen(pc.bold(pc.cyan(" WEAVETAB PLUGIN MANAGER ")), { padding: 0, borderStyle: "round", borderColor: "cyan" }));
|
|
@@ -428,7 +434,7 @@ export async function runPluginCli(args) {
|
|
|
428
434
|
console.log(` weavetab plugin <command> [options]`);
|
|
429
435
|
console.log();
|
|
430
436
|
console.log(pc.bold("COMMANDS"));
|
|
431
|
-
console.log(` ${pc.cyan("add <pkg>")} Install
|
|
437
|
+
console.log(` ${pc.cyan("add <pkg>")} Install any plugin by npm package name (e.g. @fy2ne/wt-youtube, my-plugin)`);
|
|
432
438
|
console.log(` ${pc.cyan("browse")} Interactive terminal explorer to browse & 1-click install`);
|
|
433
439
|
console.log(` ${pc.cyan("search [query]")} Search plugins across official registry & npm`);
|
|
434
440
|
console.log(` ${pc.cyan("list")} List all installed plugins and their registered tools`);
|
|
@@ -436,10 +442,11 @@ export async function runPluginCli(args) {
|
|
|
436
442
|
console.log(` ${pc.cyan("verify <pkg>")} Check trust tier and registry verification status`);
|
|
437
443
|
console.log();
|
|
438
444
|
console.log(pc.bold("DEVELOPER SDK (@weavetab/sdk)"));
|
|
439
|
-
console.log(` ${pc.dim("Scaffold a plugin:")} ${pc.cyan("npx
|
|
445
|
+
console.log(` ${pc.dim("Scaffold a plugin:")} ${pc.cyan("npx @weavetab/sdk init <name>")}`);
|
|
440
446
|
console.log(` ${pc.dim("Build & validate:")} ${pc.cyan("npx @weavetab/sdk build")}`);
|
|
441
447
|
console.log();
|
|
442
|
-
console.log(pc.bold("OPTIONS (for 'add')"));
|
|
448
|
+
console.log(pc.bold("OPTIONS (for 'add' / general)"));
|
|
449
|
+
console.log(` ${pc.cyan("--refresh")} Invalidate and refresh the verified plugin cache`);
|
|
443
450
|
console.log(` ${pc.cyan("--official, -O")} Prefer official @weavetab/plugin-<name>`);
|
|
444
451
|
console.log(` ${pc.cyan("--community, -C")} Prefer community package`);
|
|
445
452
|
console.log(` ${pc.cyan("--yes, -y")} Bypass interactive warning prompt for unverified plugins`);
|
|
@@ -448,13 +455,13 @@ export async function runPluginCli(args) {
|
|
|
448
455
|
}
|
|
449
456
|
if (subcmd === "init" || subcmd === "create" || subcmd === "new" || subcmd === "build" || subcmd === "manifest") {
|
|
450
457
|
console.log(pc.yellow(`\n[Weavetab] Plugin authoring commands belong to the developer SDK (@weavetab/sdk).`));
|
|
451
|
-
console.log(pc.dim("To scaffold a new plugin: ") + pc.cyan("npx
|
|
458
|
+
console.log(pc.dim("To scaffold a new plugin: ") + pc.cyan("npx @weavetab/sdk init <name>"));
|
|
452
459
|
console.log(pc.dim("To build/validate a plugin: ") + pc.cyan("npx @weavetab/sdk build"));
|
|
453
460
|
console.log(pc.dim("SDK documentation: ") + pc.cyan("https://github.com/weavetab/sdk\n"));
|
|
454
461
|
return;
|
|
455
462
|
}
|
|
456
463
|
if (subcmd === "add" || subcmd === "install" || subcmd === "i") {
|
|
457
|
-
await runPluginAdd(
|
|
464
|
+
await runPluginAdd(cleanArgs[1], cleanArgs.slice(2));
|
|
458
465
|
return;
|
|
459
466
|
}
|
|
460
467
|
if (subcmd === "browse" || subcmd === "explore") {
|
|
@@ -462,7 +469,7 @@ export async function runPluginCli(args) {
|
|
|
462
469
|
return;
|
|
463
470
|
}
|
|
464
471
|
if (subcmd === "search" || subcmd === "find") {
|
|
465
|
-
await runPluginSearch(
|
|
472
|
+
await runPluginSearch(cleanArgs[1]);
|
|
466
473
|
return;
|
|
467
474
|
}
|
|
468
475
|
if (subcmd === "list" || subcmd === "ls") {
|
|
@@ -470,11 +477,11 @@ export async function runPluginCli(args) {
|
|
|
470
477
|
return;
|
|
471
478
|
}
|
|
472
479
|
if (subcmd === "remove" || subcmd === "rm" || subcmd === "uninstall") {
|
|
473
|
-
await runPluginRemove(
|
|
480
|
+
await runPluginRemove(cleanArgs[1], cleanArgs.slice(2));
|
|
474
481
|
return;
|
|
475
482
|
}
|
|
476
483
|
if (subcmd === "verify" || subcmd === "check") {
|
|
477
|
-
await runPluginVerify(
|
|
484
|
+
await runPluginVerify(cleanArgs[1]);
|
|
478
485
|
return;
|
|
479
486
|
}
|
|
480
487
|
console.log(pc.red(`[ERROR] Unknown plugin subcommand: "${subcmd}"`));
|
package/dist/cli/updater.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function checkForUpdates(): Promise<void>;
|
|
1
|
+
export declare function checkForUpdates(force?: boolean): Promise<void>;
|
package/dist/cli/updater.js
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
6
|
const __dirname = path.dirname(__filename);
|
|
6
|
-
|
|
7
|
+
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".weavetab-system", "cache", "update_check.json");
|
|
8
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
9
|
+
export async function checkForUpdates(force = false) {
|
|
7
10
|
try {
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
// Check cache unless forced
|
|
13
|
+
if (!force && fs.existsSync(UPDATE_CACHE_FILE)) {
|
|
14
|
+
try {
|
|
15
|
+
const cached = JSON.parse(fs.readFileSync(UPDATE_CACHE_FILE, "utf-8"));
|
|
16
|
+
if (cached.lastChecked && now - cached.lastChecked < CHECK_INTERVAL_MS) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch { /* malformed cache */ }
|
|
21
|
+
}
|
|
8
22
|
// Read local package.json to get current version
|
|
9
23
|
const packageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
10
24
|
if (!fs.existsSync(packageJsonPath))
|
|
11
25
|
return;
|
|
12
26
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
13
27
|
const currentVersion = pkg.version;
|
|
14
|
-
// Fast, non-blocking fetch with
|
|
28
|
+
// Fast, non-blocking fetch with 1.5-second timeout
|
|
15
29
|
const controller = new AbortController();
|
|
16
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
30
|
+
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
|
17
31
|
const res = await fetch("https://registry.npmjs.org/@weavetab/mcp/latest", {
|
|
18
32
|
signal: controller.signal,
|
|
19
33
|
});
|
|
@@ -22,12 +36,18 @@ export async function checkForUpdates() {
|
|
|
22
36
|
return;
|
|
23
37
|
const data = await res.json();
|
|
24
38
|
const latestVersion = data.version;
|
|
25
|
-
//
|
|
39
|
+
// Save timestamp to cache
|
|
40
|
+
try {
|
|
41
|
+
fs.mkdirSync(path.dirname(UPDATE_CACHE_FILE), { recursive: true });
|
|
42
|
+
fs.writeFileSync(UPDATE_CACHE_FILE, JSON.stringify({ lastChecked: now, latestVersion }), "utf-8");
|
|
43
|
+
}
|
|
44
|
+
catch { /* non-fatal */ }
|
|
45
|
+
// Simple version comparison
|
|
26
46
|
if (isNewerVersion(currentVersion, latestVersion)) {
|
|
27
47
|
await printUpdateBox(currentVersion, latestVersion);
|
|
28
48
|
}
|
|
29
49
|
}
|
|
30
|
-
catch
|
|
50
|
+
catch {
|
|
31
51
|
// Silently fail if offline or npm is down
|
|
32
52
|
}
|
|
33
53
|
}
|
|
@@ -52,7 +72,7 @@ async function printUpdateBox(current, latest) {
|
|
|
52
72
|
chalk.bold(`Update available! `) + chalk.dim(current) + chalk.reset(` → `) + chalk.green(latest),
|
|
53
73
|
`Run ${chalk.cyan("npm i -g @weavetab/mcp")} to update.`,
|
|
54
74
|
``,
|
|
55
|
-
`Changelog: ${chalk.blue(`https://github.com/weavetab/mcp/
|
|
75
|
+
`Changelog: ${chalk.blue(`https://github.com/weavetab/mcp/releases/tag/v${latest}`)}`
|
|
56
76
|
].join("\n");
|
|
57
77
|
const box = boxen(msg, {
|
|
58
78
|
padding: 1,
|
|
@@ -67,6 +87,6 @@ async function printUpdateBox(current, latest) {
|
|
|
67
87
|
// Fallback if formatting fails
|
|
68
88
|
console.error(`\n[Weavetab MCP] Update available: ${current} -> ${latest}`);
|
|
69
89
|
console.error(`Run "npm i -g @weavetab/mcp" to update.`);
|
|
70
|
-
console.error(`Changelog: https://github.com/weavetab/mcp/
|
|
90
|
+
console.error(`Changelog: https://github.com/weavetab/mcp/releases/tag/v${latest}\n`);
|
|
71
91
|
}
|
|
72
92
|
}
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -136,4 +136,12 @@ export declare function getSystemAppDir(appName: string): string;
|
|
|
136
136
|
* Alias for consistency — identical to getSystemDir.
|
|
137
137
|
*/
|
|
138
138
|
export declare function getGlobalSystemDir(subdir: string): string;
|
|
139
|
+
/**
|
|
140
|
+
* Returns the canonical directory for persistent site profiles and learned workflow patterns.
|
|
141
|
+
* Lives in ~/.weavetab-system/memory/sites/ — strictly machine-managed, never user-authored.
|
|
142
|
+
* Separated from the legacy selector-memory dir (~/.weavetab-system/memory/) so existing
|
|
143
|
+
* per-domain selector caches are not disrupted.
|
|
144
|
+
*/
|
|
145
|
+
export declare function getSystemMemorySitesDir(): string;
|
|
139
146
|
export declare function saveConfig(config: Config): void;
|
|
147
|
+
export declare function stopConfigWatcher(): void;
|