@weavetab/mcp 2.5.0-beta.5 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/AGENTS.md +49 -49
  2. package/ARCHITECTURE.md +590 -590
  3. package/README.md +216 -100
  4. package/SECURITY.md +207 -207
  5. package/TROUBLESHOOTING.md +373 -365
  6. package/dist/anticaptcha/detector.js +395 -395
  7. package/dist/anticaptcha/solver.js +130 -130
  8. package/dist/cdp/bridge.js +73 -73
  9. package/dist/cdp/connector.js +46 -22
  10. package/dist/cdp/extension.js +34 -34
  11. package/dist/cdp/helpers.js +174 -174
  12. package/dist/cdp/pruner.js +195 -195
  13. package/dist/cdp/ref_cache.js +66 -66
  14. package/dist/cdp/security.js +10 -10
  15. package/dist/cdp/verify.js +57 -57
  16. package/dist/cdp/walker.js +563 -525
  17. package/dist/cli/plugin.js +26 -3
  18. package/dist/config/loader.d.ts +0 -3
  19. package/dist/config/loader.js +0 -9
  20. package/dist/index.js +0 -4
  21. package/dist/intelligence/intent.js +81 -81
  22. package/dist/intelligence/sessionMemory.js +2 -2
  23. package/dist/overlay/ghost.js +22 -22
  24. package/dist/overlay/injector.js +89 -89
  25. package/dist/plugin/extension_bridge.js +54 -54
  26. package/dist/plugin/interface.d.ts +7 -0
  27. package/dist/plugin/loader.js +3 -4
  28. package/dist/plugin/schema.d.ts +11 -0
  29. package/dist/plugin/schema.js +59 -0
  30. package/dist/security/guard.js +18 -18
  31. package/dist/sensors/actionability.js +167 -167
  32. package/dist/sensors/guard.js +29 -29
  33. package/dist/sensors/network.js +15 -17
  34. package/dist/server.d.ts +1 -1
  35. package/dist/server.js +173 -144
  36. package/dist/state/delta.js +6 -7
  37. package/dist/tools/automation.js +85 -85
  38. package/dist/tools/canvas.js +48 -25
  39. package/dist/tools/checkpoint.js +94 -35
  40. package/dist/tools/click.js +52 -41
  41. package/dist/tools/clipboard.js +80 -74
  42. package/dist/tools/design_extract.js +441 -441
  43. package/dist/tools/dialog.js +13 -13
  44. package/dist/tools/emulation.d.ts +2 -0
  45. package/dist/tools/emulation.js +7 -0
  46. package/dist/tools/fill.js +344 -347
  47. package/dist/tools/find.js +78 -52
  48. package/dist/tools/highlight.js +67 -67
  49. package/dist/tools/inspect.js +54 -25
  50. package/dist/tools/key.js +42 -35
  51. package/dist/tools/navigate.d.ts +1 -1
  52. package/dist/tools/navigate.js +35 -35
  53. package/dist/tools/office/parse.js +62 -18
  54. package/dist/tools/pdf.d.ts +9 -0
  55. package/dist/tools/pdf.js +7 -4
  56. package/dist/tools/performance.d.ts +11 -4
  57. package/dist/tools/performance.js +94 -42
  58. package/dist/tools/performance_test.d.ts +2 -2
  59. package/dist/tools/performance_test.js +69 -69
  60. package/dist/tools/pointer.js +116 -116
  61. package/dist/tools/read.js +99 -67
  62. package/dist/tools/recording.js +9 -0
  63. package/dist/tools/scrape.js +146 -146
  64. package/dist/tools/screenshot.d.ts +2 -0
  65. package/dist/tools/screenshot.js +46 -4
  66. package/dist/tools/scroll.d.ts +3 -1
  67. package/dist/tools/scroll.js +13 -15
  68. package/dist/tools/select.d.ts +3 -0
  69. package/dist/tools/select.js +131 -124
  70. package/dist/tools/snapshot.js +8 -6
  71. package/dist/tools/storage.d.ts +5 -1
  72. package/dist/tools/storage.js +16 -0
  73. package/dist/tools/tabs.d.ts +7 -5
  74. package/dist/tools/tabs.js +79 -26
  75. package/dist/tools/thought.js +8 -8
  76. package/dist/tools/type.js +274 -274
  77. package/dist/tools/type_secret.js +23 -23
  78. package/dist/tools/upload.js +3 -3
  79. package/dist/tools/video_compiler.js +9 -0
  80. package/dist/tools/wait.js +35 -35
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/docs/DOCUMENTATION.md +583 -585
  84. package/docs/PLUGINS.md +139 -121
  85. package/docs/TOOLS.md +3 -4
  86. package/docs/architecture/blind-injection.md +83 -83
  87. package/docs/architecture/overview.md +49 -49
  88. package/docs/configuration/configuration.md +39 -27
  89. package/docs/configuration/folders.md +62 -55
  90. package/docs/desktop-automation/electron-apps.md +225 -214
  91. package/docs/get-started/installation.md +1 -1
  92. package/docs/get-started/introduction.md +60 -60
  93. package/docs/get-started/quickstart.md +2 -2
  94. package/docs/get-started/setup-wizard.md +1 -1
  95. package/docs/intelligence/site-profiles.md +151 -151
  96. package/docs/tools/tools.md +582 -631
  97. package/docs/troubleshooting/trouble-loop.md +31 -31
  98. package/ext-dist/background.js +415 -415
  99. package/ext-dist/content.js +2527 -2527
  100. package/ext-dist/dashboard.html +583 -583
  101. package/ext-dist/hud/hud.js +3 -3
  102. package/ext-dist/hud/index.html +20 -20
  103. package/ext-dist/manifest.json +73 -73
  104. package/ext-dist/styles.css +223 -223
  105. package/mcp.json +796 -805
  106. package/package.json +8 -8
  107. package/server.json +23 -23
  108. package/smithery.yaml +7 -11
  109. package/dist/cdp/walker.test.d.ts +0 -2
  110. package/dist/cdp/walker.test.js +0 -172
  111. package/dist/intelligence/sessionMemory.test.d.ts +0 -2
  112. package/dist/intelligence/sessionMemory.test.js +0 -62
  113. package/dist/intelligence/trail.test.d.ts +0 -2
  114. package/dist/intelligence/trail.test.js +0 -110
  115. package/dist/state/cache.test.d.ts +0 -2
  116. package/dist/state/cache.test.js +0 -76
  117. package/dist/state/delta.test.d.ts +0 -2
  118. package/dist/state/delta.test.js +0 -279
  119. package/dist/state/groups.d.ts +0 -15
  120. package/dist/state/groups.js +0 -80
  121. package/dist/tools/accessibility.d.ts +0 -14
  122. package/dist/tools/accessibility.js +0 -32
  123. package/dist/tools/ask.d.ts +0 -14
  124. package/dist/tools/ask.js +0 -49
  125. package/dist/tools/click_and_wait.d.ts +0 -33
  126. package/dist/tools/click_and_wait.js +0 -149
  127. package/dist/tools/drag.d.ts +0 -15
  128. package/dist/tools/drag.js +0 -29
  129. package/dist/tools/geo.d.ts +0 -19
  130. package/dist/tools/geo.js +0 -29
  131. package/dist/tools/github/analyze.d.ts +0 -4
  132. package/dist/tools/github/analyze.js +0 -113
  133. package/dist/tools/github/cache.d.ts +0 -21
  134. package/dist/tools/github/cache.js +0 -28
  135. package/dist/tools/github/index.d.ts +0 -19
  136. package/dist/tools/github/index.js +0 -40
  137. package/dist/tools/github/issues.d.ts +0 -9
  138. package/dist/tools/github/issues.js +0 -88
  139. package/dist/tools/github/pr.d.ts +0 -5
  140. package/dist/tools/github/pr.js +0 -88
  141. package/dist/tools/github/rateLimiter.d.ts +0 -4
  142. package/dist/tools/github/rateLimiter.js +0 -46
  143. package/dist/tools/github/read.d.ts +0 -34
  144. package/dist/tools/github/read.js +0 -74
  145. package/dist/tools/github/repoContext.d.ts +0 -20
  146. package/dist/tools/github/repoContext.js +0 -129
  147. package/dist/tools/hover.d.ts +0 -19
  148. package/dist/tools/hover.js +0 -57
  149. package/dist/tools/integration.test.d.ts +0 -2
  150. package/dist/tools/integration.test.js +0 -96
  151. package/dist/tools/macro_compiler.d.ts +0 -7
  152. package/dist/tools/macro_compiler.js +0 -82
  153. package/dist/tools/mouse.d.ts +0 -21
  154. package/dist/tools/mouse.js +0 -62
  155. package/dist/tools/response.d.ts +0 -129
  156. package/dist/tools/response.js +0 -5
  157. package/dist/tools/wait_for.d.ts +0 -23
  158. package/dist/tools/wait_for.js +0 -30
  159. package/dist/tools/windows.d.ts +0 -22
  160. package/dist/tools/windows.js +0 -57
