@weavetab/mcp 2.5.0-beta.2 → 2.5.0-beta.5
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 +216 -631
- package/README.md +153 -1074
- 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 +10 -6
- 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.d.ts +1 -0
- package/dist/cli/plugin.js +81 -18
- package/dist/cli/updater.d.ts +1 -1
- package/dist/cli/updater.js +27 -7
- package/dist/config/loader.d.ts +9 -1
- package/dist/config/loader.js +42 -3
- package/dist/desktop/discovery.d.ts +44 -0
- package/dist/desktop/discovery.js +364 -0
- package/dist/index.js +137 -21
- 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 +88 -5
- package/dist/plugin/loader.d.ts +12 -1
- package/dist/plugin/loader.js +280 -70
- package/dist/plugin/pipeline.d.ts +75 -0
- package/dist/plugin/pipeline.js +183 -0
- package/dist/plugin/registry.d.ts +2 -1
- package/dist/plugin/registry.js +24 -14
- package/dist/security/secrets.d.ts +7 -0
- package/dist/security/secrets.js +37 -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 +453 -178
- 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 +52 -7
- package/dist/tools/burst.d.ts +17 -8
- package/dist/tools/burst.js +236 -77
- package/dist/tools/captcha.js +1 -1
- 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.d.ts +1 -1
- package/dist/tools/navigate.js +34 -39
- 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 +15 -7
- 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 +37 -41
- package/docs/PLUGINS.md +121 -0
- package/docs/TOOLS.md +54 -0
- package/docs/architecture/blind-injection.md +83 -20
- package/docs/architecture/overview.md +49 -49
- package/docs/architecture/security.md +8 -7
- package/docs/architecture/transport.md +2 -2
- package/docs/cli/cli-commands.md +7 -1
- package/docs/cli/cli-flags.md +15 -7
- package/docs/cli/cli-scripting.md +6 -3
- package/docs/configuration/config-locations.md +3 -4
- package/docs/configuration/env-variables.md +4 -4
- package/docs/configuration/folders.md +55 -54
- package/docs/desktop-automation/electron-apps.md +214 -162
- package/docs/get-started/introduction.md +60 -60
- package/docs/get-started/setup-wizard.md +7 -6
- package/docs/intelligence/site-profiles.md +151 -0
- package/docs/tools/tools.md +631 -293
- package/docs/troubleshooting/trouble-browser.md +1 -1
- 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/icon.png +0 -0
- package/mcp.json +87 -71
- package/package.json +118 -119
- package/server.json +5 -5
- package/ext-dist/sidepanel/sidepanel.js +0 -25
- package/ext-dist/sidepanel.html +0 -547
package/dist/tools/type.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logDebug } from "../audit/logger.js";
|
|
2
|
-
import { buildActionMap, findBestMatch, findAllMatches, safeSelector, toMarkdownTable, resolveByAria } from "../cdp/walker.js";
|
|
2
|
+
import { buildActionMap, findBestMatch, findAllMatches, safeSelector, toMarkdownTable, resolveByAria, recoverDetachedRef } from "../cdp/walker.js";
|
|
3
3
|
import { checkDomain } from "../security/blacklist.js";
|
|
4
4
|
import { logAction } from "../audit/logger.js";
|
|
5
5
|
import { recordAction, getLoopDiagnostics, trackToolCall, detectStuckLoop } from "../intelligence/trail.js";
|
|
@@ -11,7 +11,8 @@ import { storeSnapshot, computeRichDelta } from "../state/delta.js";
|
|
|
11
11
|
import { sessionRegistry } from "../state/session.js";
|
|
12
12
|
import { saveDomainProfile, getBestSelectorForIntent, logMemorySuccess, logMemoryFailure, loadDomainProfile } from "../intelligence/sessionMemory.js";
|
|
13
13
|
import { getConfigAgentId, getConfigHeadless } from "../config/loader.js";
|
|
14
|
-
import { sendToExtension } from "../cdp/helpers.js";
|
|
14
|
+
import { sendToExtension, registerAndHardenSecretField } from "../cdp/helpers.js";
|
|
15
|
+
import { SessionSecretTracker } from "../security/secrets.js";
|
|
15
16
|
import { appendKnowledgeHint } from "../intelligence/hints.js";
|
|
16
17
|
import { verifyActionability, quickHitCheck } from "../sensors/actionability.js";
|
|
17
18
|
/**
|
|
@@ -146,6 +147,13 @@ async function resolveTypingTarget(session, map, args, _targetId) {
|
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
let element = id ? map.elements.find((el) => el.id === id || el.id.endsWith(id)) : undefined;
|
|
150
|
+
// SPA Auto-Recovery: if w:NN ref detached due to re-render, attempt fast heuristic recovery
|
|
151
|
+
if (!element && id && id.startsWith("w:")) {
|
|
152
|
+
const recovery = await recoverDetachedRef(session, _targetId, id);
|
|
153
|
+
if (recovery.recovered && recovery.element) {
|
|
154
|
+
element = recovery.element;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
149
157
|
// Fast-fail: w:NN IDs not in the current map are stale. Return immediately.
|
|
150
158
|
if (!element && id && id.startsWith("w:")) {
|
|
151
159
|
return {
|
|
@@ -253,18 +261,23 @@ async function resolveTypingTarget(session, map, args, _targetId) {
|
|
|
253
261
|
y: Math.round(r.top + r.height/2)
|
|
254
262
|
};
|
|
255
263
|
};
|
|
256
|
-
var sel;
|
|
257
|
-
sel = 'textarea[name="q"], input[name="q"], [aria-label="Search"], [title="Search"]';
|
|
258
|
-
var q = document.querySelector(sel);
|
|
259
|
-
var fromQ = pick(q);
|
|
260
|
-
if (fromQ) return JSON.stringify(fromQ);
|
|
261
264
|
var el = document.activeElement;
|
|
262
|
-
var isEditable = el && (
|
|
265
|
+
var isEditable = el && el !== document.body && (
|
|
263
266
|
el.isContentEditable ||
|
|
264
267
|
el.tagName === 'TEXTAREA' ||
|
|
265
|
-
(el.tagName === 'INPUT' && !['button','submit','checkbox','radio','hidden','file'].includes((el.type||'').toLowerCase()))
|
|
268
|
+
(el.tagName === 'INPUT' && !['button','submit','checkbox','radio','hidden','file'].includes((el.type||'').toLowerCase())) ||
|
|
269
|
+
el.getAttribute('role') === 'textbox' || el.getAttribute('role') === 'searchbox' || el.getAttribute('role') === 'combobox'
|
|
266
270
|
);
|
|
267
|
-
if (isEditable)
|
|
271
|
+
if (isEditable) {
|
|
272
|
+
var fromActive = pick(el);
|
|
273
|
+
if (fromActive) return JSON.stringify(fromActive);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
var sel = 'textarea[name="q"], input[name="q"], [aria-label="Search"], [title="Search"]';
|
|
277
|
+
var q = document.querySelector(sel);
|
|
278
|
+
var fromQ = pick(q);
|
|
279
|
+
if (fromQ) return JSON.stringify(fromQ);
|
|
280
|
+
|
|
268
281
|
var sels = ['[contenteditable="true"]','[contenteditable=""]','textarea','input[type="search"]','input[type="text"]','input:not([type])','[role="textbox"]','[role="combobox"]','input[name="q"]'];
|
|
269
282
|
var best = null, bestArea = 0;
|
|
270
283
|
for (var si = 0; si < sels.length; si++) {
|
|
@@ -324,14 +337,12 @@ async function resolveTypingTarget(session, map, args, _targetId) {
|
|
|
324
337
|
};
|
|
325
338
|
}
|
|
326
339
|
export async function weaveType(session, args, config) {
|
|
327
|
-
const
|
|
340
|
+
const id = args.id || args.target || args.selector;
|
|
341
|
+
const { label, intent, backendNodeId: directNodeId, text, clearFirst, fast = false } = args;
|
|
328
342
|
const { Page, DOM } = session;
|
|
329
343
|
const startMs = Date.now();
|
|
330
344
|
const { frameTree } = await Page.getFrameTree();
|
|
331
345
|
const url = frameTree.frame.url;
|
|
332
|
-
if (!id && !label && !intent && args.x === undefined && args.y === undefined) {
|
|
333
|
-
return { success: false, error_code: "NO_TARGET", message: `[!] browser_type requires id, label, or intent — no target specified.` };
|
|
334
|
-
}
|
|
335
346
|
const domainCheck = checkDomain(url, config);
|
|
336
347
|
if (!domainCheck.allowed) {
|
|
337
348
|
return { success: false, error_code: "DOMAIN_BLOCKED", message: `[!] Weave blocked: ${domainCheck.reason}` };
|
|
@@ -460,7 +471,7 @@ export async function weaveType(session, args, config) {
|
|
|
460
471
|
}
|
|
461
472
|
const loopDiag = getLoopDiagnostics("browser_type", target.ref);
|
|
462
473
|
if (loopDiag.severity === "critical") {
|
|
463
|
-
return { success: false, error_code: "STUCK_LOOP_WARNING", message: `[!] Loop detected: You have tried to type into [${target.ref}] ${loopDiag.strikes} times without success.
|
|
474
|
+
return { success: false, error_code: "STUCK_LOOP_WARNING", message: `[!] Loop detected: You have tried to type into [${target.ref}] ${loopDiag.strikes} times without success. Run browser_map({ force: true }) if this is a new task.` };
|
|
464
475
|
}
|
|
465
476
|
if (loopDiag.severity === "warning") {
|
|
466
477
|
logAction("browser_type", target.ref, `loop warning: ${loopDiag.strikes} consecutive strikes on [${target.ref}]`);
|
|
@@ -536,7 +547,27 @@ export async function weaveType(session, args, config) {
|
|
|
536
547
|
coords = { x: actCheck.x, y: actCheck.y };
|
|
537
548
|
}
|
|
538
549
|
}
|
|
539
|
-
|
|
550
|
+
// Detect password / secret fields early to protect HUD, logs, and register secret
|
|
551
|
+
const isSecretParam = !!args.isSecret || !!args.secret;
|
|
552
|
+
const isPassword = isSecretParam || await session.Runtime.evaluate({
|
|
553
|
+
expression: `(function(sel) {
|
|
554
|
+
const el = (sel === 'document.activeElement') ? document.activeElement : (document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null));
|
|
555
|
+
if (!el) return false;
|
|
556
|
+
const t = (el.getAttribute('type') || '').toLowerCase();
|
|
557
|
+
const ac = (el.getAttribute('autocomplete') || '').toLowerCase();
|
|
558
|
+
const name = (el.getAttribute('name') || '').toLowerCase();
|
|
559
|
+
const id = (el.getAttribute('id') || '').toLowerCase();
|
|
560
|
+
const aria = (el.getAttribute('aria-label') || '').toLowerCase();
|
|
561
|
+
const isSecAttr = el.hasAttribute('data-wt-secret');
|
|
562
|
+
const secretRegex = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha/i;
|
|
563
|
+
return isSecAttr || t === 'password' || ac.includes('password') || secretRegex.test(name) || secretRegex.test(id) || secretRegex.test(aria);
|
|
564
|
+
})(${JSON.stringify(selector)})`,
|
|
565
|
+
returnByValue: true
|
|
566
|
+
}).then(r => r.result?.value === true).catch(() => false);
|
|
567
|
+
if (isPassword && text) {
|
|
568
|
+
SessionSecretTracker.registerSecretValue(targetId, text);
|
|
569
|
+
}
|
|
570
|
+
const typePreview = isPassword ? "••••••••" : text.slice(0, 20).replace(/'/g, "");
|
|
540
571
|
const typeStatus = ["typing", "entering", "inputting", "writing"][Math.floor(Math.random() * 4)];
|
|
541
572
|
await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `${typeStatus} ${typePreview}...` } });
|
|
542
573
|
// ── 3. Focus + Type ──────────────────────────────────────────────────────
|
|
@@ -569,10 +600,31 @@ export async function weaveType(session, args, config) {
|
|
|
569
600
|
};
|
|
570
601
|
}
|
|
571
602
|
}
|
|
572
|
-
if
|
|
603
|
+
// Check if element is ALREADY focused before clicking it to prevent unfocusing or toggling
|
|
604
|
+
const alreadyFocusedRes = await session.Runtime.evaluate({
|
|
605
|
+
expression: `(function(sel) {
|
|
606
|
+
if (!sel || sel === 'document.activeElement') return true;
|
|
607
|
+
const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
608
|
+
return el ? (document.activeElement === el || el.contains(document.activeElement)) : false;
|
|
609
|
+
})(${JSON.stringify(selector)})`,
|
|
610
|
+
returnByValue: true
|
|
611
|
+
}).catch(() => null);
|
|
612
|
+
const isAlreadyFocused = alreadyFocusedRes?.result?.value === true;
|
|
613
|
+
if (!isAlreadyFocused && coords.x !== undefined && coords.y !== undefined) {
|
|
573
614
|
await ghostClick(session, coords.x, coords.y);
|
|
574
615
|
await session.Input.dispatchMouseEvent({ type: "mouseReleased", x: coords.x, y: coords.y, button: "left", clickCount: 1 });
|
|
575
|
-
await new Promise(r => setTimeout(r, fast ? 10 :
|
|
616
|
+
await new Promise(r => setTimeout(r, fast ? 10 : 80 + Math.random() * 80));
|
|
617
|
+
}
|
|
618
|
+
else if (isAlreadyFocused) {
|
|
619
|
+
// Ensure focus without hardware click re-trigger
|
|
620
|
+
await session.Runtime.evaluate({
|
|
621
|
+
expression: `(function(sel) {
|
|
622
|
+
if (!sel || sel === 'document.activeElement') return;
|
|
623
|
+
const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
624
|
+
if (el && typeof el.focus === 'function') el.focus();
|
|
625
|
+
})(${JSON.stringify(selector)})`,
|
|
626
|
+
returnByValue: false
|
|
627
|
+
}).catch(() => { });
|
|
576
628
|
}
|
|
577
629
|
// Manual clear only when explicitly requested (insertText replaces naturally)
|
|
578
630
|
const shouldPaste = args.paste || text.length > 8;
|
|
@@ -621,18 +673,10 @@ export async function weaveType(session, args, config) {
|
|
|
621
673
|
// When true the post-type event dispatch block is suppressed to prevent
|
|
622
674
|
// the double-onChange triplication bug on React/Vue controlled inputs.
|
|
623
675
|
let nativeSetterUsed = false;
|
|
624
|
-
// Detect password fields — always ghost type these regardless of profile
|
|
625
|
-
const isPassword = await session.Runtime.evaluate({
|
|
626
|
-
expression: `(function(sel) {
|
|
627
|
-
const el = document.querySelector(sel);
|
|
628
|
-
return el ? (el.getAttribute('type') || '').toLowerCase() === 'password' : false;
|
|
629
|
-
})(${JSON.stringify(selector)})`,
|
|
630
|
-
returnByValue: true
|
|
631
|
-
}).then(r => r.result?.value === true).catch(() => false);
|
|
632
676
|
// Click-to-type delay
|
|
633
677
|
await new Promise(r => setTimeout(r, 50 + Math.random() * 100));
|
|
634
|
-
// Auto-enable paste mode for long inputs to prevent character dropping
|
|
635
|
-
const shouldPasteForType = args.paste || text.length > 8;
|
|
678
|
+
// Auto-enable paste mode for long inputs to prevent character dropping (never for secret/password fields)
|
|
679
|
+
const shouldPasteForType = args.paste || (text.length > 8 && !isPassword);
|
|
636
680
|
if (shouldPasteForType && !isPassword) {
|
|
637
681
|
// Paste mode: Use clipboard for reliable long input handling
|
|
638
682
|
// This prevents character dropping on long inputs
|
|
@@ -653,7 +697,7 @@ export async function weaveType(session, args, config) {
|
|
|
653
697
|
await session.Input.dispatchKeyEvent({ type: "rawKeyDown", key: "v", windowsVirtualKeyCode: 86, nativeVirtualKeyCode: 86, modifiers: 2 });
|
|
654
698
|
await session.Input.dispatchKeyEvent({ type: "keyUp", key: "v", windowsVirtualKeyCode: 86, nativeVirtualKeyCode: 86, modifiers: 2 });
|
|
655
699
|
await new Promise(r => setTimeout(r, 120));
|
|
656
|
-
// Paste verification: check if the field actually received
|
|
700
|
+
// Paste verification: check if the field actually received text.
|
|
657
701
|
// Vue/React controlled inputs re-render the DOM node during focus, making
|
|
658
702
|
// Ctrl+V land on the old (detached) node. If el.value is still empty,
|
|
659
703
|
// fallback to Input.insertText which targets the currently-focused element.
|
|
@@ -662,6 +706,8 @@ export async function weaveType(session, args, config) {
|
|
|
662
706
|
expression: `(function(sel) {
|
|
663
707
|
var el = (sel === 'document.activeElement') ? document.activeElement : (document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null));
|
|
664
708
|
if (!el) return null;
|
|
709
|
+
var isSec = el.hasAttribute('data-wt-secret') || (el.tagName === 'INPUT' && el.type === 'password');
|
|
710
|
+
if (isSec) return (el.value && el.value.length > 0) ? '__HAS_CONTENT__' : '';
|
|
665
711
|
return el.value !== undefined ? el.value : (el.textContent || null);
|
|
666
712
|
})(${JSON.stringify(selector)})`,
|
|
667
713
|
returnByValue: true,
|
|
@@ -782,53 +828,50 @@ export async function weaveType(session, args, config) {
|
|
|
782
828
|
if (text) {
|
|
783
829
|
try {
|
|
784
830
|
const isActiveElementRef = selector === "document.activeElement";
|
|
785
|
-
const
|
|
831
|
+
const checkFieldValue = async () => {
|
|
786
832
|
const expression = isActiveElementRef
|
|
787
|
-
? `(function() {
|
|
833
|
+
? `(function(expected) {
|
|
788
834
|
var el = document.activeElement;
|
|
789
|
-
if (!el) return
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
835
|
+
if (!el) return false;
|
|
836
|
+
var val = el.value !== undefined ? el.value : (el.textContent || '');
|
|
837
|
+
return val === expected || (typeof val === 'string' && val.trim() === expected.trim())
|
|
838
|
+
|| (typeof val === 'string' && typeof expected === 'string' &&
|
|
839
|
+
val.replace(/\\D/g, '') === expected.replace(/\\D/g, '') && expected.length > 3);
|
|
840
|
+
})(${JSON.stringify(text)})`
|
|
841
|
+
: `(function(sel, expected) {
|
|
842
|
+
var el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
843
|
+
if (!el) return false;
|
|
844
|
+
var val = el.value !== undefined ? el.value : (el.textContent || '');
|
|
845
|
+
return val === expected || (typeof val === 'string' && val.trim() === expected.trim())
|
|
846
|
+
|| (typeof val === 'string' && typeof expected === 'string' &&
|
|
847
|
+
val.replace(/\\D/g, '') === expected.replace(/\\D/g, '') && expected.length > 3);
|
|
848
|
+
})(${JSON.stringify(selector)}, ${JSON.stringify(text)})`;
|
|
849
|
+
const { result } = await session.Runtime.evaluate({ expression, returnByValue: true }).catch(() => ({ result: { value: false } }));
|
|
850
|
+
return result?.value === true;
|
|
799
851
|
};
|
|
800
|
-
|
|
801
|
-
textApplied = val === text || (typeof val === 'string' && val.trim() === text.trim())
|
|
802
|
-
// tel/formatted inputs may strip chars (e.g. dashes); also
|
|
803
|
-
// accept if the value simply contains the digits we typed.
|
|
804
|
-
|| (typeof val === 'string' && typeof text === 'string' &&
|
|
805
|
-
val.replace(/\D/g, '') === text.replace(/\D/g, '') && text.length > 3);
|
|
852
|
+
textApplied = await checkFieldValue();
|
|
806
853
|
if (!textApplied) {
|
|
807
854
|
await new Promise(r => setTimeout(r, 200));
|
|
808
855
|
await waitForDOMStable(session, 50, 200).catch(() => { });
|
|
809
|
-
|
|
810
|
-
textApplied = retryVal === text || (typeof retryVal === 'string' && retryVal.trim() === text.trim());
|
|
856
|
+
textApplied = await checkFieldValue();
|
|
811
857
|
}
|
|
812
858
|
if (!textApplied) {
|
|
813
859
|
await new Promise(r => setTimeout(r, 400));
|
|
814
860
|
await waitForDOMStable(session, 50, 300).catch(() => { });
|
|
815
|
-
|
|
816
|
-
textApplied = finalVal === text || (typeof finalVal === 'string' && finalVal.trim() === text.trim());
|
|
861
|
+
textApplied = await checkFieldValue();
|
|
817
862
|
}
|
|
818
863
|
// ghostType may drop characters on busy Windows machines.
|
|
819
864
|
// Retry with atomic Input.insertText if ghostType failed verification.
|
|
820
865
|
if (!textApplied && !args.noRetry && (isPassword || args.profile === 'deliberate')) {
|
|
821
866
|
await session.Input.insertText({ text });
|
|
822
867
|
await new Promise(r => setTimeout(r, 300));
|
|
823
|
-
|
|
824
|
-
textApplied = retryVal === text || (typeof retryVal === 'string' && retryVal.trim() === text.trim());
|
|
868
|
+
textApplied = await checkFieldValue();
|
|
825
869
|
}
|
|
826
870
|
// Paste mode failed — retry with Input.insertText (already focused, so this lands correctly).
|
|
827
871
|
if (!textApplied && !args.noRetry && shouldPasteForType && !isPassword) {
|
|
828
872
|
await session.Input.insertText({ text });
|
|
829
873
|
await new Promise(r => setTimeout(r, 200));
|
|
830
|
-
|
|
831
|
-
textApplied = pasteRetryVal === text || (typeof pasteRetryVal === 'string' && pasteRetryVal.trim() === text.trim());
|
|
874
|
+
textApplied = await checkFieldValue();
|
|
832
875
|
}
|
|
833
876
|
}
|
|
834
877
|
catch { /* non-fatal */ }
|
|
@@ -955,7 +998,11 @@ export async function weaveType(session, args, config) {
|
|
|
955
998
|
}
|
|
956
999
|
}
|
|
957
1000
|
catch { /* best-effort */ }
|
|
958
|
-
|
|
1001
|
+
// If typing into a password/secret field, register and harden DOM immediately
|
|
1002
|
+
if (isPassword) {
|
|
1003
|
+
await registerAndHardenSecretField(session, targetId, selector, text, target.objectId);
|
|
1004
|
+
}
|
|
1005
|
+
logAction("browser_type", resolvedRef, isPassword ? `[secret text] into ${resolvedLabel} (exec: ${executionMs}ms)` : `[text] into ${resolvedLabel} (exec: ${executionMs}ms)`);
|
|
959
1006
|
recordAction({
|
|
960
1007
|
timestamp: new Date().toISOString(),
|
|
961
1008
|
tool: "browser_type",
|
|
@@ -1007,15 +1054,17 @@ export async function weaveType(session, args, config) {
|
|
|
1007
1054
|
else {
|
|
1008
1055
|
suggestedNext = "Typing successful - consider clicking submit or proceeding to next field";
|
|
1009
1056
|
}
|
|
1010
|
-
// Read back the verified field value to report accurate length.
|
|
1057
|
+
// Read back the verified field value to report accurate length (skip DOM value inspect for password fields).
|
|
1011
1058
|
// Previously this was always text.length, which was misleading when paste failed silently.
|
|
1012
1059
|
let verifiedLength = text.length;
|
|
1013
|
-
if (selector && selector !== 'document.activeElement') {
|
|
1060
|
+
if (!isPassword && selector && selector !== 'document.activeElement') {
|
|
1014
1061
|
try {
|
|
1015
1062
|
const { result: vlRes } = await session.Runtime.evaluate({
|
|
1016
1063
|
expression: `(function(sel) {
|
|
1017
1064
|
var el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
1018
1065
|
if (!el) return -1;
|
|
1066
|
+
var isSec = el.hasAttribute('data-wt-secret') || (el.tagName === 'INPUT' && el.type === 'password');
|
|
1067
|
+
if (isSec) return -1;
|
|
1019
1068
|
var v = el.value !== undefined ? el.value : (el.textContent || '');
|
|
1020
1069
|
return (v || '').length;
|
|
1021
1070
|
})(${JSON.stringify(selector)})`,
|
|
@@ -4,7 +4,7 @@ import { recordAction } from "../intelligence/trail.js";
|
|
|
4
4
|
import { SessionSecretTracker, secretStore } from "../security/secrets.js";
|
|
5
5
|
import { sessionRegistry } from "../state/session.js";
|
|
6
6
|
import { getConfigAgentId } from "../config/loader.js";
|
|
7
|
-
import { sendToExtension } from "../cdp/helpers.js";
|
|
7
|
+
import { sendToExtension, applySecretHardening } from "../cdp/helpers.js";
|
|
8
8
|
import { cacheActionMap, getCachedActionMap } from "../state/cache.js";
|
|
9
9
|
export async function weaveTypeSecret(session, args, config) {
|
|
10
10
|
const startTime = Date.now();
|
|
@@ -68,30 +68,45 @@ export async function weaveTypeSecret(session, args, config) {
|
|
|
68
68
|
map = await buildActionMap(session, { lite: true }, targetId);
|
|
69
69
|
cacheActionMap(targetId, map);
|
|
70
70
|
}
|
|
71
|
-
const query = args.id || args.target;
|
|
71
|
+
const query = args.id || args.target || args.selector;
|
|
72
72
|
if (!query) {
|
|
73
|
-
return { success: false, error_code: "INVALID_ARGS", message: "[!] Must provide id or
|
|
73
|
+
return { success: false, error_code: "INVALID_ARGS", message: "[!] Must provide id, target, or selector" };
|
|
74
74
|
}
|
|
75
|
-
let target = map.elements.find(el => el.id === query ||
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
let target = map.elements.find(el => el.id === query ||
|
|
76
|
+
el.id === `w:${query}` ||
|
|
77
|
+
el.selector === query ||
|
|
78
|
+
el.id.endsWith(query.startsWith("w:") ? query.slice(2) : query));
|
|
79
|
+
let selector = "";
|
|
80
|
+
if (query === "focused") {
|
|
81
|
+
selector = "document.activeElement";
|
|
82
|
+
}
|
|
83
|
+
else if (!target) {
|
|
84
|
+
if (query.startsWith("#") || query.startsWith(".") || query.startsWith("[") || query.startsWith("input")) {
|
|
85
|
+
selector = query;
|
|
84
86
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
else {
|
|
88
|
+
const matches = findAllMatches(map.elements, query);
|
|
89
|
+
if (matches.length === 0) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error_code: "ELEMENT_NOT_FOUND",
|
|
93
|
+
message: `[!] Could not find input matching "${query}"`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (matches.length > 1) {
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
error_code: "MULTIPLE_ELEMENTS_FOUND",
|
|
100
|
+
message: `[!] Refusing to guess: found ${matches.length} matches for "${query}". Please use a more specific selector.`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
target = matches[0];
|
|
91
104
|
}
|
|
92
|
-
target = matches[0];
|
|
93
105
|
}
|
|
94
|
-
|
|
106
|
+
if (!selector && target) {
|
|
107
|
+
const rawId = (target.id ?? "").replace(/^w:/, "");
|
|
108
|
+
selector = target.selector || `[data-wt-id="${rawId}"]`;
|
|
109
|
+
}
|
|
95
110
|
await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `injecting [REDACTED]...` } });
|
|
96
111
|
// ── Step 5: Resolve DOM node, mark as secret, focus ───────────────────────
|
|
97
112
|
const nodeInfo = await session.Runtime.evaluate({
|
|
@@ -151,76 +166,27 @@ export async function weaveTypeSecret(session, args, config) {
|
|
|
151
166
|
SessionSecretTracker.registerSecretValue(targetId, secretValue);
|
|
152
167
|
await session.Input.insertText({ text: secretValue });
|
|
153
168
|
// ── Step 7.5: Hardened Proxy & Shadow DOM Isolation ───────────────────────
|
|
154
|
-
await session.
|
|
155
|
-
functionDeclaration: `function() {
|
|
156
|
-
try {
|
|
157
|
-
const el = this;
|
|
158
|
-
// 1. Redact direct .value access
|
|
159
|
-
const proto = Object.getPrototypeOf(el);
|
|
160
|
-
const origDesc = Object.getOwnPropertyDescriptor(proto, 'value');
|
|
161
|
-
if (origDesc) {
|
|
162
|
-
Object.defineProperty(el, 'value', {
|
|
163
|
-
get: function() { return '[REDACTED]'; },
|
|
164
|
-
set: origDesc.set,
|
|
165
|
-
configurable: false,
|
|
166
|
-
enumerable: true
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 2. Defeat array spread, iteration, and coercion
|
|
171
|
-
Object.defineProperty(el, Symbol.iterator, {
|
|
172
|
-
value: function* () { yield '[REDACTED]'; },
|
|
173
|
-
configurable: false
|
|
174
|
-
});
|
|
175
|
-
Object.defineProperty(el, Symbol.toPrimitive, {
|
|
176
|
-
value: function() { return '[REDACTED]'; },
|
|
177
|
-
configurable: false
|
|
178
|
-
});
|
|
179
|
-
// 2b. Defeat JSON.stringify and structuredClone data leaks
|
|
180
|
-
Object.defineProperty(el, 'toJSON', {
|
|
181
|
-
value: function() { return '[REDACTED]'; },
|
|
182
|
-
configurable: false
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// 3. Isolate in closed shadow DOM to prevent outer-world DOM traversal
|
|
186
|
-
if (el.parentNode && !el.parentNode.shadowRoot) {
|
|
187
|
-
const wrapper = document.createElement('div');
|
|
188
|
-
wrapper.className = '__wt_secret_vault';
|
|
189
|
-
// copy styles so layout doesn't break
|
|
190
|
-
const compStyle = window.getComputedStyle(el);
|
|
191
|
-
wrapper.style.display = compStyle.display === 'inline' ? 'inline-block' : compStyle.display;
|
|
192
|
-
wrapper.style.width = compStyle.width;
|
|
193
|
-
wrapper.style.height = compStyle.height;
|
|
194
|
-
|
|
195
|
-
el.parentNode.insertBefore(wrapper, el);
|
|
196
|
-
const shadow = wrapper.attachShadow({ mode: 'closed' });
|
|
197
|
-
shadow.appendChild(el);
|
|
198
|
-
}
|
|
199
|
-
} catch (e) {
|
|
200
|
-
// fail silently so as not to disrupt the page execution
|
|
201
|
-
}
|
|
202
|
-
}`,
|
|
203
|
-
objectId: nodeInfo.result.objectId,
|
|
204
|
-
silent: true
|
|
205
|
-
});
|
|
169
|
+
await applySecretHardening(session, nodeInfo.result.objectId, selector);
|
|
206
170
|
// Immediately zero out the local binding after use
|
|
207
171
|
// (TypeScript doesn't guarantee this but it signals intent to reviewers)
|
|
208
172
|
// secretValue = undefined; // can't reassign const — variable goes out of scope here
|
|
209
173
|
const executionMs = Date.now() - startTime;
|
|
210
174
|
// ── Step 8: Trail — key name + domain only, NEVER value ──────────────────
|
|
211
|
-
const
|
|
212
|
-
|
|
175
|
+
const targetLabel = target?.label || selector;
|
|
176
|
+
const targetIdStr = target?.id || query;
|
|
177
|
+
const finalRef = targetIdStr.startsWith('w:') ? targetIdStr : `w:${targetIdStr}`;
|
|
178
|
+
logAction("browser_type_secret", finalRef, `[secret injected: ${args.envKey}@${currentHostname}] into ${targetLabel} (exec: ${executionMs}ms)`);
|
|
213
179
|
recordAction({
|
|
214
180
|
timestamp: new Date().toISOString(),
|
|
215
181
|
tool: "browser_type_secret",
|
|
216
182
|
// input contains key name and domain ONLY — value is excluded intentionally
|
|
217
|
-
input: { ref: finalRef, label:
|
|
183
|
+
input: { ref: finalRef, label: targetLabel, envKey: args.envKey, domain: currentHostname },
|
|
218
184
|
result: { success: true },
|
|
219
185
|
});
|
|
220
186
|
// ── Step 9: Return — no secret value in result ────────────────────────────
|
|
221
187
|
return {
|
|
222
188
|
success: true,
|
|
223
|
-
typed_into:
|
|
189
|
+
typed_into: targetLabel,
|
|
224
190
|
ref: finalRef,
|
|
225
191
|
key: args.envKey,
|
|
226
192
|
domain: currentHostname,
|
package/dist/tools/upload.js
CHANGED
|
@@ -93,7 +93,7 @@ export async function browserUpload(session, options, config) {
|
|
|
93
93
|
}
|
|
94
94
|
return {
|
|
95
95
|
success: uploaded,
|
|
96
|
-
message: `
|
|
96
|
+
message: `attached ${filePaths.length} file(s)`,
|
|
97
97
|
file: path.basename(filePaths[0])
|
|
98
98
|
};
|
|
99
99
|
}
|
|
@@ -15,7 +15,7 @@ export interface CompileVideoResult {
|
|
|
15
15
|
compilerUsed?: string;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Checks if ffmpeg is available on the system PATH.
|
|
18
|
+
* Checks if ffmpeg is available on the system PATH without using a shell.
|
|
19
19
|
*/
|
|
20
20
|
export declare function isFFmpegAvailable(): boolean;
|
|
21
21
|
/**
|
|
@@ -5,17 +5,16 @@
|
|
|
5
5
|
* Falls back to ffmpeg if installed for .mp4/.webm video encoding.
|
|
6
6
|
* Weavetab by fy2ne
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
/**
|
|
12
|
-
* Checks if ffmpeg is available on the system PATH.
|
|
12
|
+
* Checks if ffmpeg is available on the system PATH without using a shell.
|
|
13
13
|
*/
|
|
14
14
|
export function isFFmpegAvailable() {
|
|
15
15
|
try {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
return true;
|
|
16
|
+
const res = spawnSync("ffmpeg", ["-version"], { stdio: "ignore" });
|
|
17
|
+
return res.status === 0;
|
|
19
18
|
}
|
|
20
19
|
catch {
|
|
21
20
|
return false;
|
|
@@ -120,21 +119,21 @@ export async function compileVideo(options) {
|
|
|
120
119
|
const concatFilePath = path.join(recordingDir, "concat.txt");
|
|
121
120
|
const concatContent = files.map(f => `file '${f.replace(/'/g, "'\\''")}'\nduration ${1 / fps}`).join("\n");
|
|
122
121
|
fs.writeFileSync(concatFilePath, concatContent);
|
|
123
|
-
let
|
|
122
|
+
let ffmpegArgs = ["-y", "-f", "concat", "-safe", "0", "-i", concatFilePath];
|
|
124
123
|
if (format === "webm") {
|
|
125
|
-
|
|
124
|
+
ffmpegArgs.push("-c:v", "libvpx-vp9", "-pix_fmt", "yuv420p", "-b:v", "1M");
|
|
126
125
|
}
|
|
127
126
|
else if (format === "mp4") {
|
|
128
|
-
|
|
127
|
+
ffmpegArgs.push("-c:v", "libx264", "-pix_fmt", "yuv420p", "-vf", "trunc(iw/2)*2:trunc(ih/2)*2");
|
|
129
128
|
}
|
|
130
129
|
else if (format === "gif") {
|
|
131
|
-
|
|
130
|
+
ffmpegArgs.push("-vf", `fps=${fps},scale=640:-1:flags=lanczos`);
|
|
132
131
|
}
|
|
133
132
|
else {
|
|
134
|
-
|
|
133
|
+
ffmpegArgs.push("-c:v", "libvpx-vp9", "-pix_fmt", "yuv420p", "-b:v", "1M");
|
|
135
134
|
}
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
ffmpegArgs.push(videoPath);
|
|
136
|
+
execFile("ffmpeg", ffmpegArgs, { cwd: recordingDir }, (error) => {
|
|
138
137
|
try {
|
|
139
138
|
fs.unlinkSync(concatFilePath);
|
|
140
139
|
}
|