@weavetab/mcp 2.5.0-beta.2 → 2.5.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +1 -1
  4. package/README.md +158 -961
  5. package/SECURITY.md +33 -22
  6. package/TROUBLESHOOTING.md +2 -2
  7. package/dist/cdp/bridge.js +2 -1
  8. package/dist/cdp/connector.d.ts +2 -0
  9. package/dist/cdp/connector.js +8 -4
  10. package/dist/cdp/helpers.d.ts +4 -0
  11. package/dist/cdp/helpers.js +181 -0
  12. package/dist/cdp/omni_broker.d.ts +1 -1
  13. package/dist/cdp/omni_broker.js +1 -1
  14. package/dist/cdp/walker.d.ts +25 -1
  15. package/dist/cdp/walker.js +304 -7
  16. package/dist/cli/plugin.js +24 -17
  17. package/dist/cli/updater.d.ts +1 -1
  18. package/dist/cli/updater.js +27 -7
  19. package/dist/config/loader.d.ts +8 -0
  20. package/dist/config/loader.js +41 -2
  21. package/dist/desktop/discovery.d.ts +44 -0
  22. package/dist/desktop/discovery.js +364 -0
  23. package/dist/index.js +95 -15
  24. package/dist/intelligence/marks.d.ts +36 -0
  25. package/dist/intelligence/marks.js +30 -0
  26. package/dist/intelligence/patternCompiler.d.ts +118 -0
  27. package/dist/intelligence/patternCompiler.js +259 -0
  28. package/dist/intelligence/sessionMemory.d.ts +2 -0
  29. package/dist/intelligence/sessionMemory.js +18 -8
  30. package/dist/overlay/injector.js +2 -2
  31. package/dist/plugin/interface.d.ts +76 -2
  32. package/dist/plugin/loader.d.ts +15 -1
  33. package/dist/plugin/loader.js +217 -70
  34. package/dist/plugin/pipeline.d.ts +75 -0
  35. package/dist/plugin/pipeline.js +183 -0
  36. package/dist/plugin/registry.js +3 -2
  37. package/dist/security/secrets.d.ts +1 -0
  38. package/dist/security/secrets.js +13 -0
  39. package/dist/sensors/actionability.js +1 -2
  40. package/dist/sensors/guard.js +4 -10
  41. package/dist/sensors/mutations.js +12 -0
  42. package/dist/sensors/operation_monitor.js +1 -0
  43. package/dist/server.d.ts +1 -1
  44. package/dist/server.js +428 -156
  45. package/dist/state/agent.d.ts +1 -0
  46. package/dist/state/agent.js +3 -0
  47. package/dist/state/delta.d.ts +12 -4
  48. package/dist/state/delta.js +67 -14
  49. package/dist/state/session.d.ts +6 -0
  50. package/dist/state/session.js +27 -0
  51. package/dist/tools/browser_detect.d.ts +16 -3
  52. package/dist/tools/browser_detect.js +50 -5
  53. package/dist/tools/burst.d.ts +17 -8
  54. package/dist/tools/burst.js +236 -77
  55. package/dist/tools/checkpoint.d.ts +38 -0
  56. package/dist/tools/checkpoint.js +271 -0
  57. package/dist/tools/click.d.ts +1 -0
  58. package/dist/tools/click.js +74 -70
  59. package/dist/tools/design_extract.js +1 -1
  60. package/dist/tools/eval.js +1 -1
  61. package/dist/tools/fill.d.ts +20 -1
  62. package/dist/tools/fill.js +199 -40
  63. package/dist/tools/find.d.ts +16 -0
  64. package/dist/tools/find.js +67 -44
  65. package/dist/tools/github/index.d.ts +19 -4
  66. package/dist/tools/github/index.js +40 -4
  67. package/dist/tools/highlight.d.ts +6 -2
  68. package/dist/tools/highlight.js +158 -72
  69. package/dist/tools/inspect.js +1 -1
  70. package/dist/tools/key.js +65 -56
  71. package/dist/tools/navigate.js +29 -34
  72. package/dist/tools/pdf.js +21 -13
  73. package/dist/tools/pointer.d.ts +18 -3
  74. package/dist/tools/pointer.js +89 -1
  75. package/dist/tools/read.d.ts +28 -8
  76. package/dist/tools/read.js +230 -56
  77. package/dist/tools/scrape.js +67 -13
  78. package/dist/tools/screenshot.js +21 -16
  79. package/dist/tools/scroll.js +6 -11
  80. package/dist/tools/select.js +8 -2
  81. package/dist/tools/snapshot.js +3 -34
  82. package/dist/tools/tabs.js +14 -6
  83. package/dist/tools/thought.d.ts +2 -0
  84. package/dist/tools/thought.js +13 -14
  85. package/dist/tools/type.d.ts +2 -0
  86. package/dist/tools/type.js +107 -58
  87. package/dist/tools/type_secret.js +42 -76
  88. package/dist/tools/upload.js +1 -1
  89. package/dist/tools/video_compiler.d.ts +1 -1
  90. package/dist/tools/video_compiler.js +11 -12
  91. package/dist/version.d.ts +5 -0
  92. package/dist/version.js +5 -0
  93. package/docs/DOCUMENTATION.md +21 -32
  94. package/docs/PLUGINS.md +121 -0
  95. package/docs/TOOLS.md +56 -0
  96. package/docs/architecture/blind-injection.md +83 -20
  97. package/docs/architecture/overview.md +49 -49
  98. package/docs/configuration/folders.md +55 -54
  99. package/docs/desktop-automation/electron-apps.md +217 -162
  100. package/docs/get-started/introduction.md +60 -60
  101. package/docs/intelligence/site-profiles.md +151 -0
  102. package/docs/tools/tools.md +631 -293
  103. package/docs/troubleshooting/trouble-loop.md +31 -31
  104. package/ext-dist/background.js +10 -62
  105. package/ext-dist/content.js +333 -920
  106. package/ext-dist/hud/hud.js +9 -9
  107. package/ext-dist/manifest.json +2 -6
  108. package/ext-dist/styles.css +6 -1
  109. package/mcp.json +101 -67
  110. package/package.json +5 -6
  111. package/server.json +3 -3
  112. package/ext-dist/sidepanel/sidepanel.js +0 -25
  113. package/ext-dist/sidepanel.html +0 -547
