@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.
Files changed (127) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +6 -6
  3. package/LICENSE +216 -631
  4. package/README.md +153 -1074
  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 +10 -6
  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.d.ts +1 -0
  17. package/dist/cli/plugin.js +81 -18
  18. package/dist/cli/updater.d.ts +1 -1
  19. package/dist/cli/updater.js +27 -7
  20. package/dist/config/loader.d.ts +9 -1
  21. package/dist/config/loader.js +42 -3
  22. package/dist/desktop/discovery.d.ts +44 -0
  23. package/dist/desktop/discovery.js +364 -0
  24. package/dist/index.js +137 -21
  25. package/dist/intelligence/marks.d.ts +36 -0
  26. package/dist/intelligence/marks.js +30 -0
  27. package/dist/intelligence/patternCompiler.d.ts +118 -0
  28. package/dist/intelligence/patternCompiler.js +259 -0
  29. package/dist/intelligence/sessionMemory.d.ts +2 -0
  30. package/dist/intelligence/sessionMemory.js +18 -8
  31. package/dist/overlay/injector.js +2 -2
  32. package/dist/plugin/interface.d.ts +88 -5
  33. package/dist/plugin/loader.d.ts +12 -1
  34. package/dist/plugin/loader.js +280 -70
  35. package/dist/plugin/pipeline.d.ts +75 -0
  36. package/dist/plugin/pipeline.js +183 -0
  37. package/dist/plugin/registry.d.ts +2 -1
  38. package/dist/plugin/registry.js +24 -14
  39. package/dist/security/secrets.d.ts +7 -0
  40. package/dist/security/secrets.js +37 -0
  41. package/dist/sensors/actionability.js +1 -2
  42. package/dist/sensors/guard.js +4 -10
  43. package/dist/sensors/mutations.js +12 -0
  44. package/dist/sensors/operation_monitor.js +1 -0
  45. package/dist/server.d.ts +1 -1
  46. package/dist/server.js +453 -178
  47. package/dist/state/agent.d.ts +1 -0
  48. package/dist/state/agent.js +3 -0
  49. package/dist/state/delta.d.ts +12 -4
  50. package/dist/state/delta.js +67 -14
  51. package/dist/state/session.d.ts +6 -0
  52. package/dist/state/session.js +27 -0
  53. package/dist/tools/browser_detect.d.ts +16 -3
  54. package/dist/tools/browser_detect.js +52 -7
  55. package/dist/tools/burst.d.ts +17 -8
  56. package/dist/tools/burst.js +236 -77
  57. package/dist/tools/captcha.js +1 -1
  58. package/dist/tools/checkpoint.d.ts +38 -0
  59. package/dist/tools/checkpoint.js +271 -0
  60. package/dist/tools/click.d.ts +1 -0
  61. package/dist/tools/click.js +74 -70
  62. package/dist/tools/design_extract.js +1 -1
  63. package/dist/tools/eval.js +1 -1
  64. package/dist/tools/fill.d.ts +20 -1
  65. package/dist/tools/fill.js +199 -40
  66. package/dist/tools/find.d.ts +16 -0
  67. package/dist/tools/find.js +67 -44
  68. package/dist/tools/github/index.d.ts +19 -4
  69. package/dist/tools/github/index.js +40 -4
  70. package/dist/tools/highlight.d.ts +6 -2
  71. package/dist/tools/highlight.js +158 -72
  72. package/dist/tools/inspect.js +1 -1
  73. package/dist/tools/key.js +65 -56
  74. package/dist/tools/navigate.d.ts +1 -1
  75. package/dist/tools/navigate.js +34 -39
  76. package/dist/tools/pdf.js +21 -13
  77. package/dist/tools/pointer.d.ts +18 -3
  78. package/dist/tools/pointer.js +89 -1
  79. package/dist/tools/read.d.ts +28 -8
  80. package/dist/tools/read.js +230 -56
  81. package/dist/tools/scrape.js +67 -13
  82. package/dist/tools/screenshot.js +21 -16
  83. package/dist/tools/scroll.js +6 -11
  84. package/dist/tools/select.js +8 -2
  85. package/dist/tools/snapshot.js +3 -34
  86. package/dist/tools/tabs.js +15 -7
  87. package/dist/tools/thought.d.ts +2 -0
  88. package/dist/tools/thought.js +13 -14
  89. package/dist/tools/type.d.ts +2 -0
  90. package/dist/tools/type.js +107 -58
  91. package/dist/tools/type_secret.js +42 -76
  92. package/dist/tools/upload.js +1 -1
  93. package/dist/tools/video_compiler.d.ts +1 -1
  94. package/dist/tools/video_compiler.js +11 -12
  95. package/dist/version.d.ts +5 -0
  96. package/dist/version.js +5 -0
  97. package/docs/DOCUMENTATION.md +37 -41
  98. package/docs/PLUGINS.md +121 -0
  99. package/docs/TOOLS.md +54 -0
  100. package/docs/architecture/blind-injection.md +83 -20
  101. package/docs/architecture/overview.md +49 -49
  102. package/docs/architecture/security.md +8 -7
  103. package/docs/architecture/transport.md +2 -2
  104. package/docs/cli/cli-commands.md +7 -1
  105. package/docs/cli/cli-flags.md +15 -7
  106. package/docs/cli/cli-scripting.md +6 -3
  107. package/docs/configuration/config-locations.md +3 -4
  108. package/docs/configuration/env-variables.md +4 -4
  109. package/docs/configuration/folders.md +55 -54
  110. package/docs/desktop-automation/electron-apps.md +214 -162
  111. package/docs/get-started/introduction.md +60 -60
  112. package/docs/get-started/setup-wizard.md +7 -6
  113. package/docs/intelligence/site-profiles.md +151 -0
  114. package/docs/tools/tools.md +631 -293
  115. package/docs/troubleshooting/trouble-browser.md +1 -1
  116. package/docs/troubleshooting/trouble-loop.md +31 -31
  117. package/ext-dist/background.js +10 -62
  118. package/ext-dist/content.js +333 -920
  119. package/ext-dist/hud/hud.js +9 -9
  120. package/ext-dist/manifest.json +2 -6
  121. package/ext-dist/styles.css +6 -1
  122. package/icon.png +0 -0
  123. package/mcp.json +87 -71
  124. package/package.json +118 -119
  125. package/server.json +5 -5
  126. package/ext-dist/sidepanel/sidepanel.js +0 -25
  127. package/ext-dist/sidepanel.html +0 -547
