@weavetab/mcp 2.5.0 → 2.5.1
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 +49 -49
- package/ARCHITECTURE.md +590 -590
- package/LICENSE +216 -216
- package/README.md +269 -269
- package/SECURITY.md +207 -207
- package/TROUBLESHOOTING.md +373 -365
- package/dist/anticaptcha/detector.js +395 -395
- package/dist/anticaptcha/solver.js +130 -130
- package/dist/cdp/bridge.js +73 -73
- package/dist/cdp/connector.js +46 -22
- package/dist/cdp/extension.js +34 -34
- package/dist/cdp/helpers.js +174 -174
- package/dist/cdp/pruner.js +195 -195
- package/dist/cdp/ref_cache.js +66 -66
- package/dist/cdp/security.js +10 -10
- package/dist/cdp/verify.js +57 -57
- package/dist/cdp/walker.js +563 -525
- package/dist/cli/plugin.js +26 -3
- package/dist/config/loader.d.ts +0 -3
- package/dist/config/loader.js +0 -9
- package/dist/index.js +0 -4
- package/dist/intelligence/intent.js +81 -81
- package/dist/intelligence/sessionMemory.js +2 -2
- package/dist/overlay/ghost.js +22 -22
- package/dist/overlay/injector.js +89 -89
- package/dist/plugin/extension_bridge.js +54 -54
- package/dist/plugin/interface.d.ts +7 -0
- package/dist/plugin/loader.js +3 -4
- package/dist/plugin/schema.d.ts +11 -0
- package/dist/plugin/schema.js +59 -0
- package/dist/security/guard.js +18 -18
- package/dist/sensors/actionability.js +167 -167
- package/dist/sensors/guard.js +29 -29
- package/dist/sensors/network.js +15 -17
- package/dist/server.d.ts +1 -1
- package/dist/server.js +173 -144
- package/dist/state/delta.js +6 -7
- package/dist/tools/automation.js +85 -85
- package/dist/tools/canvas.js +48 -25
- package/dist/tools/checkpoint.js +94 -35
- package/dist/tools/click.js +52 -41
- package/dist/tools/clipboard.js +80 -74
- package/dist/tools/design_extract.js +441 -441
- package/dist/tools/dialog.js +13 -13
- package/dist/tools/emulation.d.ts +2 -0
- package/dist/tools/emulation.js +7 -0
- package/dist/tools/fill.js +344 -347
- package/dist/tools/find.js +78 -52
- package/dist/tools/highlight.js +67 -67
- package/dist/tools/inspect.js +54 -25
- package/dist/tools/key.js +42 -35
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +35 -35
- package/dist/tools/office/parse.js +62 -18
- package/dist/tools/pdf.d.ts +9 -0
- package/dist/tools/pdf.js +7 -4
- package/dist/tools/performance.d.ts +11 -4
- package/dist/tools/performance.js +94 -42
- package/dist/tools/performance_test.d.ts +2 -2
- package/dist/tools/performance_test.js +69 -69
- package/dist/tools/pointer.js +116 -116
- package/dist/tools/read.js +99 -67
- package/dist/tools/recording.js +9 -0
- package/dist/tools/scrape.js +146 -146
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +46 -4
- package/dist/tools/scroll.d.ts +3 -1
- package/dist/tools/scroll.js +13 -15
- package/dist/tools/select.d.ts +3 -0
- package/dist/tools/select.js +131 -124
- package/dist/tools/snapshot.js +8 -6
- package/dist/tools/storage.d.ts +5 -1
- package/dist/tools/storage.js +16 -0
- package/dist/tools/tabs.d.ts +7 -5
- package/dist/tools/tabs.js +79 -26
- package/dist/tools/thought.js +8 -8
- package/dist/tools/type.js +274 -274
- package/dist/tools/type_secret.js +23 -23
- package/dist/tools/upload.js +3 -3
- package/dist/tools/video_compiler.js +9 -0
- package/dist/tools/wait.js +35 -35
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/DOCUMENTATION.md +583 -585
- package/docs/PLUGINS.md +139 -121
- package/docs/TOOLS.md +3 -4
- package/docs/architecture/blind-injection.md +83 -83
- package/docs/architecture/overview.md +49 -49
- package/docs/architecture/security.md +36 -36
- package/docs/architecture/transport.md +37 -37
- package/docs/cli/cli-commands.md +50 -50
- package/docs/cli/cli-flags.md +28 -28
- package/docs/cli/cli-scripting.md +35 -35
- package/docs/configuration/config-locations.md +23 -23
- package/docs/configuration/configuration.md +39 -27
- package/docs/configuration/env-variables.md +19 -19
- package/docs/configuration/folders.md +62 -55
- package/docs/desktop-automation/electron-apps.md +225 -214
- package/docs/get-started/installation.md +1 -1
- package/docs/get-started/introduction.md +60 -60
- package/docs/get-started/quickstart.md +2 -2
- package/docs/get-started/setup-wizard.md +27 -27
- package/docs/intelligence/site-profiles.md +151 -151
- package/docs/tools/tools.md +582 -631
- package/docs/troubleshooting/trouble-browser.md +27 -27
- package/docs/troubleshooting/trouble-loop.md +31 -31
- package/ext-dist/background.js +415 -415
- package/ext-dist/content.js +2527 -2527
- package/ext-dist/dashboard.html +583 -583
- package/ext-dist/hud/hud.js +3 -3
- package/ext-dist/hud/index.html +20 -20
- package/ext-dist/manifest.json +73 -73
- package/ext-dist/styles.css +223 -223
- package/mcp.json +796 -805
- package/package.json +118 -118
- package/server.json +23 -23
- package/dist/cdp/walker.test.d.ts +0 -2
- package/dist/cdp/walker.test.js +0 -172
- package/dist/intelligence/sessionMemory.test.d.ts +0 -2
- package/dist/intelligence/sessionMemory.test.js +0 -62
- package/dist/intelligence/trail.test.d.ts +0 -2
- package/dist/intelligence/trail.test.js +0 -110
- package/dist/state/cache.test.d.ts +0 -2
- package/dist/state/cache.test.js +0 -76
- package/dist/state/delta.test.d.ts +0 -2
- package/dist/state/delta.test.js +0 -279
- package/dist/state/groups.d.ts +0 -15
- package/dist/state/groups.js +0 -80
- package/dist/tools/accessibility.d.ts +0 -14
- package/dist/tools/accessibility.js +0 -32
- package/dist/tools/ask.d.ts +0 -14
- package/dist/tools/ask.js +0 -49
- package/dist/tools/click_and_wait.d.ts +0 -33
- package/dist/tools/click_and_wait.js +0 -149
- package/dist/tools/drag.d.ts +0 -15
- package/dist/tools/drag.js +0 -29
- package/dist/tools/geo.d.ts +0 -19
- package/dist/tools/geo.js +0 -29
- package/dist/tools/github/analyze.d.ts +0 -4
- package/dist/tools/github/analyze.js +0 -113
- package/dist/tools/github/cache.d.ts +0 -21
- package/dist/tools/github/cache.js +0 -28
- package/dist/tools/github/index.d.ts +0 -19
- package/dist/tools/github/index.js +0 -40
- package/dist/tools/github/issues.d.ts +0 -9
- package/dist/tools/github/issues.js +0 -88
- package/dist/tools/github/pr.d.ts +0 -5
- package/dist/tools/github/pr.js +0 -88
- package/dist/tools/github/rateLimiter.d.ts +0 -4
- package/dist/tools/github/rateLimiter.js +0 -46
- package/dist/tools/github/read.d.ts +0 -34
- package/dist/tools/github/read.js +0 -74
- package/dist/tools/github/repoContext.d.ts +0 -20
- package/dist/tools/github/repoContext.js +0 -129
- package/dist/tools/hover.d.ts +0 -19
- package/dist/tools/hover.js +0 -57
- package/dist/tools/integration.test.d.ts +0 -2
- package/dist/tools/integration.test.js +0 -96
- package/dist/tools/macro_compiler.d.ts +0 -7
- package/dist/tools/macro_compiler.js +0 -82
- package/dist/tools/mouse.d.ts +0 -21
- package/dist/tools/mouse.js +0 -62
- package/dist/tools/response.d.ts +0 -129
- package/dist/tools/response.js +0 -5
- package/dist/tools/wait_for.d.ts +0 -23
- package/dist/tools/wait_for.js +0 -30
- package/dist/tools/windows.d.ts +0 -22
- package/dist/tools/windows.js +0 -57
package/dist/state/delta.js
CHANGED
|
@@ -12,20 +12,19 @@ const INTERACTIVE_ROLES = new Set([
|
|
|
12
12
|
'checkbox', 'radio', 'menuitem', 'tab', 'select',
|
|
13
13
|
]);
|
|
14
14
|
function serializeProps(el, targetId) {
|
|
15
|
-
const
|
|
15
|
+
const isSecretField = (el.backendNodeId !== undefined && targetId &&
|
|
16
16
|
SessionSecretTracker.isNodeSecret(targetId, el.backendNodeId)) ||
|
|
17
|
-
el.label === '[REDACTED]' ||
|
|
18
|
-
el.value === '[REDACTED]' ||
|
|
19
17
|
el.role === 'password' ||
|
|
20
18
|
el.type === 'password';
|
|
19
|
+
const isSecretValue = isSecretField || el.value === '[REDACTED]';
|
|
21
20
|
return {
|
|
22
|
-
label:
|
|
23
|
-
value:
|
|
21
|
+
label: targetId ? SessionSecretTracker.redact(targetId, el.label ?? '') : (el.label ?? ''),
|
|
22
|
+
value: isSecretValue ? '[REDACTED]' : (targetId ? SessionSecretTracker.redact(targetId, el.value ?? '') : (el.value ?? '')),
|
|
24
23
|
disabled: String(!!el.disabled),
|
|
25
24
|
role: el.role,
|
|
26
25
|
href: el.href ?? '',
|
|
27
|
-
placeholder:
|
|
28
|
-
textContent:
|
|
26
|
+
placeholder: targetId ? SessionSecretTracker.redact(targetId, el.placeholder ?? '') : (el.placeholder ?? ''),
|
|
27
|
+
textContent: (isSecretField && el.textContent && !el.value) ? '[REDACTED]' : (targetId ? SessionSecretTracker.redact(targetId, el.textContent ?? '') : (el.textContent ?? '')),
|
|
29
28
|
className: (el.className ?? '').split(' ').filter(Boolean).sort().join(' '),
|
|
30
29
|
};
|
|
31
30
|
}
|
package/dist/tools/automation.js
CHANGED
|
@@ -26,77 +26,77 @@ export async function browserAutomation(session, options, _config) {
|
|
|
26
26
|
// Wrap the script in an IIFE using ONLY closure state for lifecycle.
|
|
27
27
|
// No window.__wt_automations map. Stop signal read from a window boolean.
|
|
28
28
|
// Results written to a window array per automation (mailbox, not registry).
|
|
29
|
-
const wrappedScript = `
|
|
30
|
-
(function() {
|
|
31
|
-
var _id = ${JSON.stringify(id)};
|
|
32
|
-
var _persistent = ${JSON.stringify(persistent)};
|
|
33
|
-
var _onResult = ${JSON.stringify(onResult)};
|
|
34
|
-
var _active = true;
|
|
35
|
-
var _resultMailbox = window[${JSON.stringify(resultMailboxKey(id))}];
|
|
36
|
-
var wt = {
|
|
37
|
-
query: function(sel) {
|
|
38
|
-
return document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
39
|
-
},
|
|
40
|
-
click: function(sel) {
|
|
41
|
-
var el = this.query(sel);
|
|
42
|
-
if (el) el.click();
|
|
43
|
-
},
|
|
44
|
-
fill: function(sel, val) {
|
|
45
|
-
var el = this.query(sel);
|
|
46
|
-
if (el) {
|
|
47
|
-
el.value = val;
|
|
48
|
-
el.dispatchEvent(new Event('input', {bubbles:true}));
|
|
49
|
-
el.dispatchEvent(new Event('change', {bubbles:true}));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Create result mailbox as a plain array on window — single-purpose, not a registry
|
|
55
|
-
if (!_resultMailbox) {
|
|
56
|
-
_resultMailbox = [];
|
|
57
|
-
Object.defineProperty(window, ${JSON.stringify(resultMailboxKey(id))}, {
|
|
58
|
-
value: _resultMailbox,
|
|
59
|
-
writable: true,
|
|
60
|
-
configurable: true,
|
|
61
|
-
enumerable: false
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function _report(data) {
|
|
66
|
-
var safeData;
|
|
67
|
-
try { JSON.stringify(data); safeData = data; } catch(e) { safeData = String(data); }
|
|
68
|
-
_resultMailbox.push({ data: safeData, timestamp: new Date().toISOString() });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function _checkStop() {
|
|
72
|
-
return window[${JSON.stringify(stopSignalKey(id))}] === true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function _run() {
|
|
76
|
-
if (!_active) return;
|
|
77
|
-
if (_checkStop()) { _active = false; return; }
|
|
78
|
-
(async function() { return eval(${JSON.stringify(script)}); })().then(function(r) {
|
|
79
|
-
if (r !== undefined && _onResult !== 'ignore') _report(r);
|
|
80
|
-
}).catch(function(e) {
|
|
81
|
-
if (_onResult !== 'ignore') _report({ error: e.message || String(e) });
|
|
82
|
-
}).finally(function() {
|
|
83
|
-
if (_persistent && _active) {
|
|
84
|
-
setTimeout(_run, 100);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (_persistent) {
|
|
90
|
-
_run();
|
|
91
|
-
} else {
|
|
92
|
-
(async function() { return eval(${JSON.stringify(script)}); })().then(function(r) {
|
|
93
|
-
if (r !== undefined && _onResult !== 'ignore') _report(r);
|
|
94
|
-
window[${JSON.stringify(stopSignalKey(id))}] = true;
|
|
95
|
-
}).catch(function(e) {
|
|
96
|
-
if (_onResult !== 'ignore') _report({ error: e.message || String(e) });
|
|
97
|
-
window[${JSON.stringify(stopSignalKey(id))}] = true;
|
|
98
|
-
});
|
|
99
|
-
}
|
|
29
|
+
const wrappedScript = `
|
|
30
|
+
(function() {
|
|
31
|
+
var _id = ${JSON.stringify(id)};
|
|
32
|
+
var _persistent = ${JSON.stringify(persistent)};
|
|
33
|
+
var _onResult = ${JSON.stringify(onResult)};
|
|
34
|
+
var _active = true;
|
|
35
|
+
var _resultMailbox = window[${JSON.stringify(resultMailboxKey(id))}];
|
|
36
|
+
var wt = {
|
|
37
|
+
query: function(sel) {
|
|
38
|
+
return document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
39
|
+
},
|
|
40
|
+
click: function(sel) {
|
|
41
|
+
var el = this.query(sel);
|
|
42
|
+
if (el) el.click();
|
|
43
|
+
},
|
|
44
|
+
fill: function(sel, val) {
|
|
45
|
+
var el = this.query(sel);
|
|
46
|
+
if (el) {
|
|
47
|
+
el.value = val;
|
|
48
|
+
el.dispatchEvent(new Event('input', {bubbles:true}));
|
|
49
|
+
el.dispatchEvent(new Event('change', {bubbles:true}));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Create result mailbox as a plain array on window — single-purpose, not a registry
|
|
55
|
+
if (!_resultMailbox) {
|
|
56
|
+
_resultMailbox = [];
|
|
57
|
+
Object.defineProperty(window, ${JSON.stringify(resultMailboxKey(id))}, {
|
|
58
|
+
value: _resultMailbox,
|
|
59
|
+
writable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
enumerable: false
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function _report(data) {
|
|
66
|
+
var safeData;
|
|
67
|
+
try { JSON.stringify(data); safeData = data; } catch(e) { safeData = String(data); }
|
|
68
|
+
_resultMailbox.push({ data: safeData, timestamp: new Date().toISOString() });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function _checkStop() {
|
|
72
|
+
return window[${JSON.stringify(stopSignalKey(id))}] === true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function _run() {
|
|
76
|
+
if (!_active) return;
|
|
77
|
+
if (_checkStop()) { _active = false; return; }
|
|
78
|
+
(async function() { return eval(${JSON.stringify(script)}); })().then(function(r) {
|
|
79
|
+
if (r !== undefined && _onResult !== 'ignore') _report(r);
|
|
80
|
+
}).catch(function(e) {
|
|
81
|
+
if (_onResult !== 'ignore') _report({ error: e.message || String(e) });
|
|
82
|
+
}).finally(function() {
|
|
83
|
+
if (_persistent && _active) {
|
|
84
|
+
setTimeout(_run, 100);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (_persistent) {
|
|
90
|
+
_run();
|
|
91
|
+
} else {
|
|
92
|
+
(async function() { return eval(${JSON.stringify(script)}); })().then(function(r) {
|
|
93
|
+
if (r !== undefined && _onResult !== 'ignore') _report(r);
|
|
94
|
+
window[${JSON.stringify(stopSignalKey(id))}] = true;
|
|
95
|
+
}).catch(function(e) {
|
|
96
|
+
if (_onResult !== 'ignore') _report({ error: e.message || String(e) });
|
|
97
|
+
window[${JSON.stringify(stopSignalKey(id))}] = true;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
100
|
})();`;
|
|
101
101
|
const resp = await session.Runtime.evaluate({
|
|
102
102
|
expression: wrappedScript,
|
|
@@ -134,10 +134,10 @@ export async function browserAutomation(session, options, _config) {
|
|
|
134
134
|
// Set stop signal boolean on window (single-purpose, same pattern as __wt_stop_requested)
|
|
135
135
|
// The running IIFE checks this flag on each iteration and exits when set.
|
|
136
136
|
await session.Runtime.evaluate({
|
|
137
|
-
expression: `(function() {
|
|
138
|
-
window[${JSON.stringify(stopSignalKey(id))}] = true;
|
|
139
|
-
// Also clear the result mailbox
|
|
140
|
-
delete window[${JSON.stringify(resultMailboxKey(id))}];
|
|
137
|
+
expression: `(function() {
|
|
138
|
+
window[${JSON.stringify(stopSignalKey(id))}] = true;
|
|
139
|
+
// Also clear the result mailbox
|
|
140
|
+
delete window[${JSON.stringify(resultMailboxKey(id))}];
|
|
141
141
|
})()`,
|
|
142
142
|
returnByValue: true,
|
|
143
143
|
}).catch(() => { });
|
|
@@ -167,11 +167,11 @@ export async function browserAutomation(session, options, _config) {
|
|
|
167
167
|
for (const [autoId] of automationRegistry) {
|
|
168
168
|
try {
|
|
169
169
|
const resp = await session.Runtime.evaluate({
|
|
170
|
-
expression: `(function() {
|
|
171
|
-
var mb = window[${JSON.stringify(resultMailboxKey(autoId))}];
|
|
172
|
-
if (!mb || mb.length === 0) return '[]';
|
|
173
|
-
var items = mb.splice(0, mb.length);
|
|
174
|
-
return JSON.stringify(items);
|
|
170
|
+
expression: `(function() {
|
|
171
|
+
var mb = window[${JSON.stringify(resultMailboxKey(autoId))}];
|
|
172
|
+
if (!mb || mb.length === 0) return '[]';
|
|
173
|
+
var items = mb.splice(0, mb.length);
|
|
174
|
+
return JSON.stringify(items);
|
|
175
175
|
})()`,
|
|
176
176
|
returnByValue: true,
|
|
177
177
|
});
|
|
@@ -194,11 +194,11 @@ export async function browserAutomation(session, options, _config) {
|
|
|
194
194
|
for (const [autoId] of automationRegistry) {
|
|
195
195
|
try {
|
|
196
196
|
const resp = await session.Runtime.evaluate({
|
|
197
|
-
expression: `(function() {
|
|
198
|
-
var mb = window[${JSON.stringify(resultMailboxKey(autoId))}];
|
|
199
|
-
if (!mb || mb.length === 0) return '[]';
|
|
200
|
-
var items = mb.splice(0, mb.length);
|
|
201
|
-
return JSON.stringify(items);
|
|
197
|
+
expression: `(function() {
|
|
198
|
+
var mb = window[${JSON.stringify(resultMailboxKey(autoId))}];
|
|
199
|
+
if (!mb || mb.length === 0) return '[]';
|
|
200
|
+
var items = mb.splice(0, mb.length);
|
|
201
|
+
return JSON.stringify(items);
|
|
202
202
|
})()`,
|
|
203
203
|
returnByValue: true,
|
|
204
204
|
});
|
package/dist/tools/canvas.js
CHANGED
|
@@ -34,16 +34,19 @@ async function resolveCanvasViaCDP(session, backendNodeId) {
|
|
|
34
34
|
throw new Error("Could not resolve canvas node to object");
|
|
35
35
|
const { result } = await session.Runtime.callFunctionOn({
|
|
36
36
|
objectId: object.objectId,
|
|
37
|
-
functionDeclaration: `function() {
|
|
38
|
-
var rect = this.getBoundingClientRect();
|
|
39
|
-
return JSON.stringify({
|
|
40
|
-
x: rect.left,
|
|
41
|
-
y: rect.top,
|
|
42
|
-
dataUrl: this.toDataURL('image/png')
|
|
43
|
-
});
|
|
37
|
+
functionDeclaration: `function() {
|
|
38
|
+
var rect = this.getBoundingClientRect();
|
|
39
|
+
return JSON.stringify({
|
|
40
|
+
x: rect.left,
|
|
41
|
+
y: rect.top,
|
|
42
|
+
dataUrl: this.toDataURL('image/png')
|
|
43
|
+
});
|
|
44
44
|
}()`,
|
|
45
45
|
returnByValue: true,
|
|
46
46
|
});
|
|
47
|
+
if (typeof result.value !== 'string') {
|
|
48
|
+
throw new Error("Canvas data extraction failed (cross-origin/tainted canvas or detached node).");
|
|
49
|
+
}
|
|
47
50
|
return JSON.parse(result.value);
|
|
48
51
|
}
|
|
49
52
|
async function resolveCanvasViaSelector(session, options, targetId) {
|
|
@@ -63,27 +66,47 @@ async function resolveCanvasViaSelector(session, options, targetId) {
|
|
|
63
66
|
else {
|
|
64
67
|
selector = "canvas";
|
|
65
68
|
}
|
|
69
|
+
const probe = `(function() {
|
|
70
|
+
var selector = ${JSON.stringify(selector)};
|
|
71
|
+
var isDefault = selector === 'canvas';
|
|
72
|
+
var candidates = [];
|
|
73
|
+
if (isDefault) {
|
|
74
|
+
candidates = Array.prototype.slice.call(document.querySelectorAll('canvas')).filter(function(c) {
|
|
75
|
+
return !(c.closest('[data-wt-overlay]') || c.closest('.weavetab-overlay') || c.id === '__wt_ghost_canvas') && c.width > 0 && c.height > 0;
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
var el;
|
|
79
|
+
try { el = document.querySelector(selector); } catch(e) { el = null; }
|
|
80
|
+
if (el) candidates = [el];
|
|
81
|
+
if (candidates.length === 1 && (candidates[0].closest('[data-wt-overlay]') || candidates[0].closest('.weavetab-overlay') || candidates[0].id === '__wt_ghost_canvas')) {
|
|
82
|
+
return JSON.stringify({ error: 'Selector matched a WeaveTab overlay canvas (hidden). Try a more specific selector or use nodeId.', exists: false });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (candidates.length === 0) {
|
|
86
|
+
var total = document.querySelectorAll('canvas').length;
|
|
87
|
+
if (total === 0) return JSON.stringify({ error: 'No <canvas> elements found on this page.', exists: false });
|
|
88
|
+
return JSON.stringify({ error: 'Selector did not match a usable <canvas> (' + total + ' on page — overlay or zero-size canvases are skipped). Try a more specific selector or use nodeId.', exists: false });
|
|
89
|
+
}
|
|
90
|
+
var target = candidates[0];
|
|
91
|
+
if (target.tagName !== 'CANVAS') return JSON.stringify({ error: 'Element is not a <canvas> — found <' + target.tagName.toLowerCase() + '> instead.', exists: false });
|
|
92
|
+
var r = target.getBoundingClientRect();
|
|
93
|
+
return JSON.stringify({ x: r.left, y: r.top, dataUrl: target.toDataURL('image/png'), exists: true });
|
|
94
|
+
})()`;
|
|
66
95
|
const { omniEvaluate } = await import("../cdp/omni_broker.js");
|
|
67
|
-
const raw = await omniEvaluate(session, targetId,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!el) {
|
|
76
|
-
var total = document.querySelectorAll('canvas').length;
|
|
77
|
-
if (total === 0) return JSON.stringify({ error: 'No <canvas> elements found on this page.', exists: false });
|
|
78
|
-
return JSON.stringify({ error: 'Selector did not match any element. Found ' + total + ' <canvas> element(s) on page — try a more specific selector or use nodeId.', exists: false });
|
|
79
|
-
}
|
|
80
|
-
if (el.tagName !== 'CANVAS') return JSON.stringify({ error: 'Element is not a <canvas> — found <' + el.tagName.toLowerCase() + '> instead.', exists: false });
|
|
81
|
-
var r = el.getBoundingClientRect();
|
|
82
|
-
return JSON.stringify({ x: r.left, y: r.top, dataUrl: el.toDataURL('image/png'), exists: true });
|
|
83
|
-
})()`);
|
|
84
|
-
const parsed = JSON.parse(raw || "{}");
|
|
96
|
+
const raw = await omniEvaluate(session, targetId, probe);
|
|
97
|
+
let parsed;
|
|
98
|
+
try {
|
|
99
|
+
parsed = JSON.parse(raw || "{}");
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
throw new Error("Canvas query returned a malformed response — retry with a specific selector.");
|
|
103
|
+
}
|
|
85
104
|
if (parsed.error)
|
|
86
105
|
throw new Error(parsed.error);
|
|
106
|
+
if (typeof parsed.dataUrl !== 'string' ||
|
|
107
|
+
!/^data:[^;]+;base64,[A-Za-z0-9+/]+={0,2}$/.test(parsed.dataUrl)) {
|
|
108
|
+
throw new Error("Canvas returned no usable image data (empty, hidden, detached, or tainted canvas).");
|
|
109
|
+
}
|
|
87
110
|
return parsed;
|
|
88
111
|
}
|
|
89
112
|
export async function browserCanvas(session, options, _config) {
|
package/dist/tools/checkpoint.js
CHANGED
|
@@ -12,14 +12,56 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Weavetab by fy2ne
|
|
14
14
|
*/
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import path from "path";
|
|
15
17
|
import { logAction } from "../audit/logger.js";
|
|
16
18
|
import { recordAction } from "../intelligence/trail.js";
|
|
17
19
|
import { clearTabCache } from "../state/cache.js";
|
|
18
20
|
import { sessionRegistry } from "../state/session.js";
|
|
19
|
-
import { getConfigAgentId } from "../config/loader.js";
|
|
20
|
-
// In-memory checkpoint registry keyed by targetId -> name -> CheckpointData
|
|
21
|
+
import { getConfigAgentId, getSystemDir } from "../config/loader.js";
|
|
22
|
+
// In-memory checkpoint registry keyed by targetId -> name -> CheckpointData.
|
|
23
|
+
// Backed by ~/.weavetab-system/checkpoints/<targetId>.json so checkpoints
|
|
24
|
+
// survive server restarts.
|
|
21
25
|
const checkpointRegistry = new Map();
|
|
26
|
+
const loadedTargets = new Set();
|
|
27
|
+
function checkpointFilePath(targetId) {
|
|
28
|
+
const dir = getSystemDir("checkpoints");
|
|
29
|
+
const safe = targetId.replace(/[^a-zA-Z0-9_-]/g, "_") || "default";
|
|
30
|
+
return path.join(dir, `${safe}.json`);
|
|
31
|
+
}
|
|
32
|
+
function ensureTargetLoaded(targetId) {
|
|
33
|
+
if (loadedTargets.has(targetId))
|
|
34
|
+
return;
|
|
35
|
+
loadedTargets.add(targetId);
|
|
36
|
+
try {
|
|
37
|
+
const file = checkpointFilePath(targetId);
|
|
38
|
+
if (!fs.existsSync(file))
|
|
39
|
+
return;
|
|
40
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
41
|
+
if (parsed?.checkpoints) {
|
|
42
|
+
checkpointRegistry.set(targetId, new Map(Object.entries(parsed.checkpoints)));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Corrupt or unreadable file — start empty rather than crash.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function persistTarget(targetId) {
|
|
50
|
+
try {
|
|
51
|
+
const map = checkpointRegistry.get(targetId) ?? new Map();
|
|
52
|
+
const payload = {
|
|
53
|
+
targetId,
|
|
54
|
+
updatedAt: new Date().toISOString(),
|
|
55
|
+
checkpoints: Object.fromEntries(map),
|
|
56
|
+
};
|
|
57
|
+
fs.writeFileSync(checkpointFilePath(targetId), JSON.stringify(payload, null, 2), "utf-8");
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Persistence is best-effort; the in-memory registry remains authoritative.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
22
63
|
export function getCheckpointsForTarget(targetId) {
|
|
64
|
+
ensureTargetLoaded(targetId);
|
|
23
65
|
let map = checkpointRegistry.get(targetId);
|
|
24
66
|
if (!map) {
|
|
25
67
|
map = new Map();
|
|
@@ -28,7 +70,21 @@ export function getCheckpointsForTarget(targetId) {
|
|
|
28
70
|
return map;
|
|
29
71
|
}
|
|
30
72
|
export function clearAllCheckpoints() {
|
|
73
|
+
const targets = new Set([...loadedTargets, ...checkpointRegistry.keys()]);
|
|
31
74
|
checkpointRegistry.clear();
|
|
75
|
+
loadedTargets.clear();
|
|
76
|
+
// Also purge persisted checkpoints; otherwise ensureTargetLoaded() would
|
|
77
|
+
// lazily reload them from disk on the next access.
|
|
78
|
+
for (const targetId of targets) {
|
|
79
|
+
try {
|
|
80
|
+
const file = checkpointFilePath(targetId);
|
|
81
|
+
if (fs.existsSync(file))
|
|
82
|
+
fs.unlinkSync(file);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Best-effort cleanup.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
32
88
|
}
|
|
33
89
|
export async function browserCheckpoint(session, args, config) {
|
|
34
90
|
let targetId = "default";
|
|
@@ -65,6 +121,8 @@ export async function browserCheckpoint(session, args, config) {
|
|
|
65
121
|
};
|
|
66
122
|
}
|
|
67
123
|
const existed = targetCheckpoints.delete(args.name);
|
|
124
|
+
if (existed)
|
|
125
|
+
persistTarget(targetId);
|
|
68
126
|
return {
|
|
69
127
|
success: existed,
|
|
70
128
|
action: "delete",
|
|
@@ -110,27 +168,27 @@ export async function browserCheckpoint(session, args, config) {
|
|
|
110
168
|
};
|
|
111
169
|
try {
|
|
112
170
|
const evalRes = await Runtime.evaluate({
|
|
113
|
-
expression: `(function() {
|
|
114
|
-
var ls = {};
|
|
115
|
-
try {
|
|
116
|
-
for (var i = 0; i < localStorage.length; i++) {
|
|
117
|
-
var k = localStorage.key(i);
|
|
118
|
-
if (k) ls[k] = localStorage.getItem(k);
|
|
119
|
-
}
|
|
120
|
-
} catch(e) {}
|
|
121
|
-
var ss = {};
|
|
122
|
-
try {
|
|
123
|
-
for (var j = 0; j < sessionStorage.length; j++) {
|
|
124
|
-
var sk = sessionStorage.key(j);
|
|
125
|
-
if (sk) ss[sk] = sessionStorage.getItem(sk);
|
|
126
|
-
}
|
|
127
|
-
} catch(e) {}
|
|
128
|
-
return {
|
|
129
|
-
localStorage: ls,
|
|
130
|
-
sessionStorage: ss,
|
|
131
|
-
scroll: { x: window.scrollX || 0, y: window.scrollY || 0 },
|
|
132
|
-
title: document.title || ""
|
|
133
|
-
};
|
|
171
|
+
expression: `(function() {
|
|
172
|
+
var ls = {};
|
|
173
|
+
try {
|
|
174
|
+
for (var i = 0; i < localStorage.length; i++) {
|
|
175
|
+
var k = localStorage.key(i);
|
|
176
|
+
if (k) ls[k] = localStorage.getItem(k);
|
|
177
|
+
}
|
|
178
|
+
} catch(e) {}
|
|
179
|
+
var ss = {};
|
|
180
|
+
try {
|
|
181
|
+
for (var j = 0; j < sessionStorage.length; j++) {
|
|
182
|
+
var sk = sessionStorage.key(j);
|
|
183
|
+
if (sk) ss[sk] = sessionStorage.getItem(sk);
|
|
184
|
+
}
|
|
185
|
+
} catch(e) {}
|
|
186
|
+
return {
|
|
187
|
+
localStorage: ls,
|
|
188
|
+
sessionStorage: ss,
|
|
189
|
+
scroll: { x: window.scrollX || 0, y: window.scrollY || 0 },
|
|
190
|
+
title: document.title || ""
|
|
191
|
+
};
|
|
134
192
|
})()`,
|
|
135
193
|
returnByValue: true,
|
|
136
194
|
});
|
|
@@ -167,6 +225,7 @@ export async function browserCheckpoint(session, args, config) {
|
|
|
167
225
|
domSnapshot,
|
|
168
226
|
};
|
|
169
227
|
targetCheckpoints.set(args.name, checkpoint);
|
|
228
|
+
persistTarget(targetId);
|
|
170
229
|
logAction("browser_checkpoint", args.name, `Saved checkpoint at ${currentUrl}`);
|
|
171
230
|
recordAction({
|
|
172
231
|
timestamp: checkpoint.timestamp,
|
|
@@ -226,18 +285,18 @@ export async function browserCheckpoint(session, args, config) {
|
|
|
226
285
|
// 3. Restore LocalStorage, SessionStorage, and Scroll position
|
|
227
286
|
try {
|
|
228
287
|
await Runtime.evaluate({
|
|
229
|
-
expression: `(function(ls, ss, scroll) {
|
|
230
|
-
try {
|
|
231
|
-
localStorage.clear();
|
|
232
|
-
for (var k in ls) { localStorage.setItem(k, ls[k]); }
|
|
233
|
-
} catch(e) {}
|
|
234
|
-
try {
|
|
235
|
-
sessionStorage.clear();
|
|
236
|
-
for (var sk in ss) { sessionStorage.setItem(sk, ss[sk]); }
|
|
237
|
-
} catch(e) {}
|
|
238
|
-
if (scroll) {
|
|
239
|
-
window.scrollTo(scroll.x, scroll.y);
|
|
240
|
-
}
|
|
288
|
+
expression: `(function(ls, ss, scroll) {
|
|
289
|
+
try {
|
|
290
|
+
localStorage.clear();
|
|
291
|
+
for (var k in ls) { localStorage.setItem(k, ls[k]); }
|
|
292
|
+
} catch(e) {}
|
|
293
|
+
try {
|
|
294
|
+
sessionStorage.clear();
|
|
295
|
+
for (var sk in ss) { sessionStorage.setItem(sk, ss[sk]); }
|
|
296
|
+
} catch(e) {}
|
|
297
|
+
if (scroll) {
|
|
298
|
+
window.scrollTo(scroll.x, scroll.y);
|
|
299
|
+
}
|
|
241
300
|
})(${JSON.stringify(checkpoint.localStorage)}, ${JSON.stringify(checkpoint.sessionStorage)}, ${JSON.stringify(checkpoint.scroll)})`,
|
|
242
301
|
returnByValue: false,
|
|
243
302
|
});
|
package/dist/tools/click.js
CHANGED
|
@@ -59,7 +59,7 @@ export async function weaveClick(session, args, config) {
|
|
|
59
59
|
execution_ms: Date.now() - startMs,
|
|
60
60
|
tab_id: targetId,
|
|
61
61
|
url: initialUrl,
|
|
62
|
-
title: "",
|
|
62
|
+
title: clientMatch?.title || "",
|
|
63
63
|
url_changed: false,
|
|
64
64
|
dom_changed: false,
|
|
65
65
|
stuck_warning: false,
|
|
@@ -355,9 +355,10 @@ export async function weaveClick(session, args, config) {
|
|
|
355
355
|
if (hasOpenDialog(session)) {
|
|
356
356
|
return { stuck: false, url: currentUrl, newMap: null, dialog_open: true };
|
|
357
357
|
}
|
|
358
|
+
const isElectronSession = Boolean(clientMatch?.isElectron);
|
|
358
359
|
await Promise.all([
|
|
359
360
|
waitForDOMStable(session, 20, 150),
|
|
360
|
-
waitForNetworkIdle(session, 30, 150).catch(() => { })
|
|
361
|
+
isElectronSession ? Promise.resolve() : waitForNetworkIdle(session, 30, 150).catch(() => { })
|
|
361
362
|
]);
|
|
362
363
|
const finalUrl = (await Page.getFrameTree()).frameTree.frame.url;
|
|
363
364
|
// Fast path: URL changed → definitely not stuck, skip map rebuild
|
|
@@ -372,11 +373,11 @@ export async function weaveClick(session, args, config) {
|
|
|
372
373
|
if (sel) {
|
|
373
374
|
try {
|
|
374
375
|
const { result: canvasCheck } = await session.Runtime.evaluate({
|
|
375
|
-
expression: `(function(s) {
|
|
376
|
-
const el = document.querySelector(s);
|
|
377
|
-
if (!el) return false;
|
|
378
|
-
// Check if the element itself or its closest ancestor is a canvas
|
|
379
|
-
return el.tagName === 'CANVAS' || !!el.closest('canvas');
|
|
376
|
+
expression: `(function(s) {
|
|
377
|
+
const el = document.querySelector(s);
|
|
378
|
+
if (!el) return false;
|
|
379
|
+
// Check if the element itself or its closest ancestor is a canvas
|
|
380
|
+
return el.tagName === 'CANVAS' || !!el.closest('canvas');
|
|
380
381
|
})(${JSON.stringify(sel)})`,
|
|
381
382
|
returnByValue: true,
|
|
382
383
|
});
|
|
@@ -395,10 +396,10 @@ export async function weaveClick(session, args, config) {
|
|
|
395
396
|
if (coords) {
|
|
396
397
|
try {
|
|
397
398
|
const { result: coordCanvasCheck } = await session.Runtime.evaluate({
|
|
398
|
-
expression: `(function(x, y) {
|
|
399
|
-
const el = document.elementFromPoint(x, y);
|
|
400
|
-
if (!el) return false;
|
|
401
|
-
return el.tagName === 'CANVAS' || !!el.closest('canvas');
|
|
399
|
+
expression: `(function(x, y) {
|
|
400
|
+
const el = document.elementFromPoint(x, y);
|
|
401
|
+
if (!el) return false;
|
|
402
|
+
return el.tagName === 'CANVAS' || !!el.closest('canvas');
|
|
402
403
|
})(${coords.x}, ${coords.y})`,
|
|
403
404
|
returnByValue: true,
|
|
404
405
|
});
|
|
@@ -416,10 +417,20 @@ export async function weaveClick(session, args, config) {
|
|
|
416
417
|
}
|
|
417
418
|
let newMap = getCachedActionMap(targetId, finalUrl);
|
|
418
419
|
if (!newMap) {
|
|
419
|
-
|
|
420
|
-
|
|
420
|
+
const _emptyMap = { url: finalUrl, title: '', elements: [], targetId };
|
|
421
|
+
const timeoutMap = new Promise((resolve) => {
|
|
422
|
+
setTimeout(() => resolve(_emptyMap), 1500);
|
|
423
|
+
});
|
|
424
|
+
newMap = await Promise.race([
|
|
425
|
+
buildActionMap(session, { lite: true }, targetId),
|
|
426
|
+
timeoutMap
|
|
427
|
+
]).catch(() => _emptyMap);
|
|
428
|
+
if (newMap.elements.length > 0) {
|
|
429
|
+
cacheActionMap(targetId, newMap);
|
|
430
|
+
}
|
|
421
431
|
}
|
|
422
|
-
const
|
|
432
|
+
const safeMap = newMap || { url: finalUrl, title: '', elements: [], targetId };
|
|
433
|
+
const delta = computeDelta(targetId, initialUrl, safeMap.elements);
|
|
423
434
|
const changesCount = delta ? delta.added.length + delta.removed.length + delta.changed.length : 0;
|
|
424
435
|
// Check scroll position — same-page anchors scroll without URL/DOM change
|
|
425
436
|
let scrollYAfter = 0;
|
|
@@ -437,22 +448,22 @@ export async function weaveClick(session, args, config) {
|
|
|
437
448
|
const sel = safeSelector(element);
|
|
438
449
|
if (sel) {
|
|
439
450
|
const { result: ckResult } = await session.Runtime.evaluate({
|
|
440
|
-
expression: `(function(sel) {
|
|
441
|
-
var el = document.querySelector(sel);
|
|
442
|
-
if (!el) return null;
|
|
443
|
-
// Check the element itself
|
|
444
|
-
var t = (el.getAttribute('type') || '').toLowerCase();
|
|
445
|
-
if (t === 'radio' || t === 'checkbox') return el.checked;
|
|
446
|
-
// If it's a <label>, follow its 'for' attribute to the referenced input
|
|
447
|
-
if (el.tagName === 'LABEL') {
|
|
448
|
-
var forId = el.getAttribute('for');
|
|
449
|
-
var target = forId ? document.getElementById(forId) : el.querySelector('input');
|
|
450
|
-
if (target) {
|
|
451
|
-
var t2 = (target.getAttribute('type') || '').toLowerCase();
|
|
452
|
-
if (t2 === 'radio' || t2 === 'checkbox') return target.checked;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return null;
|
|
451
|
+
expression: `(function(sel) {
|
|
452
|
+
var el = document.querySelector(sel);
|
|
453
|
+
if (!el) return null;
|
|
454
|
+
// Check the element itself
|
|
455
|
+
var t = (el.getAttribute('type') || '').toLowerCase();
|
|
456
|
+
if (t === 'radio' || t === 'checkbox') return el.checked;
|
|
457
|
+
// If it's a <label>, follow its 'for' attribute to the referenced input
|
|
458
|
+
if (el.tagName === 'LABEL') {
|
|
459
|
+
var forId = el.getAttribute('for');
|
|
460
|
+
var target = forId ? document.getElementById(forId) : el.querySelector('input');
|
|
461
|
+
if (target) {
|
|
462
|
+
var t2 = (target.getAttribute('type') || '').toLowerCase();
|
|
463
|
+
if (t2 === 'radio' || t2 === 'checkbox') return target.checked;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
456
467
|
})(${JSON.stringify(sel)})`,
|
|
457
468
|
returnByValue: true,
|
|
458
469
|
}).catch(() => ({ result: { value: null } }));
|
|
@@ -469,16 +480,16 @@ export async function weaveClick(session, args, config) {
|
|
|
469
480
|
const sel = safeSelector(element);
|
|
470
481
|
if (sel) {
|
|
471
482
|
const { result: ariaRes } = await session.Runtime.evaluate({
|
|
472
|
-
expression: `(function(sel) {
|
|
473
|
-
const el = document.querySelector(sel);
|
|
474
|
-
if (!el) return null;
|
|
475
|
-
return JSON.stringify({
|
|
476
|
-
expanded: el.getAttribute('aria-expanded'),
|
|
477
|
-
selected: el.getAttribute('aria-selected'),
|
|
478
|
-
pressed: el.getAttribute('aria-pressed'),
|
|
479
|
-
checked: el.getAttribute('aria-checked'),
|
|
480
|
-
current: el.getAttribute('aria-current')
|
|
481
|
-
});
|
|
483
|
+
expression: `(function(sel) {
|
|
484
|
+
const el = document.querySelector(sel);
|
|
485
|
+
if (!el) return null;
|
|
486
|
+
return JSON.stringify({
|
|
487
|
+
expanded: el.getAttribute('aria-expanded'),
|
|
488
|
+
selected: el.getAttribute('aria-selected'),
|
|
489
|
+
pressed: el.getAttribute('aria-pressed'),
|
|
490
|
+
checked: el.getAttribute('aria-checked'),
|
|
491
|
+
current: el.getAttribute('aria-current')
|
|
492
|
+
});
|
|
482
493
|
})(${JSON.stringify(sel)})`,
|
|
483
494
|
returnByValue: true,
|
|
484
495
|
}).catch(() => ({ result: { value: null } }));
|
|
@@ -548,7 +559,7 @@ export async function weaveClick(session, args, config) {
|
|
|
548
559
|
execution_ms: executionMs,
|
|
549
560
|
tab_id: targetId,
|
|
550
561
|
url: finalUrl,
|
|
551
|
-
title: newMap?.title || "",
|
|
562
|
+
title: newMap?.title || clientMatch?.title || "",
|
|
552
563
|
url_changed: finalUrl !== initialUrl,
|
|
553
564
|
...(args.include_map ? { page_map: newMap ? toMarkdownTable(newMap.elements, finalUrl, newMap.title) : "" } : {}),
|
|
554
565
|
dom_changed: !isStuck,
|