@@ -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) return JSON.stringify(pick(el));
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 { id, label, intent, backendNodeId: directNodeId, text, clearFirst, fast = false } = args;
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. Call browser_reset_loop_counter if this is a new task.` };
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
- const typePreview = text.slice(0, 20).replace(/'/g, "");
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 (coords.x !== undefined && coords.y !== undefined) {
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 : 100 + Math.random() * 100));
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 the text.
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 getFieldValue = async () => {
831
+ const checkFieldValue = async () => {
786
832
  const expression = isActiveElementRef
787
- ? `(function() {
833
+ ? `(function(expected) {
788
834
  var el = document.activeElement;
789
- if (!el) return null;
790
- return el.value !== undefined ? el.value : (el.textContent || null);
791
- })()`
792
- : `(function(sel) {
793
- var el = document.querySelector(sel);
794
- if (!el) return null;
795
- return el.value !== undefined ? el.value : (el.textContent || null);
796
- })(${JSON.stringify(selector)})`;
797
- const { result } = await session.Runtime.evaluate({ expression, returnByValue: true });
798
- return result?.value;
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
- const val = await getFieldValue();
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
- const retryVal = await getFieldValue();
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
- const finalVal = await getFieldValue();
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
- const retryVal = await getFieldValue();
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
- const pasteRetryVal = await getFieldValue();
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
- logAction("browser_type", resolvedRef, `[text] into ${resolvedLabel} (exec: ${executionMs}ms)`);
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 target" };
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 || el.id.endsWith(query.startsWith("w:") ? query.slice(2) : query));
76
- if (!target) {
77
- const matches = findAllMatches(map.elements, query);
78
- if (matches.length === 0) {
79
- return {
80
- success: false,
81
- error_code: "ELEMENT_NOT_FOUND",
82
- message: `[!] Could not find input matching "${query}"`,
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
- if (matches.length > 1) {
86
- return {
87
- success: false,
88
- error_code: "MULTIPLE_ELEMENTS_FOUND",
89
- message: `[!] Refusing to guess: found ${matches.length} matches for "${query}". Please use a more specific selector.`,
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
- const selector = target.selector || `[data-wt-id="${target.id}"]`;
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.Runtime.callFunctionOn({
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 finalRef = target.id.startsWith('w:') ? target.id : `w:${target.id}`;
212
- logAction("browser_type_secret", finalRef, `[secret injected: ${args.envKey}@${currentHostname}] into ${target.label} (exec: ${executionMs}ms)`);
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: target.label, envKey: args.envKey, domain: currentHostname },
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: target.label,
189
+ typed_into: targetLabel,
224
190
  ref: finalRef,
225
191
  key: args.envKey,
226
192
  domain: currentHostname,
@@ -93,7 +93,7 @@ export async function browserUpload(session, options, config) {
93
93
  }
94
94
  return {
95
95
  success: uploaded,
96
- message: `Successfully attached ${filePaths.length} file(s)`,
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 { exec, execSync } from "node:child_process";
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 cmd = process.platform === "win32" ? "where ffmpeg" : "which ffmpeg";
17
- execSync(cmd, { stdio: "ignore" });
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 codecFlags = "";
122
+ let ffmpegArgs = ["-y", "-f", "concat", "-safe", "0", "-i", concatFilePath];
124
123
  if (format === "webm") {
125
- codecFlags = "-c:v libvpx-vp9 -pix_fmt yuv420p -b:v 1M";
124
+ ffmpegArgs.push("-c:v", "libvpx-vp9", "-pix_fmt", "yuv420p", "-b:v", "1M");
126
125
  }
127
126
  else if (format === "mp4") {
128
- codecFlags = "-c:v libx264 -pix_fmt yuv420p -vf \"trunc(iw/2)*2:trunc(ih/2)*2\"";
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
- codecFlags = `-vf "fps=${fps},scale=640:-1:flags=lanczos"`;
130
+ ffmpegArgs.push("-vf", `fps=${fps},scale=640:-1:flags=lanczos`);
132
131
  }
133
132
  else {
134
- codecFlags = "-c:v libvpx-vp9 -pix_fmt yuv420p -b:v 1M";
133
+ ffmpegArgs.push("-c:v", "libvpx-vp9", "-pix_fmt", "yuv420p", "-b:v", "1M");
135
134
  }
136
- const cmd = `ffmpeg -y -f concat -safe 0 -i "${concatFilePath}" ${codecFlags} "${videoPath}"`;
137
- exec(cmd, { cwd: recordingDir }, (error) => {
135
+ ffmpegArgs.push(videoPath);
136
+ execFile("ffmpeg", ffmpegArgs, { cwd: recordingDir }, (error) => {
138
137
  try {
139
138
  fs.unlinkSync(concatFilePath);
140
139
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Weavetab Version Constant
3
+ * Single source of truth for runtime versioning.
4
+ */
5
+ export declare const WEAVETAB_VERSION = "2.5.0-beta.4";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Weavetab Version Constant
3
+ * Single source of truth for runtime versioning.
4
+ */
5
+ export const WEAVETAB_VERSION = "2.5.0-beta.4";