@@ -1,18 +1,22 @@
1
1
  /**
2
2
  * Weavetab — tools/highlight.ts
3
- * browser_highlight: draws a CSS shimmer animation over the element that auto-fades.
4
- * Falls back through 5 strategies so w:NN refs always resolve.
3
+ * browser_highlight: draws visual consideration, target lock, danger/success or shimmer overlays.
4
+ * Supports multiple candidate refs, custom styles, floating labels, and multi-tier fallbacks.
5
5
  */
6
6
  import CDP from "chrome-remote-interface";
7
7
  import type { Config } from "../config/loader.js";
8
+ export type HighlightStyle = "consideration" | "target" | "danger" | "success" | "shimmer";
8
9
  export interface HighlightOptions {
9
10
  selector?: string;
10
11
  id?: string;
11
12
  ref?: string;
13
+ refs?: string[];
12
14
  target?: string;
13
15
  label?: string;
14
16
  nodeId?: number;
15
17
  color?: string;
18
+ style?: HighlightStyle;
19
+ duration?: number;
16
20
  }
17
21
  export declare function browserHighlight(session: CDP.Client, options: HighlightOptions, _config: Config): Promise<{
18
22
  success: boolean;
@@ -1,31 +1,95 @@
1
1
  import { getConfigAgentId } from "../config/loader.js";
2
2
  export async function browserHighlight(session, options, _config) {
3
- const { selector, nodeId } = options;
4
- // Inject shimmer keyframe once
3
+ const style = options.style || (options.refs && options.refs.length > 0 ? "consideration" : "shimmer");
4
+ const defaultDuration = style === "shimmer" ? 600 : 2000;
5
+ const duration = typeof options.duration === "number" && options.duration > 0 ? options.duration : defaultDuration;
6
+ const customLabel = options.label || "";
7
+ const customColor = options.color;
8
+ // Inject required keyframes once
5
9
  await session.Runtime.evaluate({
6
10
  expression: `(function(){
7
- if (!document.getElementById('wt-shimmer-style')) {
11
+ if (!document.getElementById('wt-highlight-styles')) {
8
12
  var s = document.createElement('style');
9
- s.id = 'wt-shimmer-style';
10
- s.textContent = '@keyframes wt-shimmer{0%{opacity:0;background-position:-200% 0}15%{opacity:0.9}40%{opacity:0.7}85%{opacity:0.3}100%{opacity:0;background-position:200% 0}}';
13
+ s.id = 'wt-highlight-styles';
14
+ s.textContent = \`
15
+ @keyframes wt-shimmer {
16
+ 0% { opacity: 0; background-position: -200% 0; }
17
+ 15% { opacity: 0.9; }
18
+ 40% { opacity: 0.7; }
19
+ 85% { opacity: 0.3; }
20
+ 100% { opacity: 0; background-position: 200% 0; }
21
+ }
22
+ @keyframes wt-consider-pulse {
23
+ 0%, 100% { box-shadow: 0 0 0 1px rgba(56,189,248,0.7), 0 0 14px rgba(56,189,248,0.35); opacity: 0.92; }
24
+ 50% { box-shadow: 0 0 0 2px rgba(56,189,248,0.95), 0 0 22px rgba(56,189,248,0.6); opacity: 1; }
25
+ }
26
+ @keyframes wt-target-lock {
27
+ 0% { transform: scale(1.05); opacity: 0.5; }
28
+ 100% { transform: scale(1); opacity: 1; }
29
+ }
30
+ @keyframes wt-danger-pulse {
31
+ 0%, 100% { box-shadow: 0 0 0 1px rgba(239,68,68,0.7), 0 0 14px rgba(239,68,68,0.35); opacity: 0.92; }
32
+ 50% { box-shadow: 0 0 0 2px rgba(239,68,68,0.95), 0 0 22px rgba(239,68,68,0.6); opacity: 1; }
33
+ }
34
+ @keyframes wt-success-glow {
35
+ 0% { opacity: 0.6; box-shadow: 0 0 0 1px rgba(16,185,129,0.7); }
36
+ 100% { opacity: 1; box-shadow: 0 0 0 2px rgba(16,185,129,0.9), 0 0 18px rgba(16,185,129,0.45); }
37
+ }
38
+ \`;
11
39
  (document.head || document.documentElement).appendChild(s);
12
40
  }
13
41
  })()`,
14
42
  returnByValue: true,
15
43
  }).catch(() => { });
16
- // Draw shimmer overlay at raw coordinates — works without any DOM query
17
- function makeCoordShimmer(x, y, w, h) {
44
+ function getOverlayCss(styleName, durMs, colorOverride) {
45
+ if (colorOverride) {
46
+ return `border:2px solid ${colorOverride};box-shadow:0 0 14px ${colorOverride};background:${colorOverride}1a;border-radius:4px;transition:opacity 0.2s ease-out;`;
47
+ }
48
+ switch (styleName) {
49
+ case "consideration":
50
+ return `border:1.5px solid rgba(56,189,248,0.9);background:rgba(56,189,248,0.08);box-shadow:0 0 14px rgba(56,189,248,0.4);border-radius:4px;animation:wt-consider-pulse 1.4s ease-in-out infinite;`;
51
+ case "target":
52
+ return `border:2px solid rgba(245,158,11,0.95);background:rgba(245,158,11,0.12);box-shadow:0 0 16px rgba(245,158,11,0.45);border-radius:4px;animation:wt-target-lock 0.2s cubic-bezier(0.16,1,0.3,1);`;
53
+ case "danger":
54
+ return `border:1.5px solid rgba(239,68,68,0.95);background:rgba(239,68,68,0.1);box-shadow:0 0 16px rgba(239,68,68,0.45);border-radius:4px;animation:wt-danger-pulse 1.4s ease-in-out infinite;`;
55
+ case "success":
56
+ return `border:1.5px solid rgba(16,185,129,0.95);background:rgba(16,185,129,0.1);box-shadow:0 0 16px rgba(16,185,129,0.45);border-radius:4px;animation:wt-success-glow 0.3s ease-out forwards;`;
57
+ case "shimmer":
58
+ default:
59
+ return `background:linear-gradient(120deg,transparent 30%,rgba(139,92,246,0.3) 50%,transparent 70%);background-size:200% 100%;animation:wt-shimmer ${durMs / 1000}s ease-out forwards;border:1.5px solid rgba(139,92,246,0.6);border-radius:3px;box-shadow:0 0 8px rgba(139,92,246,0.25);`;
60
+ }
61
+ }
62
+ // Draw overlay at raw coordinates
63
+ function makeCoordOverlay(x, y, w, h, badgeLabel) {
64
+ const styleCss = getOverlayCss(style, duration, customColor);
65
+ const escapedBadge = badgeLabel ? badgeLabel.replace(/"/g, '&quot;') : '';
66
+ const badgeHtml = escapedBadge
67
+ ? `<span style="position:absolute;top:-20px;left:0;background:#09090b;border:1px solid #27272a;color:#f4f4f5;font-size:10px;font-family:-apple-system,BlinkMacSystemFont,\\'Segoe UI\\',sans-serif;font-weight:500;padding:1px 6px;border-radius:4px;white-space:nowrap;pointer-events:none;box-shadow:0 2px 6px rgba(0,0,0,0.5);">${escapedBadge}</span>`
68
+ : '';
18
69
  return `(function(){
19
70
  var div = document.createElement('div');
20
- div.style.cssText = 'position:fixed;left:${x}px;top:${y}px;width:${w}px;height:${h}px;z-index:2147483647;pointer-events:none;background:linear-gradient(120deg,transparent 30%,rgba(139,92,246,0.3) 50%,transparent 70%);background-size:200% 100%;animation:wt-shimmer 0.5s ease-out forwards;border:1.5px solid rgba(139,92,246,0.6);border-radius:3px;box-shadow:0 0 8px rgba(139,92,246,0.25);';
71
+ div.setAttribute('data-wt-ignore', 'true');
72
+ div.style.cssText = 'position:fixed;left:${x}px;top:${y}px;width:${w}px;height:${h}px;z-index:2147483647;pointer-events:none;${styleCss}';
73
+ if (${JSON.stringify(badgeHtml)}) {
74
+ div.innerHTML = ${JSON.stringify(badgeHtml)};
75
+ }
21
76
  (document.getElementById('Weavetab-root')?.shadowRoot || document.getElementById('Weavetab-root') || document.body).appendChild(div);
22
- setTimeout(function(){ if(div.parentNode) div.parentNode.removeChild(div); }, 600);
77
+ setTimeout(function(){
78
+ div.style.transition = 'opacity 0.2s ease-out';
79
+ div.style.opacity = '0';
80
+ setTimeout(function(){ if(div.parentNode) div.parentNode.removeChild(div); }, 220);
81
+ }, ${duration});
23
82
  return true;
24
83
  })()`;
25
84
  }
26
- // Draw shimmer by querying a CSS selector
27
- function makeSelectorShimmer(sel) {
85
+ // Draw overlay by querying selector
86
+ function makeSelectorOverlay(sel, badgeLabel) {
28
87
  const escaped = sel.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
88
+ const styleCss = getOverlayCss(style, duration, customColor);
89
+ const escapedBadge = badgeLabel ? badgeLabel.replace(/"/g, '&quot;') : '';
90
+ const badgeHtml = escapedBadge
91
+ ? `<span style="position:absolute;top:-20px;left:0;background:#09090b;border:1px solid #27272a;color:#f4f4f5;font-size:10px;font-family:-apple-system,BlinkMacSystemFont,\\'Segoe UI\\',sans-serif;font-weight:500;padding:1px 6px;border-radius:4px;white-space:nowrap;pointer-events:none;box-shadow:0 2px 6px rgba(0,0,0,0.5);">${escapedBadge}</span>`
92
+ : '';
29
93
  return `(function(){
30
94
  var el;
31
95
  try { el = document.querySelector('${escaped}'); } catch(e) {}
@@ -36,19 +100,26 @@ export async function browserHighlight(session, options, _config) {
36
100
  var r = el.getBoundingClientRect();
37
101
  if (r.width === 0 && r.height === 0) return false;
38
102
  var div = document.createElement('div');
39
- div.style.cssText = 'position:fixed;left:'+r.left+'px;top:'+r.top+'px;width:'+r.width+'px;height:'+r.height+'px;z-index:2147483647;pointer-events:none;background:linear-gradient(120deg,transparent 30%,rgba(139,92,246,0.3) 50%,transparent 70%);background-size:200% 100%;animation:wt-shimmer 0.5s ease-out forwards;border:1.5px solid rgba(139,92,246,0.6);border-radius:3px;box-shadow:0 0 8px rgba(139,92,246,0.25);';
103
+ div.setAttribute('data-wt-ignore', 'true');
104
+ div.style.cssText = 'position:fixed;left:'+r.left+'px;top:'+r.top+'px;width:'+r.width+'px;height:'+r.height+'px;z-index:2147483647;pointer-events:none;${styleCss}';
105
+ if (${JSON.stringify(badgeHtml)}) {
106
+ div.innerHTML = ${JSON.stringify(badgeHtml)};
107
+ }
40
108
  (document.getElementById('Weavetab-root')?.shadowRoot || document.getElementById('Weavetab-root') || document.body).appendChild(div);
41
- setTimeout(function(){ if(div.parentNode) div.parentNode.removeChild(div); }, 600);
109
+ setTimeout(function(){
110
+ div.style.transition = 'opacity 0.2s ease-out';
111
+ div.style.opacity = '0';
112
+ setTimeout(function(){ if(div.parentNode) div.parentNode.removeChild(div); }, 220);
113
+ }, ${duration});
42
114
  return true;
43
115
  })()`;
44
116
  }
45
- async function trySelector(sel) {
46
- const r = await session.Runtime.evaluate({ expression: makeSelectorShimmer(sel), returnByValue: true })
117
+ async function trySelector(sel, badgeLabel) {
118
+ const r = await session.Runtime.evaluate({ expression: makeSelectorOverlay(sel, badgeLabel), returnByValue: true })
47
119
  .catch(() => ({ result: { value: false } }));
48
120
  return !!r?.result?.value;
49
121
  }
50
- // Use CDP DOM.getBoxModel to get coordinates for a backendNodeId — no DOM query needed
51
- async function tryNodeHighlight(bnId) {
122
+ async function tryNodeHighlight(bnId, badgeLabel) {
52
123
  try {
53
124
  const { nodeIds } = await session.DOM.pushNodesByBackendIdsToFrontend({ backendNodeIds: [bnId] });
54
125
  if (!nodeIds?.[0])
@@ -60,81 +131,96 @@ export async function browserHighlight(session, options, _config) {
60
131
  const x = q[0], y = q[1], w = q[2] - q[0], h = q[5] - q[1];
61
132
  if (w <= 0 || h <= 0)
62
133
  return false;
63
- const r = await session.Runtime.evaluate({ expression: makeCoordShimmer(x, y, w, h), returnByValue: true });
134
+ const r = await session.Runtime.evaluate({ expression: makeCoordOverlay(x, y, w, h, badgeLabel), returnByValue: true });
64
135
  return !!r?.result?.value;
65
136
  }
66
137
  catch {
67
138
  return false;
68
139
  }
69
140
  }
70
- // ── Resolve params ─────────────────────────────────────────────────────────
71
- let resolvedSelector = selector;
72
- let numericId = null;
73
- if (!resolvedSelector) {
74
- const refId = options.id || options.ref || options.target;
75
- if (refId) {
76
- const stripped = refId.replace(/^w:/, '');
141
+ async function highlightSingleTarget(targetIdentifier, badge) {
142
+ let sel = targetIdentifier;
143
+ let numericId = null;
144
+ if (targetIdentifier.startsWith("w:")) {
145
+ const stripped = targetIdentifier.slice(2);
77
146
  numericId = parseInt(stripped, 10);
78
- resolvedSelector = `[data-wt-id="${stripped}"]`;
147
+ sel = `[data-wt-id="${stripped}"]`;
79
148
  }
80
- else if (options.label) {
81
- resolvedSelector = `[aria-label="${options.label.replace(/"/g, '\\"')}"]`;
82
- }
83
- }
84
- // Strategy 1: CSS selector query
85
- if (resolvedSelector && await trySelector(resolvedSelector)) {
86
- return { success: true, message: `Highlighted ${resolvedSelector}` };
87
- }
88
- // Strategy 2: Extension oracle coords (__Weavetab_getCoords)
89
- if (resolvedSelector) {
149
+ if (await trySelector(sel, badge))
150
+ return true;
151
+ // Extension oracle coords fallback
90
152
  const coordsRes = await session.Runtime.evaluate({
91
- expression: `(window.__Weavetab_getCoords || window.__Weavetab_getCoords || function(){return null;})(${JSON.stringify(resolvedSelector)})`,
153
+ expression: `(window.__Weavetab_getCoords || function(){return null;})(${JSON.stringify(sel)})`,
92
154
  returnByValue: true,
93
155
  }).catch(() => null);
94
156
  const cv = coordsRes?.result?.value;
95
157
  if (cv?.raw) {
96
158
  const { left, top, w, h } = cv.raw;
97
- const r = await session.Runtime.evaluate({ expression: makeCoordShimmer(left, top, w, h), returnByValue: true });
159
+ const r = await session.Runtime.evaluate({ expression: makeCoordOverlay(left, top, w, h, badge), returnByValue: true });
98
160
  if (r?.result?.value)
99
- return { success: true, message: `Highlighted ${resolvedSelector} via coords` };
161
+ return true;
100
162
  }
101
- }
102
- // Strategy 3: CDP DOM by backendNodeId (for explicit nodeId param)
103
- if (nodeId && await tryNodeHighlight(nodeId)) {
104
- return { success: true, message: `Highlighted backendNodeId ${nodeId}` };
105
- }
106
- // Strategy 4: Walk action map cache to find the backendNodeId for a w:NN ref
107
- if (numericId !== null) {
108
- try {
109
- const { getCachedActionMap } = await import('../state/cache.js');
110
- const { sessionRegistry } = await import('../state/session.js');
111
- const activeClient = sessionRegistry.getActive(getConfigAgentId(_config));
112
- const targetId = activeClient?.targetId || 'default';
113
- const cachedMap = activeClient?.url ? getCachedActionMap(targetId, activeClient.url) : null;
114
- if (cachedMap?.elements) {
115
- const el = cachedMap.elements.find(e => {
116
- const n = parseInt(String(e.id).replace(/^w:/, ''), 10);
117
- return n === numericId;
118
- });
119
- if (el?.selector && await trySelector(el.selector)) {
120
- return { success: true, message: `Highlighted w:${numericId} via cache selector` };
121
- }
122
- if (el?.backendNodeId && await tryNodeHighlight(el.backendNodeId)) {
123
- return { success: true, message: `Highlighted w:${numericId} via action map backendNodeId` };
124
- }
125
- // Strategy 5: coordinate fallback from cached element position
126
- if (el?.x !== undefined && el?.y !== undefined) {
127
- const ew = el.width ?? 80, eh = el.height ?? 24;
128
- const r = await session.Runtime.evaluate({
129
- expression: makeCoordShimmer(el.x - ew / 2, el.y - eh / 2, ew, eh),
130
- returnByValue: true,
163
+ // Action map cache fallback
164
+ if (numericId !== null) {
165
+ try {
166
+ const { getCachedActionMap } = await import('../state/cache.js');
167
+ const { sessionRegistry } = await import('../state/session.js');
168
+ const activeClient = sessionRegistry.getActive(getConfigAgentId(_config));
169
+ const targetId = activeClient?.targetId || 'default';
170
+ const cachedMap = activeClient?.url ? getCachedActionMap(targetId, activeClient.url) : null;
171
+ if (cachedMap?.elements) {
172
+ const el = cachedMap.elements.find(e => {
173
+ const n = parseInt(String(e.id).replace(/^w:/, ''), 10);
174
+ return n === numericId;
131
175
  });
132
- if (r?.result?.value)
133
- return { success: true, message: `Highlighted w:${numericId} via cache coords` };
176
+ if (el?.selector && await trySelector(el.selector, badge))
177
+ return true;
178
+ if (el?.backendNodeId && await tryNodeHighlight(el.backendNodeId, badge))
179
+ return true;
180
+ if (el?.x !== undefined && el?.y !== undefined) {
181
+ const ew = el.width ?? 80, eh = el.height ?? 24;
182
+ const r = await session.Runtime.evaluate({
183
+ expression: makeCoordOverlay(el.x - ew / 2, el.y - eh / 2, ew, eh, badge),
184
+ returnByValue: true,
185
+ });
186
+ if (r?.result?.value)
187
+ return true;
188
+ }
134
189
  }
135
190
  }
191
+ catch { /* ignore */ }
192
+ }
193
+ return false;
194
+ }
195
+ // ── Multi-ref consideration overlay ───────────────────────────────────────
196
+ if (options.refs && Array.isArray(options.refs) && options.refs.length > 0) {
197
+ let successCount = 0;
198
+ for (let i = 0; i < options.refs.length; i++) {
199
+ const refItem = options.refs[i];
200
+ const itemBadge = customLabel ? (options.refs.length > 1 ? `${customLabel} [${i + 1}]` : customLabel) : refItem;
201
+ const ok = await highlightSingleTarget(refItem, itemBadge);
202
+ if (ok)
203
+ successCount++;
204
+ }
205
+ return {
206
+ success: successCount > 0,
207
+ message: `Highlighted ${successCount}/${options.refs.length} element(s) with style '${style}' (${duration}ms)`
208
+ };
209
+ }
210
+ // ── Single target resolution ──────────────────────────────────────────────
211
+ if (options.nodeId && await tryNodeHighlight(options.nodeId, customLabel)) {
212
+ return { success: true, message: `Highlighted backendNodeId ${options.nodeId} with style '${style}'` };
213
+ }
214
+ let singleRef = options.selector || options.id || options.ref || options.target;
215
+ if (!singleRef && options.label) {
216
+ singleRef = `[aria-label="${options.label.replace(/"/g, '\\"')}"]`;
217
+ }
218
+ if (singleRef) {
219
+ const badgeText = customLabel || (singleRef.startsWith("w:") ? singleRef : undefined);
220
+ const ok = await highlightSingleTarget(singleRef, badgeText);
221
+ if (ok) {
222
+ return { success: true, message: `Highlighted ${singleRef} with style '${style}' (${duration}ms)` };
136
223
  }
137
- catch { /* no cache */ }
138
224
  }
139
225
  return { success: false, message: `Could not find element to highlight` };
140
226
  }
@@ -121,5 +121,5 @@ export async function browserInspect(session, options, _config) {
121
121
  computedStyles: computedStylesData,
122
122
  eventListeners: listeners
123
123
  };
124
- return { success: true, data, message: "Inspected element successfully" };
124
+ return { success: true, data, message: "inspected element" };
125
125
  }
package/dist/tools/key.js CHANGED
@@ -4,11 +4,8 @@
4
4
  * Weavetab by fy2ne
5
5
  */
6
6
  import { recordAction } from "../intelligence/trail.js";
7
- import { waitForDOMStable } from "../sensors/guard.js";
8
- import { waitForNetworkIdle } from "../sensors/wait.js";
9
7
  import { buildActionMap, toRefEntries } from "../cdp/walker.js";
10
- import { cacheActionMap, cacheBoxModel } from "../state/cache.js";
11
- import { computeDelta, storeSnapshot } from "../state/delta.js";
8
+ import { computeDelta } from "../state/delta.js";
12
9
  import { sessionRegistry } from "../state/session.js";
13
10
  import { getConfigAgentId } from "../config/loader.js";
14
11
  import { sendToExtension } from "../cdp/helpers.js";
@@ -20,36 +17,74 @@ const ALLOWED_KEYS = new Set([
20
17
  ]);
21
18
  export async function weaveKey(session, args, _config) {
22
19
  const { key, modifiers } = args;
20
+ let effectiveKey = key.trim();
21
+ const effectiveModifiers = [...(modifiers || [])];
22
+ // Auto-parse combo strings e.g. "Ctrl+A", "Control+Shift+P", "Cmd+K", "Alt+ArrowDown"
23
+ if (effectiveKey.includes("+")) {
24
+ const parts = effectiveKey.split("+").map(p => p.trim());
25
+ effectiveKey = parts[parts.length - 1] || "Enter";
26
+ for (let i = 0; i < parts.length - 1; i++) {
27
+ const mod = parts[i].toLowerCase();
28
+ if ((mod === "ctrl" || mod === "control") && !effectiveModifiers.includes("Ctrl"))
29
+ effectiveModifiers.push("Ctrl");
30
+ else if (mod === "shift" && !effectiveModifiers.includes("Shift"))
31
+ effectiveModifiers.push("Shift");
32
+ else if ((mod === "alt" || mod === "option") && !effectiveModifiers.includes("Alt"))
33
+ effectiveModifiers.push("Alt");
34
+ else if ((mod === "meta" || mod === "cmd" || mod === "command" || mod === "win") && !effectiveModifiers.includes("Meta"))
35
+ effectiveModifiers.push("Meta");
36
+ }
37
+ }
38
+ // Capitalize common key names if needed (e.g. "enter" -> "Enter", "escape" -> "Escape", "tab" -> "Tab")
39
+ const keyMap = {
40
+ enter: "Enter",
41
+ escape: "Escape",
42
+ esc: "Escape",
43
+ tab: "Tab",
44
+ backspace: "Backspace",
45
+ delete: "Delete",
46
+ space: "Space",
47
+ arrowup: "ArrowUp",
48
+ arrowdown: "ArrowDown",
49
+ arrowleft: "ArrowLeft",
50
+ arrowright: "ArrowRight",
51
+ up: "ArrowUp",
52
+ down: "ArrowDown",
53
+ left: "ArrowLeft",
54
+ right: "ArrowRight",
55
+ home: "Home",
56
+ end: "End",
57
+ pageup: "PageUp",
58
+ pagedown: "PageDown",
59
+ };
60
+ if (keyMap[effectiveKey.toLowerCase()]) {
61
+ effectiveKey = keyMap[effectiveKey.toLowerCase()];
62
+ }
23
63
  // Allow single printable chars or control keys from the allowlist
24
- if (key.length > 1 && !ALLOWED_KEYS.has(key)) {
64
+ if (effectiveKey.length > 1 && !ALLOWED_KEYS.has(effectiveKey)) {
25
65
  return {
26
66
  success: false,
27
67
  error_code: "INVALID_KEY",
28
- message: `[!] Key "${key}" is not a valid control key. Use single characters or: ${[...ALLOWED_KEYS].join(", ")}`,
68
+ message: `[!] Key "${effectiveKey}" is not a valid control key. Use single characters or: ${[...ALLOWED_KEYS].join(", ")}`,
29
69
  };
30
70
  }
31
71
  let modMask = 0;
32
- if (modifiers) {
33
- if (modifiers.includes("Alt"))
34
- modMask |= 1;
35
- if (modifiers.includes("Ctrl"))
36
- modMask |= 2;
37
- if (modifiers.includes("Meta"))
38
- modMask |= 4;
39
- if (modifiers.includes("Shift"))
40
- modMask |= 8;
41
- }
42
- const label = modifiers && modifiers.length > 0 ? `${modifiers.join("+")}+${key}` : key;
72
+ if (effectiveModifiers.includes("Alt"))
73
+ modMask |= 1;
74
+ if (effectiveModifiers.includes("Ctrl"))
75
+ modMask |= 2;
76
+ if (effectiveModifiers.includes("Meta"))
77
+ modMask |= 4;
78
+ if (effectiveModifiers.includes("Shift"))
79
+ modMask |= 8;
80
+ const label = effectiveModifiers.length > 0 ? `${effectiveModifiers.join("+")}+${effectiveKey}` : effectiveKey;
43
81
  await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `pressing ${label}` } });
44
- const { frameTree: beforeFrameTree } = await session.Page.getFrameTree();
82
+ const { frameTree: beforeFrameTree } = await session.Page.getFrameTree().catch(() => ({ frameTree: { frame: { url: '' } } }));
45
83
  const urlBefore = beforeFrameTree.frame.url;
46
- // Track map before for delta if Enter is pressed
47
84
  let targetId = "default";
48
85
  const active = sessionRegistry.getActive(getConfigAgentId(_config));
49
86
  if (active)
50
87
  targetId = active.targetId;
51
- const mapBefore = await buildActionMap(session, { lite: true }, targetId);
52
- storeSnapshot(targetId, urlBefore, toRefEntries(mapBefore.elements));
53
88
  // We removed the direct toast injection here because the extension handles HUD display via Weavetab_STATE (line 66)
54
89
  await session.Input.dispatchKeyEvent({ type: "rawKeyDown", key, modifiers: modMask, windowsVirtualKeyCode: key === "Enter" ? 13 : undefined, nativeVirtualKeyCode: key === "Enter" ? 13 : undefined });
55
90
  if (key === "Enter") {
@@ -92,49 +127,23 @@ export async function weaveKey(session, args, _config) {
92
127
  })();
93
128
  ` }).catch(() => { });
94
129
  }
95
- await session.Input.dispatchKeyEvent({ type: "keyUp", key, modifiers: modMask, windowsVirtualKeyCode: key === "Enter" ? 13 : undefined, nativeVirtualKeyCode: key === "Enter" ? 13 : undefined });
96
- // ── Post-Action Pipeline & Predictive Action Queue ────────────────────────
97
- const predictiveQueue = async () => {
98
- try {
99
- const nextMap = await buildActionMap(session, { lite: true }, targetId);
100
- cacheActionMap(targetId, nextMap);
101
- for (const el of nextMap.elements.slice(0, 10)) {
102
- if (el.backendNodeId) {
103
- const { nodeIds } = await session.DOM.pushNodesByBackendIdsToFrontend({ backendNodeIds: [el.backendNodeId] }).catch(() => ({ nodeIds: [] }));
104
- if (nodeIds[0]) {
105
- const box = await session.DOM.getBoxModel({ nodeId: nodeIds[0] }).catch(() => null);
106
- if (box)
107
- cacheBoxModel(targetId, el.backendNodeId, box.model);
108
- }
109
- }
110
- }
111
- }
112
- catch { /* ignore pre-fetch errors */ }
113
- };
114
- const settleTimeout = key === "Enter" ? 5000 : 2500;
115
- await Promise.all([
116
- waitForDOMStable(session, 50, settleTimeout),
117
- waitForNetworkIdle(session, 50, settleTimeout).catch(() => { }),
118
- predictiveQueue()
119
- ]);
130
+ await session.Input.dispatchKeyEvent({ type: "keyUp", key: effectiveKey, modifiers: modMask, windowsVirtualKeyCode: effectiveKey === "Enter" ? 13 : undefined, nativeVirtualKeyCode: effectiveKey === "Enter" ? 13 : undefined });
131
+ // Fast return for non-submission keys (Tab, Escape, Arrows, Shortcuts)
132
+ if (effectiveKey === "Enter") {
133
+ await new Promise(r => setTimeout(r, 60));
134
+ }
135
+ else {
136
+ await new Promise(r => setTimeout(r, 15));
137
+ }
120
138
  // Post-key location context
121
139
  let postUrl = "";
122
140
  let postTitle = "";
123
141
  try {
124
142
  const { frameTree } = await session.Page.getFrameTree();
125
143
  postUrl = frameTree.frame.url;
126
- try {
127
- const titleRes = await session.Runtime.evaluate({ expression: 'document.title', returnByValue: true });
128
- postTitle = titleRes?.result?.value || postUrl;
129
- }
130
- catch {
131
- postTitle = postUrl;
132
- }
133
144
  const activeSession = sessionRegistry.getActive(getConfigAgentId(_config));
134
- if (activeSession) {
145
+ if (activeSession)
135
146
  activeSession.url = postUrl;
136
- activeSession.title = postTitle;
137
- }
138
147
  }
139
148
  catch { /* ignore */ }
140
149
  const urlChanged = postUrl !== urlBefore;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Weavetab — tools/navigate.ts
2
+ * Weavetab — tools/navigate.ts
3
3
  * URL navigation with domain blocklist enforcement and post-navigation DOM stability wait.
4
4
  * Weavetab by fy2ne
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Weavetab — tools/navigate.ts
2
+ * Weavetab — tools/navigate.ts
3
3
  * URL navigation with domain blocklist enforcement and post-navigation DOM stability wait.
4
4
  * Weavetab by fy2ne
5
5
  */
@@ -71,10 +71,10 @@ export async function weaveNavigate(session, url, config, targetId = "default",
71
71
  }
72
72
  }
73
73
  // Extension pages: open in new tab instead of navigating current tab
74
- // (chrome-extension:// pages use a different process context — Page.navigate
74
+ // (chrome-extension:// pages use a different process context — Page.navigate
75
75
  // can break the CDP session or not fire loadEventFired properly)
76
76
  if (parsed.protocol === 'chrome-extension:') {
77
- logDebug(`[weaveNavigate] Extension URL detected — opening in new tab: ${parsed.href}`);
77
+ logDebug(`[weaveNavigate] Extension URL detected — opening in new tab: ${parsed.href}`);
78
78
  const { sendToExtension } = await import("../cdp/helpers.js");
79
79
  await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `opening extension page...` } });
80
80
  const { WeavetabOpen } = await import("./tabs.js");
@@ -98,7 +98,7 @@ export async function weaveNavigate(session, url, config, targetId = "default",
98
98
  }
99
99
  }
100
100
  catch { /* overlay install is best-effort */ }
101
- logAction("browser_go", url, `opened extension tab ${openRes.tabId}`);
101
+ logAction("browser_navigate", url, `opened extension tab ${openRes.tabId}`);
102
102
  return {
103
103
  success: true,
104
104
  url: openRes.url,
@@ -115,7 +115,7 @@ export async function weaveNavigate(session, url, config, targetId = "default",
115
115
  expression: `new Promise((resolve) => {
116
116
  let done = false;
117
117
  const finish = (val) => { if (!done) { done = true; resolve(val); } };
118
- setTimeout(() => finish(false), 500);
118
+ setTimeout(() => finish(false), 30);
119
119
  try {
120
120
  const browserAPI = typeof browser !== 'undefined' ? browser : (typeof chrome !== 'undefined' ? chrome : null);
121
121
  if (browserAPI && browserAPI.runtime && browserAPI.runtime.id) {
@@ -129,7 +129,7 @@ export async function weaveNavigate(session, url, config, targetId = "default",
129
129
  })`,
130
130
  awaitPromise: true,
131
131
  returnByValue: true
132
- });
132
+ }).catch(() => null);
133
133
  isAnchor = res?.result?.value === true;
134
134
  }
135
135
  catch (e) {
@@ -151,55 +151,50 @@ export async function weaveNavigate(session, url, config, targetId = "default",
151
151
  sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `navigating to ${parsed.hostname}...` } }).catch(() => { });
152
152
  sendToExtension(session, { type: 'Weavetab_STATE', cursor: { x: 10, y: 10 }, state: 'thinking' }).catch(() => { });
153
153
  await session.Page.navigate({ url: parsed.href });
154
- // Proper navigation wait — DOM stability after load
154
+ // Fast navigation wait
155
155
  try {
156
156
  await awaitNavigation(session, config.navigationTimeout || 30000);
157
157
  }
158
158
  catch (navErr) {
159
159
  logDebug(`[weaveNavigate] Navigation timeout: ${navErr}`);
160
- // Continue — page may be partially loaded
161
160
  }
162
- // Clear delta snapshot — next weave_read will be a fresh full read
161
+ // Clear delta snapshot & caches
163
162
  clearSnapshot(resolvedTargetId);
164
163
  resetLoopCounter("navigation");
165
- clearToolCallWindow(); // prevents cross-page STUCK_LOOP bricks
164
+ clearToolCallWindow();
166
165
  resetFallbackIdCounter();
167
166
  clearShadowDomCache(resolvedTargetId);
168
167
  clearAXTreeCache(resolvedTargetId);
169
168
  clearTabCache(resolvedTargetId);
170
- // Dismiss common cookie banners / popups inline (popups.ts was removed)
171
- await session.Runtime.evaluate({
172
- expression: `
173
- (function(){
174
- var selectors = [
175
- '[id*="cookie"] button','[class*="cookie"] button',
176
- '[id*="consent"] button','[class*="consent"] button',
177
- '[id*="gdpr"] button','[class*="gdpr"] button',
178
- '[aria-label*="Accept"]','[aria-label*="Agree"]',
179
- 'button[data-cookiebanner]'
180
- ];
181
- for(var i=0;i<selectors.length;i++){
182
- var el=document.querySelector(selectors[i]);
183
- if(el){try{el.click();}catch(e){}break;}
184
- }
185
- })()
186
- `
187
- }).catch(() => { });
188
- const { frameTree } = await session.Page.getFrameTree();
189
- const finalUrl = frameTree.frame.url;
190
- let title = parsed.hostname;
191
- try {
192
- const titleRes = await session.Runtime.evaluate({ expression: 'document.title', returnByValue: true });
193
- if (titleRes?.result?.value) {
194
- title = titleRes.result.value;
195
- }
196
- }
197
- catch { }
169
+ // Parallelize post-navigation metadata & cookie banner dismissal
170
+ const [frameRes, titleRes] = await Promise.all([
171
+ session.Page.getFrameTree().catch(() => ({ frameTree: { frame: { url: parsed.href } } })),
172
+ session.Runtime.evaluate({ expression: 'document.title', returnByValue: true }).catch(() => null),
173
+ session.Runtime.evaluate({
174
+ expression: `
175
+ (function(){
176
+ var selectors = [
177
+ '[id*="cookie"] button','[class*="cookie"] button',
178
+ '[id*="consent"] button','[class*="consent"] button',
179
+ '[id*="gdpr"] button','[class*="gdpr"] button',
180
+ '[aria-label*="Accept"]','[aria-label*="Agree"]',
181
+ 'button[data-cookiebanner]'
182
+ ];
183
+ for(var i=0;i<selectors.length;i++){
184
+ var el=document.querySelector(selectors[i]);
185
+ if(el){try{el.click();}catch(e){}break;}
186
+ }
187
+ })()
188
+ `
189
+ }).catch(() => { })
190
+ ]);
191
+ const finalUrl = frameRes?.frameTree?.frame?.url || parsed.href;
192
+ const title = titleRes?.result?.value || parsed.hostname;
198
193
  // Keep session registry in sync with the new URL
199
194
  if (active)
200
195
  active.url = finalUrl;
201
196
  sendToExtension(session, { type: 'Weavetab_STATE', cursor: { x: 10, y: 10 }, state: 'idle', hud: { action: '' } }).catch(() => { });
202
- logAction("browser_go", url, `arrived at ${title}`);
197
+ logAction("browser_navigate", url, `arrived at ${title}`);
203
198
  // Bump the visit counter for this domain exactly once per navigation
204
199
  bumpVisitCount(parsed.hostname).catch(() => { });
205
200
  return { success: true, url: finalUrl, title, tab_id: resolvedTargetId };