@@ -43,51 +43,51 @@ export class PluginExtensionBridge {
43
43
  const safeText = JSON.stringify(text);
44
44
  try {
45
45
  await session.Runtime.evaluate({
46
- expression: `(function() {
47
- var existing = document.getElementById('__wt_plugin_thought_${this.pluginName}');
48
- if (existing) existing.remove();
49
-
50
- var bubble = document.createElement('div');
51
- bubble.id = '__wt_plugin_thought_${this.pluginName}';
52
- bubble.className = 'wt-bubble';
53
- bubble.setAttribute('data-wt-internal', 'true');
54
- bubble.style.cssText = [
55
- 'position: fixed',
56
- 'bottom: 24px',
57
- 'right: 24px',
58
- 'background: linear-gradient(135deg, rgba(17, 24, 39, 0.95), rgba(30, 41, 59, 0.95))',
59
- 'color: #f8fafc',
60
- 'padding: 12px 18px',
61
- 'border-radius: 12px',
62
- 'font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
63
- 'font-size: 13px',
64
- 'font-weight: 500',
65
- 'box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)',
66
- 'z-index: 2147483647',
67
- 'backdrop-filter: blur(8px)',
68
- 'pointer-events: none',
69
- 'animation: wtThoughtFade 0.25s cubic-bezier(0.16, 1, 0.3, 1)',
70
- 'display: flex',
71
- 'align-items: center',
72
- 'gap: 8px'
73
- ].join(';');
74
-
75
- var icon = document.createElement('span');
76
- icon.textContent = '✨';
77
- var textSpan = document.createElement('span');
78
- textSpan.textContent = ${safeText};
79
-
80
- bubble.appendChild(icon);
81
- bubble.appendChild(textSpan);
82
- document.body.appendChild(bubble);
83
-
84
- setTimeout(function() {
85
- if (bubble && bubble.parentNode) {
86
- bubble.style.opacity = '0';
87
- bubble.style.transition = 'opacity 0.3s ease';
88
- setTimeout(function() { bubble.remove(); }, 300);
89
- }
90
- }, ${duration});
46
+ expression: `(function() {
47
+ var existing = document.getElementById('__wt_plugin_thought_${this.pluginName}');
48
+ if (existing) existing.remove();
49
+
50
+ var bubble = document.createElement('div');
51
+ bubble.id = '__wt_plugin_thought_${this.pluginName}';
52
+ bubble.className = 'wt-bubble';
53
+ bubble.setAttribute('data-wt-internal', 'true');
54
+ bubble.style.cssText = [
55
+ 'position: fixed',
56
+ 'bottom: 24px',
57
+ 'right: 24px',
58
+ 'background: linear-gradient(135deg, rgba(17, 24, 39, 0.95), rgba(30, 41, 59, 0.95))',
59
+ 'color: #f8fafc',
60
+ 'padding: 12px 18px',
61
+ 'border-radius: 12px',
62
+ 'font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
63
+ 'font-size: 13px',
64
+ 'font-weight: 500',
65
+ 'box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)',
66
+ 'z-index: 2147483647',
67
+ 'backdrop-filter: blur(8px)',
68
+ 'pointer-events: none',
69
+ 'animation: wtThoughtFade 0.25s cubic-bezier(0.16, 1, 0.3, 1)',
70
+ 'display: flex',
71
+ 'align-items: center',
72
+ 'gap: 8px'
73
+ ].join(';');
74
+
75
+ var icon = document.createElement('span');
76
+ icon.textContent = '✨';
77
+ var textSpan = document.createElement('span');
78
+ textSpan.textContent = ${safeText};
79
+
80
+ bubble.appendChild(icon);
81
+ bubble.appendChild(textSpan);
82
+ document.body.appendChild(bubble);
83
+
84
+ setTimeout(function() {
85
+ if (bubble && bubble.parentNode) {
86
+ bubble.style.opacity = '0';
87
+ bubble.style.transition = 'opacity 0.3s ease';
88
+ setTimeout(function() { bubble.remove(); }, 300);
89
+ }
90
+ }, ${duration});
91
91
  })()`,
92
92
  returnByValue: false,
93
93
  });
@@ -101,9 +101,9 @@ export class PluginExtensionBridge {
101
101
  return;
102
102
  try {
103
103
  await session.Runtime.evaluate({
104
- expression: `(function() {
105
- var existing = document.getElementById('__wt_plugin_thought_${this.pluginName}');
106
- if (existing) existing.remove();
104
+ expression: `(function() {
105
+ var existing = document.getElementById('__wt_plugin_thought_${this.pluginName}');
106
+ if (existing) existing.remove();
107
107
  })()`,
108
108
  returnByValue: false,
109
109
  });
@@ -118,12 +118,12 @@ export class PluginExtensionBridge {
118
118
  return;
119
119
  const safeCss = JSON.stringify(css);
120
120
  await session.Runtime.evaluate({
121
- expression: `(function() {
122
- var styleId = '__wt_plugin_styles_${this.pluginName}';
123
- var style = document.getElementById(styleId) || document.createElement('style');
124
- style.id = styleId;
125
- style.textContent = ${safeCss};
126
- if (!style.parentNode) document.head.appendChild(style);
121
+ expression: `(function() {
122
+ var styleId = '__wt_plugin_styles_${this.pluginName}';
123
+ var style = document.getElementById(styleId) || document.createElement('style');
124
+ style.id = styleId;
125
+ style.textContent = ${safeCss};
126
+ if (!style.parentNode) document.head.appendChild(style);
127
127
  })()`,
128
128
  returnByValue: false,
129
129
  });
@@ -7,6 +7,11 @@ export interface WeavetabToolDefinition {
7
7
  name: string;
8
8
  description: string;
9
9
  schema: Record<string, z.ZodType<any>>;
10
+ /**
11
+ * When `false`, the host does NOT establish a browser/CDP session before
12
+ * invoking this tool. Use for API-only tools. Defaults to `true`.
13
+ */
14
+ requiresSession?: boolean;
10
15
  handler: (args: Record<string, any>, session: CDP.Client, config: Config) => Promise<Record<string, any>>;
11
16
  }
12
17
  export interface WeavetabToolOverride {
@@ -146,6 +151,8 @@ export interface WeavetabPluginTool<TArgs = any> {
146
151
  schema?: Record<string, any> | Record<string, z.ZodType<any>>;
147
152
  inputSchema?: Record<string, any>;
148
153
  parameters?: Record<string, any>;
154
+ /** See {@link WeavetabToolDefinition.requiresSession}. Defaults to `true`. */
155
+ requiresSession?: boolean;
149
156
  handler: (args: TArgs, session?: any, config?: any) => Promise<any>;
150
157
  }
151
158
  export interface DOMMutationDelta {
@@ -17,7 +17,6 @@ function getNodeModulesDirs(customDir) {
17
17
  join(cwd, "MCP", "node_modules"),
18
18
  join(cwd, "..", "node_modules"),
19
19
  join(os.homedir(), ".weavetab", "plugins", "node_modules"),
20
- join(os.homedir(), ".weavetab-system", "plugins", "node_modules"),
21
20
  ];
22
21
  return Array.from(new Set(dirs.filter((d) => existsSync(d))));
23
22
  }
@@ -152,7 +151,7 @@ export function listInstalledPlugins(pluginDir) {
152
151
  if (!entry.isDirectory() && !entry.isSymbolicLink())
153
152
  continue;
154
153
  if (entry.name.startsWith("@")) {
155
- // Scoped packages (@weavetab/plugin-xyz, @scope/xyz, @fy2ne/wt-youtube)
154
+ // Scoped packages (@weavetab/plugin-xyz, @scope/xyz, @acme/weavetab-plugin)
156
155
  const scopeDir = join(modulesDir, entry.name);
157
156
  try {
158
157
  const scopeEntries = readdirSync(scopeDir, { withFileTypes: true });
@@ -419,7 +418,7 @@ export function discoverPlugins(pluginDir) {
419
418
  if (!entry.isDirectory() && !entry.isSymbolicLink())
420
419
  continue;
421
420
  if (entry.name.startsWith("@")) {
422
- // Scoped package directory (e.g. @weavetab, @fy2ne, @mycompany)
421
+ // Scoped package directory (e.g. @weavetab, @acme, @mycompany)
423
422
  const scopeDir = join(modulesDir, entry.name);
424
423
  try {
425
424
  const scopeEntries = readdirSync(scopeDir, { withFileTypes: true });
@@ -436,7 +435,7 @@ export function discoverPlugins(pluginDir) {
436
435
  catch { /* scope dir unreadable */ }
437
436
  }
438
437
  else {
439
- // Top-level package — any name works: wt-youtube, my-plugin, etc.
438
+ // Top-level package — any name works: my-plugin, wt-github, custom-plugin, etc.
440
439
  const plugin = tryLoadPlugin(join(modulesDir, entry.name));
441
440
  if (plugin && !seenPluginNames.has(plugin.name)) {
442
441
  seenPluginNames.add(plugin.name);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Weavetab by fy2ne
3
+ * @file schema.ts — Plugin tool schema normalizer.
4
+ *
5
+ * Converts a plugin-provided tool schema into a Zod raw shape accepted by
6
+ * `McpServer.tool()`. Supports the three shapes plugin authors may provide:
7
+ * 1. Zod raw shape — { field: z.string() } (preferred)
8
+ * 2. Zod object schema — z.object({ field: z.string() })
9
+ * 3. Plain JSON Schema — { type: "object", properties: { field: { type: "string" } } }
10
+ */
11
+ export declare function toPluginInputShape(schema: any): Record<string, any>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Weavetab by fy2ne
3
+ * @file schema.ts — Plugin tool schema normalizer.
4
+ *
5
+ * Converts a plugin-provided tool schema into a Zod raw shape accepted by
6
+ * `McpServer.tool()`. Supports the three shapes plugin authors may provide:
7
+ * 1. Zod raw shape — { field: z.string() } (preferred)
8
+ * 2. Zod object schema — z.object({ field: z.string() })
9
+ * 3. Plain JSON Schema — { type: "object", properties: { field: { type: "string" } } }
10
+ */
11
+ import { z } from "zod";
12
+ export function toPluginInputShape(schema) {
13
+ if (!schema || typeof schema !== "object")
14
+ return {};
15
+ // A Zod object/schema instance (e.g. z.object({...})) — use its shape.
16
+ if (schema._def || schema._zod) {
17
+ const shape = typeof schema.shape === "function" ? schema.shape() : schema.shape;
18
+ return shape && typeof shape === "object" ? shape : {};
19
+ }
20
+ // A raw shape: an object whose values are Zod schemas.
21
+ const values = Object.values(schema);
22
+ if (values.some((v) => v && typeof v === "object" && (v._def || v._zod || typeof v.parse === "function"))) {
23
+ return schema;
24
+ }
25
+ // A plain JSON Schema object — convert its properties into a Zod raw shape.
26
+ if (schema.properties && typeof schema.properties === "object") {
27
+ const required = Array.isArray(schema.required) ? schema.required : [];
28
+ const shape = {};
29
+ for (const [key, prop] of Object.entries(schema.properties)) {
30
+ let field;
31
+ switch (prop?.type) {
32
+ case "number":
33
+ case "integer":
34
+ field = z.number();
35
+ break;
36
+ case "boolean":
37
+ field = z.boolean();
38
+ break;
39
+ case "array":
40
+ field = z.array(z.any());
41
+ break;
42
+ case "object":
43
+ field = z.record(z.string(), z.any());
44
+ break;
45
+ default:
46
+ field = z.string();
47
+ }
48
+ if (typeof prop?.description === "string")
49
+ field = field.describe(prop.description);
50
+ if (prop?.default !== undefined)
51
+ field = field.default(prop.default);
52
+ if (!required.includes(key))
53
+ field = field.optional();
54
+ shape[key] = field;
55
+ }
56
+ return shape;
57
+ }
58
+ return {};
59
+ }
@@ -4,24 +4,24 @@ export function assertNotRoot() {
4
4
  throw new Error("[!] Weave failed: Running as root is not allowed. Start Weavetab as a normal user.");
5
5
  }
6
6
  }
7
- const STORAGE_BLOCK_SCRIPT = `
8
- (function() {
9
- // Only block cookie WRITES for known tracking domains, not reads.
10
- // Fully blocking storage breaks site rendering (GitHub, etc.)
11
- var _origCookieDesc = Object.getOwnPropertyDescriptor(document, 'cookie');
12
- if (_origCookieDesc) {
13
- Object.defineProperty(document, 'cookie', {
14
- get: function() { return _origCookieDesc.get.call(document); },
15
- set: function(v) {
16
- // Block cookies from known trackers
17
- if (/(_ga|_gid|_fbp|_fbc|__utm|fr|_gcl)/.test(v)) return;
18
- _origCookieDesc.set.call(document, v);
19
- },
20
- configurable: true
21
- });
22
- }
23
- // Do NOT block localStorage/sessionStorage — sites need them to render
24
- })();
7
+ const STORAGE_BLOCK_SCRIPT = `
8
+ (function() {
9
+ // Only block cookie WRITES for known tracking domains, not reads.
10
+ // Fully blocking storage breaks site rendering (GitHub, etc.)
11
+ var _origCookieDesc = Object.getOwnPropertyDescriptor(document, 'cookie');
12
+ if (_origCookieDesc) {
13
+ Object.defineProperty(document, 'cookie', {
14
+ get: function() { return _origCookieDesc.get.call(document); },
15
+ set: function(v) {
16
+ // Block cookies from known trackers
17
+ if (/(_ga|_gid|_fbp|_fbc|__utm|fr|_gcl)/.test(v)) return;
18
+ _origCookieDesc.set.call(document, v);
19
+ },
20
+ configurable: true
21
+ });
22
+ }
23
+ // Do NOT block localStorage/sessionStorage — sites need them to render
24
+ })();
25
25
  `;
26
26
  export async function blockStorageAccess(session) {
27
27
  await session.Page.addScriptToEvaluateOnNewDocument({
@@ -48,137 +48,137 @@ function buildPageExpression(selector, scrollOptions, fingerprint, stabilityMs)
48
48
  const selectorLiteral = JSON.stringify(selector);
49
49
  const scrollLiteral = scrollOptions ? JSON.stringify(scrollOptions) : "null";
50
50
  const fingerprintLiteral = fingerprint ? JSON.stringify(fingerprint) : "null";
51
- return `(async function() {
52
- // ── Helper: is this node owned by WeaveTab? ────────────────────────────
53
- function isWtOwned(el) {
54
- if (!el) return false;
55
- // Attribute-based markers: data-wt-ignore and data-wt-internal both signal
56
- // WeaveTab-owned UI that must not block page interactions.
57
- if (el.getAttribute && (
58
- el.getAttribute('data-wt-ignore') !== null ||
59
- el.getAttribute('data-wt-internal') !== null ||
60
- el.getAttribute('data-wt-overlay') !== null
61
- )) return true;
62
- const wtIds = ['Weavetab-hud', '__Weavetab_controlled_indicator', 'wt-main-container', 'wt-system-icon', 'wt-f3-host', 'Weavetab-root'];
63
- if (el.id && wtIds.includes(el.id)) return true;
64
- if (el.closest) {
65
- if (el.closest('[data-wt-ignore],[data-wt-internal],[data-wt-overlay]')) return true;
66
- if (wtIds.some(id => el.closest('#' + id))) return true;
67
- }
68
- return false;
69
- }
70
-
71
- // ── Step 1: Resolve element ────────────────────────────────────────────
72
- let el = document.querySelector(${selectorLiteral});
73
- if (!el && window.__Weavetab_shadowQuery) {
74
- el = window.__Weavetab_shadowQuery(${selectorLiteral});
75
- }
76
- if (!el) return { status: 'NOT_FOUND' };
77
-
78
- // ── Step 2: Fingerprint check ──────────────────────────────────────────
79
- const fp = ${fingerprintLiteral};
80
- if (fp) {
81
- if (fp.tag && el.tagName.toLowerCase() !== fp.tag.toLowerCase())
82
- return { status: 'FINGERPRINT_MISMATCH' };
83
- if (fp.role && el.getAttribute('role') !== fp.role)
84
- return { status: 'FINGERPRINT_MISMATCH' };
85
- }
86
-
87
- // ── Step 3: Basic visibility ───────────────────────────────────────────
88
- // Use checkVisibility() (Chrome 105+) as the most reliable single check.
89
- // Falls back to manual computed-style + rect checks for older runtimes.
90
- if (typeof el.checkVisibility === 'function') {
91
- if (!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }))
92
- return { status: 'INVISIBLE' };
93
- } else {
94
- const cs = window.getComputedStyle(el);
95
- if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0')
96
- return { status: 'INVISIBLE' };
97
- }
98
-
99
- let rect = el.getBoundingClientRect();
100
- if (rect.width === 0 || rect.height === 0) return { status: 'INVISIBLE' };
101
- // Off-screen by more than 2× viewport dimension in any direction
102
- if (rect.right < -window.innerWidth || rect.bottom < -window.innerHeight ||
103
- rect.left > 2 * window.innerWidth || rect.top > 2 * window.innerHeight)
104
- return { status: 'INVISIBLE' };
105
-
106
- // ── Step 4: Scroll into view (using provided alignment) ────────────────
107
- const scrollOpts = ${scrollLiteral};
108
- if (scrollOpts) {
109
- el.scrollIntoView(scrollOpts);
110
- await new Promise(r => setTimeout(r, 120));
111
- } else if (rect.top < 0 || rect.bottom > window.innerHeight ||
112
- rect.left < 0 || rect.right > window.innerWidth) {
113
- el.scrollIntoView({ block: 'nearest', inline: 'nearest' });
114
- await new Promise(r => setTimeout(r, 120));
115
- }
116
- rect = el.getBoundingClientRect();
117
-
118
- // ── Step 5: RAF-stability gate ─────────────────────────────────────────
119
- const stabilityDeadline = Date.now() + ${stabilityMs};
120
- let prevRect = el.getBoundingClientRect();
121
- let stable = false;
122
- while (Date.now() < stabilityDeadline) {
123
- await new Promise(r => requestAnimationFrame(r));
124
- const cur = el.getBoundingClientRect();
125
- if (
126
- Math.abs(cur.x - prevRect.x) < 0.5 &&
127
- Math.abs(cur.y - prevRect.y) < 0.5 &&
128
- Math.abs(cur.width - prevRect.width) < 0.5 &&
129
- Math.abs(cur.height - prevRect.height) < 0.5
130
- ) {
131
- stable = true;
132
- break;
133
- }
134
- prevRect = cur;
135
- }
136
- if (!stable) return { status: 'UNSTABLE' };
137
- rect = el.getBoundingClientRect();
138
-
139
- // ── Step 6: Compute click centre (clamped to viewport) ─────────────────
140
- const cx = Math.round(Math.min(Math.max(rect.left + rect.width / 2, 0), window.innerWidth - 1));
141
- const cy = Math.round(Math.min(Math.max(rect.top + rect.height / 2, 0), window.innerHeight - 1));
142
-
143
- // ── Step 7: Strict hit-target check ───────────────────────────────────
144
- // elementsFromPoint returns the full composited stack, topmost first.
145
- const stack = document.elementsFromPoint(cx, cy);
146
- if (!stack || stack.length === 0) return { status: 'ACTIONABLE', x: cx, y: cy };
147
-
148
- // Walk from the topmost element. Anything that is WeaveTab-owned is
149
- // transparent — skip it. The first non-WT element must be el or its descendant.
150
- let blocker = null;
151
- for (const hit of stack) {
152
- if (isWtOwned(hit)) continue; // WeaveTab transparency
153
- blocker = hit;
154
- break;
155
- }
156
-
157
- if (!blocker || blocker === el || el.contains(blocker)) {
158
- return { status: 'ACTIONABLE', x: cx, y: cy };
159
- }
160
-
161
- // Check composed tree (shadow DOM + slots)
162
- let ancestor = blocker;
163
- while (ancestor) {
164
- if (ancestor === el) return { status: 'ACTIONABLE', x: cx, y: cy };
165
- ancestor = ancestor.assignedSlot || ancestor.parentElement ||
166
- (ancestor.getRootNode && ancestor.getRootNode({ composed: true }) !== ancestor
167
- ? ancestor.getRootNode({ composed: true }).host : null);
168
- }
169
-
170
- return {
171
- status: 'OCCLUDED',
172
- x: cx, y: cy,
173
- occludedBy: {
174
- tag: blocker.tagName.toLowerCase(),
175
- id: blocker.id || '',
176
- className: (blocker.className && typeof blocker.className === 'string')
177
- ? blocker.className.slice(0, 60) : '',
178
- ariaLabel: blocker.getAttribute('aria-label') || blocker.getAttribute('title') || null,
179
- isWeavetabOwned: isWtOwned(blocker)
180
- }
181
- };
51
+ return `(async function() {
52
+ // ── Helper: is this node owned by WeaveTab? ────────────────────────────
53
+ function isWtOwned(el) {
54
+ if (!el) return false;
55
+ // Attribute-based markers: data-wt-ignore and data-wt-internal both signal
56
+ // WeaveTab-owned UI that must not block page interactions.
57
+ if (el.getAttribute && (
58
+ el.getAttribute('data-wt-ignore') !== null ||
59
+ el.getAttribute('data-wt-internal') !== null ||
60
+ el.getAttribute('data-wt-overlay') !== null
61
+ )) return true;
62
+ const wtIds = ['Weavetab-hud', '__Weavetab_controlled_indicator', 'wt-main-container', 'wt-system-icon', 'wt-f3-host', 'Weavetab-root'];
63
+ if (el.id && wtIds.includes(el.id)) return true;
64
+ if (el.closest) {
65
+ if (el.closest('[data-wt-ignore],[data-wt-internal],[data-wt-overlay]')) return true;
66
+ if (wtIds.some(id => el.closest('#' + id))) return true;
67
+ }
68
+ return false;
69
+ }
70
+
71
+ // ── Step 1: Resolve element ────────────────────────────────────────────
72
+ let el = document.querySelector(${selectorLiteral});
73
+ if (!el && window.__Weavetab_shadowQuery) {
74
+ el = window.__Weavetab_shadowQuery(${selectorLiteral});
75
+ }
76
+ if (!el) return { status: 'NOT_FOUND' };
77
+
78
+ // ── Step 2: Fingerprint check ──────────────────────────────────────────
79
+ const fp = ${fingerprintLiteral};
80
+ if (fp) {
81
+ if (fp.tag && el.tagName.toLowerCase() !== fp.tag.toLowerCase())
82
+ return { status: 'FINGERPRINT_MISMATCH' };
83
+ if (fp.role && el.getAttribute('role') !== fp.role)
84
+ return { status: 'FINGERPRINT_MISMATCH' };
85
+ }
86
+
87
+ // ── Step 3: Basic visibility ───────────────────────────────────────────
88
+ // Use checkVisibility() (Chrome 105+) as the most reliable single check.
89
+ // Falls back to manual computed-style + rect checks for older runtimes.
90
+ if (typeof el.checkVisibility === 'function') {
91
+ if (!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }))
92
+ return { status: 'INVISIBLE' };
93
+ } else {
94
+ const cs = window.getComputedStyle(el);
95
+ if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0')
96
+ return { status: 'INVISIBLE' };
97
+ }
98
+
99
+ let rect = el.getBoundingClientRect();
100
+ if (rect.width === 0 || rect.height === 0) return { status: 'INVISIBLE' };
101
+ // Off-screen by more than 2× viewport dimension in any direction
102
+ if (rect.right < -window.innerWidth || rect.bottom < -window.innerHeight ||
103
+ rect.left > 2 * window.innerWidth || rect.top > 2 * window.innerHeight)
104
+ return { status: 'INVISIBLE' };
105
+
106
+ // ── Step 4: Scroll into view (using provided alignment) ────────────────
107
+ const scrollOpts = ${scrollLiteral};
108
+ if (scrollOpts) {
109
+ el.scrollIntoView(scrollOpts);
110
+ await new Promise(r => setTimeout(r, 120));
111
+ } else if (rect.top < 0 || rect.bottom > window.innerHeight ||
112
+ rect.left < 0 || rect.right > window.innerWidth) {
113
+ el.scrollIntoView({ block: 'nearest', inline: 'nearest' });
114
+ await new Promise(r => setTimeout(r, 120));
115
+ }
116
+ rect = el.getBoundingClientRect();
117
+
118
+ // ── Step 5: RAF-stability gate ─────────────────────────────────────────
119
+ const stabilityDeadline = Date.now() + ${stabilityMs};
120
+ let prevRect = el.getBoundingClientRect();
121
+ let stable = false;
122
+ while (Date.now() < stabilityDeadline) {
123
+ await new Promise(r => requestAnimationFrame(r));
124
+ const cur = el.getBoundingClientRect();
125
+ if (
126
+ Math.abs(cur.x - prevRect.x) < 0.5 &&
127
+ Math.abs(cur.y - prevRect.y) < 0.5 &&
128
+ Math.abs(cur.width - prevRect.width) < 0.5 &&
129
+ Math.abs(cur.height - prevRect.height) < 0.5
130
+ ) {
131
+ stable = true;
132
+ break;
133
+ }
134
+ prevRect = cur;
135
+ }
136
+ if (!stable) return { status: 'UNSTABLE' };
137
+ rect = el.getBoundingClientRect();
138
+
139
+ // ── Step 6: Compute click centre (clamped to viewport) ─────────────────
140
+ const cx = Math.round(Math.min(Math.max(rect.left + rect.width / 2, 0), window.innerWidth - 1));
141
+ const cy = Math.round(Math.min(Math.max(rect.top + rect.height / 2, 0), window.innerHeight - 1));
142
+
143
+ // ── Step 7: Strict hit-target check ───────────────────────────────────
144
+ // elementsFromPoint returns the full composited stack, topmost first.
145
+ const stack = document.elementsFromPoint(cx, cy);
146
+ if (!stack || stack.length === 0) return { status: 'ACTIONABLE', x: cx, y: cy };
147
+
148
+ // Walk from the topmost element. Anything that is WeaveTab-owned is
149
+ // transparent — skip it. The first non-WT element must be el or its descendant.
150
+ let blocker = null;
151
+ for (const hit of stack) {
152
+ if (isWtOwned(hit)) continue; // WeaveTab transparency
153
+ blocker = hit;
154
+ break;
155
+ }
156
+
157
+ if (!blocker || blocker === el || el.contains(blocker)) {
158
+ return { status: 'ACTIONABLE', x: cx, y: cy };
159
+ }
160
+
161
+ // Check composed tree (shadow DOM + slots)
162
+ let ancestor = blocker;
163
+ while (ancestor) {
164
+ if (ancestor === el) return { status: 'ACTIONABLE', x: cx, y: cy };
165
+ ancestor = ancestor.assignedSlot || ancestor.parentElement ||
166
+ (ancestor.getRootNode && ancestor.getRootNode({ composed: true }) !== ancestor
167
+ ? ancestor.getRootNode({ composed: true }).host : null);
168
+ }
169
+
170
+ return {
171
+ status: 'OCCLUDED',
172
+ x: cx, y: cy,
173
+ occludedBy: {
174
+ tag: blocker.tagName.toLowerCase(),
175
+ id: blocker.id || '',
176
+ className: (blocker.className && typeof blocker.className === 'string')
177
+ ? blocker.className.slice(0, 60) : '',
178
+ ariaLabel: blocker.getAttribute('aria-label') || blocker.getAttribute('title') || null,
179
+ isWeavetabOwned: isWtOwned(blocker)
180
+ }
181
+ };
182
182
  })()`;
183
183
  }
184
184
  // ── Main export ───────────────────────────────────────────────────────────────
@@ -255,42 +255,42 @@ export async function verifyActionability(session, selector, opts = {}) {
255
255
  * Used for lightweight pre-dispatch validation inside click.ts.
256
256
  */
257
257
  export async function quickHitCheck(session, x, y, selectorOfTarget) {
258
- const expr = `(function() {
259
- function isWtOwned(el) {
260
- if (!el) return false;
261
- if (el.getAttribute && (
262
- el.getAttribute('data-wt-ignore') !== null ||
263
- el.getAttribute('data-wt-internal') !== null ||
264
- el.getAttribute('data-wt-overlay') !== null
265
- )) return true;
266
- const ids = ['Weavetab-hud','__Weavetab_controlled_indicator','wt-main-container','wt-system-icon','wt-f3-host','Weavetab-root'];
267
- if (el.id && ids.includes(el.id)) return true;
268
- return !!(el.closest && (el.closest('[data-wt-ignore],[data-wt-internal],[data-wt-overlay]') || ids.some(id => el.closest('#' + id))));
269
- }
270
- const target = document.querySelector(${JSON.stringify(selectorOfTarget)})
271
- || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(${JSON.stringify(selectorOfTarget)}) : null);
272
- const stack = document.elementsFromPoint(${x}, ${y});
273
- if (!stack || !stack.length) return { clear: true };
274
- for (const hit of stack) {
275
- if (isWtOwned(hit)) continue;
276
- if (!target || hit === target || target.contains(hit)) return { clear: true };
277
- // Walk composed tree for shadow DOM
278
- let ancestor = hit;
279
- while (ancestor) {
280
- if (ancestor === target) return { clear: true };
281
- ancestor = ancestor.assignedSlot || ancestor.parentElement ||
282
- (ancestor.getRootNode && ancestor.getRootNode({composed:true}) !== ancestor
283
- ? ancestor.getRootNode({composed:true}).host : null);
284
- }
285
- return {
286
- clear: false,
287
- blockerDescription: hit.tagName.toLowerCase() +
288
- (hit.id ? '#' + hit.id : '') +
289
- (hit.className && typeof hit.className === 'string' ? '.' + hit.className.split(' ')[0] : '') +
290
- ' intercepts pointer events'
291
- };
292
- }
293
- return { clear: true };
258
+ const expr = `(function() {
259
+ function isWtOwned(el) {
260
+ if (!el) return false;
261
+ if (el.getAttribute && (
262
+ el.getAttribute('data-wt-ignore') !== null ||
263
+ el.getAttribute('data-wt-internal') !== null ||
264
+ el.getAttribute('data-wt-overlay') !== null
265
+ )) return true;
266
+ const ids = ['Weavetab-hud','__Weavetab_controlled_indicator','wt-main-container','wt-system-icon','wt-f3-host','Weavetab-root'];
267
+ if (el.id && ids.includes(el.id)) return true;
268
+ return !!(el.closest && (el.closest('[data-wt-ignore],[data-wt-internal],[data-wt-overlay]') || ids.some(id => el.closest('#' + id))));
269
+ }
270
+ const target = document.querySelector(${JSON.stringify(selectorOfTarget)})
271
+ || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(${JSON.stringify(selectorOfTarget)}) : null);
272
+ const stack = document.elementsFromPoint(${x}, ${y});
273
+ if (!stack || !stack.length) return { clear: true };
274
+ for (const hit of stack) {
275
+ if (isWtOwned(hit)) continue;
276
+ if (!target || hit === target || target.contains(hit)) return { clear: true };
277
+ // Walk composed tree for shadow DOM
278
+ let ancestor = hit;
279
+ while (ancestor) {
280
+ if (ancestor === target) return { clear: true };
281
+ ancestor = ancestor.assignedSlot || ancestor.parentElement ||
282
+ (ancestor.getRootNode && ancestor.getRootNode({composed:true}) !== ancestor
283
+ ? ancestor.getRootNode({composed:true}).host : null);
284
+ }
285
+ return {
286
+ clear: false,
287
+ blockerDescription: hit.tagName.toLowerCase() +
288
+ (hit.id ? '#' + hit.id : '') +
289
+ (hit.className && typeof hit.className === 'string' ? '.' + hit.className.split(' ')[0] : '') +
290
+ ' intercepts pointer events'
291
+ };
292
+ }
293
+ return { clear: true };
294
294
  })()`;
295
295
  try {
296
296
  const { result } = await session.Runtime.evaluate({ expression: expr, returnByValue: true });