@weavetab/mcp 2.5.0-beta.0
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 +48 -0
- package/ARCHITECTURE.md +590 -0
- package/LICENSE +631 -0
- package/README.md +909 -0
- package/SECURITY.md +196 -0
- package/TROUBLESHOOTING.md +365 -0
- package/dist/anticaptcha/detector.d.ts +49 -0
- package/dist/anticaptcha/detector.js +465 -0
- package/dist/anticaptcha/integration.d.ts +36 -0
- package/dist/anticaptcha/integration.js +123 -0
- package/dist/anticaptcha/solver.d.ts +27 -0
- package/dist/anticaptcha/solver.js +552 -0
- package/dist/audit/logger.d.ts +13 -0
- package/dist/audit/logger.js +177 -0
- package/dist/audit/telemetry.d.ts +36 -0
- package/dist/audit/telemetry.js +162 -0
- package/dist/cdp/bridge.d.ts +16 -0
- package/dist/cdp/bridge.js +826 -0
- package/dist/cdp/confirm.d.ts +26 -0
- package/dist/cdp/confirm.js +79 -0
- package/dist/cdp/connector.d.ts +37 -0
- package/dist/cdp/connector.js +661 -0
- package/dist/cdp/extension.d.ts +36 -0
- package/dist/cdp/extension.js +118 -0
- package/dist/cdp/finder.d.ts +1 -0
- package/dist/cdp/finder.js +2 -0
- package/dist/cdp/helpers.d.ts +11 -0
- package/dist/cdp/helpers.js +195 -0
- package/dist/cdp/label_cache.d.ts +12 -0
- package/dist/cdp/label_cache.js +51 -0
- package/dist/cdp/lock.d.ts +33 -0
- package/dist/cdp/lock.js +206 -0
- package/dist/cdp/omni_broker.d.ts +17 -0
- package/dist/cdp/omni_broker.js +119 -0
- package/dist/cdp/profiles.d.ts +1 -0
- package/dist/cdp/profiles.js +87 -0
- package/dist/cdp/pruner.d.ts +41 -0
- package/dist/cdp/pruner.js +263 -0
- package/dist/cdp/ref_cache.d.ts +101 -0
- package/dist/cdp/ref_cache.js +234 -0
- package/dist/cdp/retry.d.ts +5 -0
- package/dist/cdp/retry.js +39 -0
- package/dist/cdp/retry_enhanced.d.ts +44 -0
- package/dist/cdp/retry_enhanced.js +102 -0
- package/dist/cdp/security.d.ts +50 -0
- package/dist/cdp/security.js +237 -0
- package/dist/cdp/verify.d.ts +39 -0
- package/dist/cdp/verify.js +143 -0
- package/dist/cdp/walker.d.ts +91 -0
- package/dist/cdp/walker.js +896 -0
- package/dist/cdp/walker.test.d.ts +2 -0
- package/dist/cdp/walker.test.js +172 -0
- package/dist/cdp/websocket_transport.d.ts +15 -0
- package/dist/cdp/websocket_transport.js +118 -0
- package/dist/cli/plugin.d.ts +9 -0
- package/dist/cli/plugin.js +1134 -0
- package/dist/cli/updater.d.ts +1 -0
- package/dist/cli/updater.js +72 -0
- package/dist/config/loader.d.ts +120 -0
- package/dist/config/loader.js +704 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1248 -0
- package/dist/intelligence/hints.d.ts +11 -0
- package/dist/intelligence/hints.js +39 -0
- package/dist/intelligence/intent.d.ts +33 -0
- package/dist/intelligence/intent.js +171 -0
- package/dist/intelligence/sessionMemory.d.ts +66 -0
- package/dist/intelligence/sessionMemory.js +284 -0
- package/dist/intelligence/sessionMemory.test.d.ts +2 -0
- package/dist/intelligence/sessionMemory.test.js +62 -0
- package/dist/intelligence/trail.d.ts +64 -0
- package/dist/intelligence/trail.js +233 -0
- package/dist/intelligence/trail.test.d.ts +2 -0
- package/dist/intelligence/trail.test.js +110 -0
- package/dist/intelligence/vision.d.ts +24 -0
- package/dist/intelligence/vision.js +151 -0
- package/dist/lifecycle/mission_manager.d.ts +16 -0
- package/dist/lifecycle/mission_manager.js +44 -0
- package/dist/overlay/bubble.d.ts +16 -0
- package/dist/overlay/bubble.js +37 -0
- package/dist/overlay/ghost.d.ts +35 -0
- package/dist/overlay/ghost.js +508 -0
- package/dist/overlay/injector.d.ts +22 -0
- package/dist/overlay/injector.js +198 -0
- package/dist/plugin/interface.d.ts +134 -0
- package/dist/plugin/interface.js +1 -0
- package/dist/plugin/loader.d.ts +18 -0
- package/dist/plugin/loader.js +155 -0
- package/dist/plugin/registry.d.ts +50 -0
- package/dist/plugin/registry.js +234 -0
- package/dist/security/blacklist.d.ts +24 -0
- package/dist/security/blacklist.js +80 -0
- package/dist/security/filter.d.ts +2 -0
- package/dist/security/filter.js +20 -0
- package/dist/security/guard.d.ts +8 -0
- package/dist/security/guard.js +35 -0
- package/dist/security/ratelimit.d.ts +11 -0
- package/dist/security/ratelimit.js +57 -0
- package/dist/security/secrets.d.ts +75 -0
- package/dist/security/secrets.js +365 -0
- package/dist/sensors/actionability.d.ts +81 -0
- package/dist/sensors/actionability.js +303 -0
- package/dist/sensors/guard.d.ts +25 -0
- package/dist/sensors/guard.js +168 -0
- package/dist/sensors/mutations.d.ts +23 -0
- package/dist/sensors/mutations.js +93 -0
- package/dist/sensors/network.d.ts +67 -0
- package/dist/sensors/network.js +193 -0
- package/dist/sensors/operation_monitor.d.ts +80 -0
- package/dist/sensors/operation_monitor.js +203 -0
- package/dist/sensors/pacing.d.ts +44 -0
- package/dist/sensors/pacing.js +111 -0
- package/dist/sensors/thoughts.d.ts +2 -0
- package/dist/sensors/thoughts.js +59 -0
- package/dist/sensors/wait.d.ts +12 -0
- package/dist/sensors/wait.js +44 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +1687 -0
- package/dist/state/agent.d.ts +36 -0
- package/dist/state/agent.js +101 -0
- package/dist/state/cache.d.ts +16 -0
- package/dist/state/cache.js +92 -0
- package/dist/state/cache.test.d.ts +2 -0
- package/dist/state/cache.test.js +76 -0
- package/dist/state/delta.d.ts +71 -0
- package/dist/state/delta.js +234 -0
- package/dist/state/delta.test.d.ts +2 -0
- package/dist/state/delta.test.js +279 -0
- package/dist/state/groups.d.ts +15 -0
- package/dist/state/groups.js +80 -0
- package/dist/state/session.d.ts +30 -0
- package/dist/state/session.js +155 -0
- package/dist/tools/accessibility.d.ts +14 -0
- package/dist/tools/accessibility.js +32 -0
- package/dist/tools/ask.d.ts +14 -0
- package/dist/tools/ask.js +49 -0
- package/dist/tools/automation.d.ts +36 -0
- package/dist/tools/automation.js +218 -0
- package/dist/tools/browser_detect.d.ts +13 -0
- package/dist/tools/browser_detect.js +73 -0
- package/dist/tools/burst.d.ts +35 -0
- package/dist/tools/burst.js +344 -0
- package/dist/tools/canvas.d.ts +25 -0
- package/dist/tools/canvas.js +132 -0
- package/dist/tools/captcha.d.ts +38 -0
- package/dist/tools/captcha.js +209 -0
- package/dist/tools/click.d.ts +36 -0
- package/dist/tools/click.js +561 -0
- package/dist/tools/click_and_wait.d.ts +33 -0
- package/dist/tools/click_and_wait.js +149 -0
- package/dist/tools/clipboard.d.ts +29 -0
- package/dist/tools/clipboard.js +279 -0
- package/dist/tools/console.d.ts +21 -0
- package/dist/tools/console.js +107 -0
- package/dist/tools/cookies.d.ts +67 -0
- package/dist/tools/cookies.js +121 -0
- package/dist/tools/design_extract.d.ts +109 -0
- package/dist/tools/design_extract.js +641 -0
- package/dist/tools/dialog.d.ts +24 -0
- package/dist/tools/dialog.js +109 -0
- package/dist/tools/drag.d.ts +15 -0
- package/dist/tools/drag.js +29 -0
- package/dist/tools/emulation.d.ts +33 -0
- package/dist/tools/emulation.js +69 -0
- package/dist/tools/eval.d.ts +20 -0
- package/dist/tools/eval.js +92 -0
- package/dist/tools/fill.d.ts +41 -0
- package/dist/tools/fill.js +667 -0
- package/dist/tools/find.d.ts +30 -0
- package/dist/tools/find.js +60 -0
- package/dist/tools/geo.d.ts +19 -0
- package/dist/tools/geo.js +29 -0
- package/dist/tools/github/analyze.d.ts +4 -0
- package/dist/tools/github/analyze.js +113 -0
- package/dist/tools/github/cache.d.ts +21 -0
- package/dist/tools/github/cache.js +28 -0
- package/dist/tools/github/index.d.ts +4 -0
- package/dist/tools/github/index.js +4 -0
- package/dist/tools/github/issues.d.ts +9 -0
- package/dist/tools/github/issues.js +88 -0
- package/dist/tools/github/pr.d.ts +5 -0
- package/dist/tools/github/pr.js +88 -0
- package/dist/tools/github/rateLimiter.d.ts +4 -0
- package/dist/tools/github/rateLimiter.js +46 -0
- package/dist/tools/github/read.d.ts +34 -0
- package/dist/tools/github/read.js +74 -0
- package/dist/tools/github/repoContext.d.ts +20 -0
- package/dist/tools/github/repoContext.js +129 -0
- package/dist/tools/highlight.d.ts +20 -0
- package/dist/tools/highlight.js +140 -0
- package/dist/tools/hover.d.ts +19 -0
- package/dist/tools/hover.js +57 -0
- package/dist/tools/inspect.d.ts +17 -0
- package/dist/tools/inspect.js +125 -0
- package/dist/tools/integration.test.d.ts +2 -0
- package/dist/tools/integration.test.js +96 -0
- package/dist/tools/key.d.ts +25 -0
- package/dist/tools/key.js +173 -0
- package/dist/tools/macro_compiler.d.ts +7 -0
- package/dist/tools/macro_compiler.js +82 -0
- package/dist/tools/mouse.d.ts +21 -0
- package/dist/tools/mouse.js +62 -0
- package/dist/tools/navigate.d.ts +16 -0
- package/dist/tools/navigate.js +188 -0
- package/dist/tools/network_intercept.d.ts +17 -0
- package/dist/tools/network_intercept.js +151 -0
- package/dist/tools/office/index.d.ts +9 -0
- package/dist/tools/office/index.js +7 -0
- package/dist/tools/office/list.d.ts +15 -0
- package/dist/tools/office/list.js +41 -0
- package/dist/tools/office/parse.d.ts +20 -0
- package/dist/tools/office/parse.js +172 -0
- package/dist/tools/pdf.d.ts +22 -0
- package/dist/tools/pdf.js +42 -0
- package/dist/tools/performance.d.ts +12 -0
- package/dist/tools/performance.js +47 -0
- package/dist/tools/performance_test.d.ts +70 -0
- package/dist/tools/performance_test.js +128 -0
- package/dist/tools/plan.d.ts +39 -0
- package/dist/tools/plan.js +161 -0
- package/dist/tools/pointer.d.ts +35 -0
- package/dist/tools/pointer.js +522 -0
- package/dist/tools/read.d.ts +30 -0
- package/dist/tools/read.js +488 -0
- package/dist/tools/recording.d.ts +45 -0
- package/dist/tools/recording.js +124 -0
- package/dist/tools/response.d.ts +129 -0
- package/dist/tools/response.js +5 -0
- package/dist/tools/scrape.d.ts +24 -0
- package/dist/tools/scrape.js +140 -0
- package/dist/tools/screenshot.d.ts +40 -0
- package/dist/tools/screenshot.js +79 -0
- package/dist/tools/scroll.d.ts +23 -0
- package/dist/tools/scroll.js +196 -0
- package/dist/tools/select.d.ts +13 -0
- package/dist/tools/select.js +217 -0
- package/dist/tools/snapshot.d.ts +27 -0
- package/dist/tools/snapshot.js +99 -0
- package/dist/tools/storage.d.ts +18 -0
- package/dist/tools/storage.js +29 -0
- package/dist/tools/tabs.d.ts +77 -0
- package/dist/tools/tabs.js +342 -0
- package/dist/tools/thought.d.ts +16 -0
- package/dist/tools/thought.js +79 -0
- package/dist/tools/type.d.ts +65 -0
- package/dist/tools/type.js +1061 -0
- package/dist/tools/type_secret.d.ts +23 -0
- package/dist/tools/type_secret.js +229 -0
- package/dist/tools/upload.d.ts +19 -0
- package/dist/tools/upload.js +103 -0
- package/dist/tools/video_compiler.d.ts +24 -0
- package/dist/tools/video_compiler.js +199 -0
- package/dist/tools/viewport.d.ts +44 -0
- package/dist/tools/viewport.js +230 -0
- package/dist/tools/wait.d.ts +19 -0
- package/dist/tools/wait.js +202 -0
- package/dist/tools/wait_for.d.ts +23 -0
- package/dist/tools/wait_for.js +30 -0
- package/dist/tools/windows.d.ts +22 -0
- package/dist/tools/windows.js +57 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +12 -0
- package/docs/DEV_MODE.md +68 -0
- package/docs/DOCUMENTATION.md +532 -0
- package/docs/PLUGINS.md +538 -0
- package/docs/TOOLS.md +104 -0
- package/docs/dev_plugin.md +310 -0
- package/ext-dist/background.js +467 -0
- package/ext-dist/content.js +3089 -0
- package/ext-dist/dashboard.html +583 -0
- package/ext-dist/hud/hud.js +73 -0
- package/ext-dist/hud/index.html +20 -0
- package/ext-dist/manifest.json +77 -0
- package/ext-dist/sidepanel/sidepanel.js +25 -0
- package/ext-dist/sidepanel.html +547 -0
- package/ext-dist/styles.css +218 -0
- package/icon.png +0 -0
- package/mcp.json +796 -0
- package/package.json +113 -0
- package/server.json +23 -0
- package/smithery.yaml +11 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — cdp/ref_cache.ts
|
|
3
|
+
* Unified Ref Coordinate Cache (URC)
|
|
4
|
+
*
|
|
5
|
+
* Single in-memory, per-tab store of refId → {selector, x, y, backendNodeId, fingerprint}
|
|
6
|
+
* populated once by buildActionMap/buildAXTreeMap and read by every interaction tool.
|
|
7
|
+
*
|
|
8
|
+
* Design goals:
|
|
9
|
+
* - Zero extra CDP calls on cache hit (< TTL)
|
|
10
|
+
* - One atomic Runtime.evaluate on stale/miss (atomicResolve)
|
|
11
|
+
* - Keyed strictly by targetId (frame-scoped) — iframe contexts never bleed
|
|
12
|
+
* - Auto-invalidated on frameNavigated
|
|
13
|
+
*
|
|
14
|
+
* Weavetab by fy2ne
|
|
15
|
+
*/
|
|
16
|
+
// ─── Storage ──────────────────────────────────────────────────────────────────
|
|
17
|
+
/** Per-tab: Map<targetId, Map<refId, RefCoordEntry>> */
|
|
18
|
+
const store = new Map();
|
|
19
|
+
/** Default TTL: 30 seconds. Overridden by config.refCacheTtlMs at call sites. */
|
|
20
|
+
export const DEFAULT_URC_TTL_MS = 30_000;
|
|
21
|
+
// ─── Stale-miss counters (foundation for adaptive TTL — Q3 follow-up) ─────────
|
|
22
|
+
const staleMissCounters = new Map();
|
|
23
|
+
function recordStaleMiss(hostname) {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const entry = staleMissCounters.get(hostname) ?? { count: 0, lastDecayAt: now };
|
|
26
|
+
// Decay: halve the counter every 5 minutes
|
|
27
|
+
const decayIntervals = Math.floor((now - entry.lastDecayAt) / (5 * 60_000));
|
|
28
|
+
if (decayIntervals > 0) {
|
|
29
|
+
entry.count = Math.floor(entry.count / Math.pow(2, decayIntervals));
|
|
30
|
+
entry.lastDecayAt = now;
|
|
31
|
+
}
|
|
32
|
+
entry.count += 1;
|
|
33
|
+
staleMissCounters.set(hostname, entry);
|
|
34
|
+
}
|
|
35
|
+
// Sweep staleMissCounters entries that have decayed to 0 so hostnames don't
|
|
36
|
+
// accumulate indefinitely. Runs every 10 minutes via an unref'd interval.
|
|
37
|
+
setInterval(() => {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
for (const [hostname, entry] of staleMissCounters.entries()) {
|
|
40
|
+
const decayIntervals = Math.floor((now - entry.lastDecayAt) / (5 * 60_000));
|
|
41
|
+
const decayed = decayIntervals > 0
|
|
42
|
+
? Math.floor(entry.count / Math.pow(2, decayIntervals))
|
|
43
|
+
: entry.count;
|
|
44
|
+
if (decayed <= 0)
|
|
45
|
+
staleMissCounters.delete(hostname);
|
|
46
|
+
}
|
|
47
|
+
}, 10 * 60_000).unref();
|
|
48
|
+
/** Returns stale miss rate [0–1] for the last observation window. */
|
|
49
|
+
export function getStaleMissRate(hostname) {
|
|
50
|
+
const entry = staleMissCounters.get(hostname);
|
|
51
|
+
if (!entry)
|
|
52
|
+
return 0;
|
|
53
|
+
// Simple heuristic: count > 5 within the window → elevated stale rate
|
|
54
|
+
return Math.min(1, entry.count / 20);
|
|
55
|
+
}
|
|
56
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
57
|
+
/** Write an entry into the URC for a given tab and refId. */
|
|
58
|
+
export function urcSet(targetId, refId, entry) {
|
|
59
|
+
if (!store.has(targetId))
|
|
60
|
+
store.set(targetId, new Map());
|
|
61
|
+
store.get(targetId).set(refId, entry);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read an entry from the URC.
|
|
65
|
+
* Returns null if: not found, OR entry is older than maxAgeMs.
|
|
66
|
+
*/
|
|
67
|
+
export function urcGet(targetId, refId, maxAgeMs = DEFAULT_URC_TTL_MS) {
|
|
68
|
+
const tabMap = store.get(targetId);
|
|
69
|
+
if (!tabMap)
|
|
70
|
+
return null;
|
|
71
|
+
const entry = tabMap.get(refId);
|
|
72
|
+
if (!entry)
|
|
73
|
+
return null;
|
|
74
|
+
if (Date.now() - entry.capturedAt > maxAgeMs)
|
|
75
|
+
return null;
|
|
76
|
+
return entry;
|
|
77
|
+
}
|
|
78
|
+
/** Return all cached entries for a tab (used by health checks and delta validation). */
|
|
79
|
+
export function urcGetAll(targetId) {
|
|
80
|
+
return store.get(targetId) ?? new Map();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Invalidate all URC entries for a tab.
|
|
84
|
+
* Must be called on: frameNavigated, full map rebuild, tab close.
|
|
85
|
+
*/
|
|
86
|
+
export function urcInvalidate(targetId) {
|
|
87
|
+
store.delete(targetId);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Bulk-populate the URC from a freshly built ActionElement array.
|
|
91
|
+
* Called at the end of buildActionMap and buildAXTreeMap.
|
|
92
|
+
* Only writes entries that have both x and selector defined.
|
|
93
|
+
*/
|
|
94
|
+
export function urcPopulate(targetId, elements) {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
for (const el of elements) {
|
|
97
|
+
if (!el.selector || el.x === undefined || el.y === undefined)
|
|
98
|
+
continue;
|
|
99
|
+
const fingerprint = el.fingerprint ??
|
|
100
|
+
`${el.role ?? ""}:${el.type ?? ""}:${(el.label ?? "").slice(0, 30)}`;
|
|
101
|
+
urcSet(targetId, el.id, {
|
|
102
|
+
selector: el.selector,
|
|
103
|
+
x: el.x,
|
|
104
|
+
y: el.y,
|
|
105
|
+
capturedAt: now,
|
|
106
|
+
backendNodeId: el.backendNodeId,
|
|
107
|
+
fingerprint,
|
|
108
|
+
visualHash: el.visualHash,
|
|
109
|
+
xpath: el.xpath,
|
|
110
|
+
value: el.value,
|
|
111
|
+
label: el.label,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ─── Atomic Resolution ────────────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Single CDP Runtime.evaluate that simultaneously:
|
|
118
|
+
* 1. Finds the element via selector (with WeakMap re-hydration fallback)
|
|
119
|
+
* 2. Validates tag/role fingerprint (lightweight string check)
|
|
120
|
+
* 3. Returns fresh coordinates from getBoundingClientRect
|
|
121
|
+
*
|
|
122
|
+
* Cost: 1 CDP call (~2–5ms). On a URC cache hit this is the ONLY extra call made.
|
|
123
|
+
* On a full miss (Stage 3+) it is still cheaper than re-running buildActionMap.
|
|
124
|
+
*/
|
|
125
|
+
export async function atomicResolve(session, selector, fingerprint, hostname) {
|
|
126
|
+
try {
|
|
127
|
+
const { result } = await session.Runtime.evaluate({
|
|
128
|
+
expression: `(function(sel, fp) {
|
|
129
|
+
try {
|
|
130
|
+
// Primary: standard querySelector
|
|
131
|
+
var el = document.querySelector(sel);
|
|
132
|
+
|
|
133
|
+
// Shadow DOM fallback
|
|
134
|
+
if (!el && typeof window.__Weavetab_shadowQuery === 'function') {
|
|
135
|
+
el = window.__Weavetab_shadowQuery(sel);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// WeakMap registry fallback: re-derive selector from stored data
|
|
139
|
+
if (!el && window.__wt_registry) {
|
|
140
|
+
// Walk the registry to find the element whose stored id matches
|
|
141
|
+
// (only feasible for small pages — skip for large DOMs by checking size)
|
|
142
|
+
try {
|
|
143
|
+
var allEls = document.querySelectorAll('[data-wt-id]');
|
|
144
|
+
for (var i = 0; i < allEls.length; i++) {
|
|
145
|
+
var candidate = allEls[i];
|
|
146
|
+
var entry = window.__wt_registry.get(candidate);
|
|
147
|
+
if (entry && ('[data-wt-id="' + entry.id + '"]' === sel ||
|
|
148
|
+
entry.selector === sel)) {
|
|
149
|
+
el = candidate;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (e) {}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!el) return { status: 'NOT_FOUND' };
|
|
157
|
+
|
|
158
|
+
// Fingerprint check: "role:type:labelPrefix"
|
|
159
|
+
if (fp) {
|
|
160
|
+
var parts = fp.split(':');
|
|
161
|
+
var fpRole = parts[0];
|
|
162
|
+
var fpType = parts[1];
|
|
163
|
+
var elRole = el.getAttribute('role') || el.tagName.toLowerCase();
|
|
164
|
+
var elType = (el.type || '').toLowerCase();
|
|
165
|
+
if (fpRole && fpRole !== '' && elRole !== fpRole && elType !== fpRole) {
|
|
166
|
+
return { status: 'STALE' };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
var rect = el.getBoundingClientRect();
|
|
171
|
+
if (!rect.width || !rect.height) return { status: 'INVISIBLE' };
|
|
172
|
+
|
|
173
|
+
// Scroll into view if out of viewport
|
|
174
|
+
if (rect.top < 0 || rect.bottom > window.innerHeight ||
|
|
175
|
+
rect.left < 0 || rect.right > window.innerWidth) {
|
|
176
|
+
el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
|
|
177
|
+
rect = el.getBoundingClientRect();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Re-hydrate data-wt-id attribute if stripped by SPA reconciliation
|
|
181
|
+
var idMatch = sel.match(/\\[data-wt-id="([^"]+)"\\]/);
|
|
182
|
+
if (idMatch && !el.hasAttribute('data-wt-id')) {
|
|
183
|
+
try { el.setAttribute('data-wt-id', idMatch[1]); } catch(e) {}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
status: 'OK',
|
|
188
|
+
x: Math.round(rect.left + rect.width / 2),
|
|
189
|
+
y: Math.round(rect.top + rect.height / 2)
|
|
190
|
+
};
|
|
191
|
+
} catch (e) {
|
|
192
|
+
return { status: 'NOT_FOUND' };
|
|
193
|
+
}
|
|
194
|
+
})(${JSON.stringify(selector)}, ${JSON.stringify(fingerprint ?? null)})`,
|
|
195
|
+
returnByValue: true,
|
|
196
|
+
awaitPromise: false,
|
|
197
|
+
});
|
|
198
|
+
const val = result?.value;
|
|
199
|
+
if (!val)
|
|
200
|
+
return { status: "NOT_FOUND" };
|
|
201
|
+
// Track stale misses for adaptive TTL follow-up
|
|
202
|
+
if (val.status === "STALE" && hostname) {
|
|
203
|
+
recordStaleMiss(hostname);
|
|
204
|
+
}
|
|
205
|
+
return val;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return { status: "NOT_FOUND" };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Attempt coordinate resolution via CDP DOM.getBoxModel using a backendNodeId.
|
|
213
|
+
* Used as Stage 4 in the click fallback chain when the selector path is unavailable.
|
|
214
|
+
*/
|
|
215
|
+
export async function resolveViaBackendNodeId(session, backendNodeId) {
|
|
216
|
+
try {
|
|
217
|
+
const { nodeIds } = await session.DOM.pushNodesByBackendIdsToFrontend({
|
|
218
|
+
backendNodeIds: [backendNodeId],
|
|
219
|
+
});
|
|
220
|
+
if (!nodeIds?.[0])
|
|
221
|
+
return null;
|
|
222
|
+
const { model } = await session.DOM.getBoxModel({ nodeId: nodeIds[0] });
|
|
223
|
+
if (!model?.content)
|
|
224
|
+
return null;
|
|
225
|
+
const c = model.content;
|
|
226
|
+
return {
|
|
227
|
+
x: Math.round((c[0] + c[4]) / 2),
|
|
228
|
+
y: Math.round((c[1] + c[5]) / 2),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { logDebug } from "../audit/logger.js";
|
|
2
|
+
/**
|
|
3
|
+
* Weavetab — cdp/retry.ts
|
|
4
|
+
* Generic CDP retry wrapper for transient connection failures.
|
|
5
|
+
* Retries on: ECONNRESET, WebSocket not open, Target closed, Session not found.
|
|
6
|
+
* Does NOT retry on: element not found, timeout, permission errors.
|
|
7
|
+
* Weavetab by fy2ne
|
|
8
|
+
*/
|
|
9
|
+
const RETRIABLE_PATTERNS = [
|
|
10
|
+
/ECONNRESET/i,
|
|
11
|
+
/WebSocket.*not.*open/i,
|
|
12
|
+
/Target closed/i,
|
|
13
|
+
/Session.*not found/i,
|
|
14
|
+
/No target with given id/i,
|
|
15
|
+
/protocol error/i,
|
|
16
|
+
/connection refused/i,
|
|
17
|
+
];
|
|
18
|
+
function isRetriable(err) {
|
|
19
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
20
|
+
return RETRIABLE_PATTERNS.some(p => p.test(msg));
|
|
21
|
+
}
|
|
22
|
+
export async function cdpRetry(fn, opts = {}) {
|
|
23
|
+
const { maxRetries = 2, baseDelay = 200 } = opts;
|
|
24
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
25
|
+
try {
|
|
26
|
+
return await fn();
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (attempt >= maxRetries || !isRetriable(err)) {
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
33
|
+
logDebug(`[Weavetab CDP] Retrying after transient error (attempt ${attempt + 1}/${maxRetries}): ${err instanceof Error ? err.message : err}`);
|
|
34
|
+
await new Promise(r => setTimeout(r, delay));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Unreachable, but TypeScript needs it
|
|
38
|
+
throw new Error("[Weavetab CDP] Exhausted retries");
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive retry framework for interaction tools
|
|
3
|
+
* Provides intelligent retry logic with multiple strategies and attempt tracking
|
|
4
|
+
*/
|
|
5
|
+
export interface RetryAttempt {
|
|
6
|
+
strategy: string;
|
|
7
|
+
success: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RetryResult<T> {
|
|
12
|
+
finalResult: T;
|
|
13
|
+
attempts: RetryAttempt[];
|
|
14
|
+
successfulStrategy: string;
|
|
15
|
+
totalDurationMs: number;
|
|
16
|
+
}
|
|
17
|
+
export interface RetryOptions {
|
|
18
|
+
maxAttempts?: number;
|
|
19
|
+
strategies?: string[];
|
|
20
|
+
onAttempt?: (attempt: number, strategy: string) => void;
|
|
21
|
+
strategyTimeoutMs?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Execute an action with adaptive retry using multiple strategies
|
|
25
|
+
* Tries each strategy in sequence until one succeeds or max attempts reached
|
|
26
|
+
*/
|
|
27
|
+
export declare function executeWithRetry<T>(strategies: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
fn: () => Promise<T>;
|
|
30
|
+
}>, options?: RetryOptions): Promise<RetryResult<T>>;
|
|
31
|
+
/**
|
|
32
|
+
* Create a timeout wrapper for any async function
|
|
33
|
+
*/
|
|
34
|
+
export declare function withTimeout<T>(fn: () => Promise<T>, timeoutMs: number, timeoutError?: string): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Execute with exponential backoff retry
|
|
37
|
+
* For operations that might succeed on retry due to transient failures
|
|
38
|
+
*/
|
|
39
|
+
export declare function executeWithBackoff<T>(fn: () => Promise<T>, options?: {
|
|
40
|
+
maxAttempts?: number;
|
|
41
|
+
initialDelayMs?: number;
|
|
42
|
+
maxDelayMs?: number;
|
|
43
|
+
backoffMultiplier?: number;
|
|
44
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive retry framework for interaction tools
|
|
3
|
+
* Provides intelligent retry logic with multiple strategies and attempt tracking
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Execute an action with adaptive retry using multiple strategies
|
|
7
|
+
* Tries each strategy in sequence until one succeeds or max attempts reached
|
|
8
|
+
*/
|
|
9
|
+
export async function executeWithRetry(strategies, options = {}) {
|
|
10
|
+
const maxAttempts = options.maxAttempts ?? strategies.length;
|
|
11
|
+
const strategyTimeoutMs = options.strategyTimeoutMs ?? 3000;
|
|
12
|
+
const attempts = [];
|
|
13
|
+
const startMs = Date.now();
|
|
14
|
+
for (let i = 0; i < Math.min(maxAttempts, strategies.length); i++) {
|
|
15
|
+
const strategy = strategies[i];
|
|
16
|
+
const attemptStartMs = Date.now();
|
|
17
|
+
if (options.onAttempt) {
|
|
18
|
+
options.onAttempt(i + 1, strategy.name);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
// Clear the timeout timer when strategy.fn() wins the race, otherwise it
|
|
22
|
+
// fires later and creates an unhandled rejected promise.
|
|
23
|
+
let timeoutId;
|
|
24
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
25
|
+
timeoutId = setTimeout(() => reject(new Error("Strategy timeout after " + strategyTimeoutMs + "ms")), strategyTimeoutMs);
|
|
26
|
+
});
|
|
27
|
+
const result = await Promise.race([
|
|
28
|
+
strategy.fn().finally(() => clearTimeout(timeoutId)),
|
|
29
|
+
timeoutPromise,
|
|
30
|
+
]);
|
|
31
|
+
const attemptDuration = Date.now() - attemptStartMs;
|
|
32
|
+
attempts.push({
|
|
33
|
+
strategy: strategy.name,
|
|
34
|
+
success: true,
|
|
35
|
+
durationMs: attemptDuration,
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
finalResult: result,
|
|
39
|
+
attempts,
|
|
40
|
+
successfulStrategy: strategy.name,
|
|
41
|
+
totalDurationMs: Date.now() - startMs,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const attemptDuration = Date.now() - attemptStartMs;
|
|
46
|
+
attempts.push({
|
|
47
|
+
strategy: strategy.name,
|
|
48
|
+
success: false,
|
|
49
|
+
error: error instanceof Error ? error.message : String(error),
|
|
50
|
+
durationMs: attemptDuration,
|
|
51
|
+
});
|
|
52
|
+
// If this is the last attempt, throw the error
|
|
53
|
+
if (i === Math.min(maxAttempts, strategies.length) - 1) {
|
|
54
|
+
throw new Error("All " + strategies.length + " strategies failed. Last error: " + error);
|
|
55
|
+
}
|
|
56
|
+
// Otherwise, continue to next strategy
|
|
57
|
+
process.stderr.write("[executeWithRetry] Strategy \"" + strategy.name + "\" failed, trying next strategy. Error: " + error + "\n");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// This should never be reached, but TypeScript needs it
|
|
61
|
+
throw new Error('No strategies were provided or all failed');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create a timeout wrapper for any async function
|
|
65
|
+
*/
|
|
66
|
+
// Same orphan-timer handling as the strategy timeout above.
|
|
67
|
+
export function withTimeout(fn, timeoutMs, timeoutError = "Operation timed out after " + timeoutMs + "ms") {
|
|
68
|
+
let timeoutId;
|
|
69
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
70
|
+
timeoutId = setTimeout(() => reject(new Error(timeoutError)), timeoutMs);
|
|
71
|
+
});
|
|
72
|
+
return Promise.race([
|
|
73
|
+
fn().finally(() => clearTimeout(timeoutId)),
|
|
74
|
+
timeoutPromise,
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Execute with exponential backoff retry
|
|
79
|
+
* For operations that might succeed on retry due to transient failures
|
|
80
|
+
*/
|
|
81
|
+
export async function executeWithBackoff(fn, options = {}) {
|
|
82
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
83
|
+
const initialDelayMs = options.initialDelayMs ?? 100;
|
|
84
|
+
const maxDelayMs = options.maxDelayMs ?? 2000;
|
|
85
|
+
const backoffMultiplier = options.backoffMultiplier ?? 2;
|
|
86
|
+
let lastError;
|
|
87
|
+
let delay = initialDelayMs;
|
|
88
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
89
|
+
try {
|
|
90
|
+
return await fn();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
94
|
+
if (attempt < maxAttempts - 1) {
|
|
95
|
+
process.stderr.write("[executeWithBackoff] Attempt " + (attempt + 1) + " failed, retrying in " + delay + "ms. Error: " + error + "\n");
|
|
96
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
97
|
+
delay = Math.min(delay * backoffMultiplier, maxDelayMs);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw lastError || new Error('Max retry attempts reached');
|
|
102
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weavetab — cdp/security.ts
|
|
3
|
+
* Security verification functions for browser isolation and process identity.
|
|
4
|
+
* - Profile verification: ensures the browser uses an isolated profile
|
|
5
|
+
* - Browser fingerprinting: verifies launched process identity
|
|
6
|
+
* - Process killing safety: whitelisting and verification
|
|
7
|
+
* Weavetab by fy2ne
|
|
8
|
+
*/
|
|
9
|
+
import CDP from "chrome-remote-interface";
|
|
10
|
+
import type { Config } from "../config/loader.js";
|
|
11
|
+
/**
|
|
12
|
+
* Verifies that the browser is actually using the isolated profile directory.
|
|
13
|
+
* Uses CDP to check the actual user data directory being used by the browser.
|
|
14
|
+
* Results are cached per session to avoid per-call overhead.
|
|
15
|
+
*/
|
|
16
|
+
export declare function verifyProfileIsolation(session: CDP.Client, config: Config, targetId: string): Promise<{
|
|
17
|
+
verified: boolean;
|
|
18
|
+
actualProfile?: string;
|
|
19
|
+
expectedProfile: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Clears the profile verification cache for a specific session.
|
|
23
|
+
* Called when a session is closed or reconnected.
|
|
24
|
+
*/
|
|
25
|
+
export declare function clearProfileVerificationCache(targetId: string): void;
|
|
26
|
+
interface BrowserFingerprint {
|
|
27
|
+
pid: number;
|
|
28
|
+
port: number;
|
|
29
|
+
profileDir: string;
|
|
30
|
+
browserPath: string;
|
|
31
|
+
launchTimestamp: number;
|
|
32
|
+
userAgent: string;
|
|
33
|
+
cdpVersion: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates a unique fingerprint for the launched browser process.
|
|
37
|
+
* This is stored in the lock file and verified on reconnection.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createBrowserFingerprint(pid: number, port: number, profileDir: string, browserPath: string): BrowserFingerprint;
|
|
40
|
+
/**
|
|
41
|
+
* Verifies that the connected browser matches the expected fingerprint.
|
|
42
|
+
* Runs at CDP connect time only.
|
|
43
|
+
*/
|
|
44
|
+
export declare function verifyBrowserFingerprint(session: CDP.Client, expectedFingerprint: BrowserFingerprint): Promise<{
|
|
45
|
+
valid: boolean;
|
|
46
|
+
reason?: string;
|
|
47
|
+
}>;
|
|
48
|
+
export declare function getCurrentFingerprint(): BrowserFingerprint | null;
|
|
49
|
+
export declare function safelyKillWeavetabProcesses(profileDir: string, expectedPid?: number): void;
|
|
50
|
+
export {};
|