@weavetab/mcp 2.5.0-beta.0

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 (281) hide show
  1. package/AGENTS.md +48 -0
  2. package/ARCHITECTURE.md +590 -0
  3. package/LICENSE +631 -0
  4. package/README.md +909 -0
  5. package/SECURITY.md +196 -0
  6. package/TROUBLESHOOTING.md +365 -0
  7. package/dist/anticaptcha/detector.d.ts +49 -0
  8. package/dist/anticaptcha/detector.js +465 -0
  9. package/dist/anticaptcha/integration.d.ts +36 -0
  10. package/dist/anticaptcha/integration.js +123 -0
  11. package/dist/anticaptcha/solver.d.ts +27 -0
  12. package/dist/anticaptcha/solver.js +552 -0
  13. package/dist/audit/logger.d.ts +13 -0
  14. package/dist/audit/logger.js +177 -0
  15. package/dist/audit/telemetry.d.ts +36 -0
  16. package/dist/audit/telemetry.js +162 -0
  17. package/dist/cdp/bridge.d.ts +16 -0
  18. package/dist/cdp/bridge.js +826 -0
  19. package/dist/cdp/confirm.d.ts +26 -0
  20. package/dist/cdp/confirm.js +79 -0
  21. package/dist/cdp/connector.d.ts +37 -0
  22. package/dist/cdp/connector.js +661 -0
  23. package/dist/cdp/extension.d.ts +36 -0
  24. package/dist/cdp/extension.js +118 -0
  25. package/dist/cdp/finder.d.ts +1 -0
  26. package/dist/cdp/finder.js +2 -0
  27. package/dist/cdp/helpers.d.ts +11 -0
  28. package/dist/cdp/helpers.js +195 -0
  29. package/dist/cdp/label_cache.d.ts +12 -0
  30. package/dist/cdp/label_cache.js +51 -0
  31. package/dist/cdp/lock.d.ts +33 -0
  32. package/dist/cdp/lock.js +206 -0
  33. package/dist/cdp/omni_broker.d.ts +17 -0
  34. package/dist/cdp/omni_broker.js +119 -0
  35. package/dist/cdp/profiles.d.ts +1 -0
  36. package/dist/cdp/profiles.js +87 -0
  37. package/dist/cdp/pruner.d.ts +41 -0
  38. package/dist/cdp/pruner.js +263 -0
  39. package/dist/cdp/ref_cache.d.ts +101 -0
  40. package/dist/cdp/ref_cache.js +234 -0
  41. package/dist/cdp/retry.d.ts +5 -0
  42. package/dist/cdp/retry.js +39 -0
  43. package/dist/cdp/retry_enhanced.d.ts +44 -0
  44. package/dist/cdp/retry_enhanced.js +102 -0
  45. package/dist/cdp/security.d.ts +50 -0
  46. package/dist/cdp/security.js +237 -0
  47. package/dist/cdp/verify.d.ts +39 -0
  48. package/dist/cdp/verify.js +143 -0
  49. package/dist/cdp/walker.d.ts +91 -0
  50. package/dist/cdp/walker.js +896 -0
  51. package/dist/cdp/walker.test.d.ts +2 -0
  52. package/dist/cdp/walker.test.js +172 -0
  53. package/dist/cdp/websocket_transport.d.ts +15 -0
  54. package/dist/cdp/websocket_transport.js +118 -0
  55. package/dist/cli/plugin.d.ts +9 -0
  56. package/dist/cli/plugin.js +1134 -0
  57. package/dist/cli/updater.d.ts +1 -0
  58. package/dist/cli/updater.js +72 -0
  59. package/dist/config/loader.d.ts +120 -0
  60. package/dist/config/loader.js +704 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +1248 -0
  63. package/dist/intelligence/hints.d.ts +11 -0
  64. package/dist/intelligence/hints.js +39 -0
  65. package/dist/intelligence/intent.d.ts +33 -0
  66. package/dist/intelligence/intent.js +171 -0
  67. package/dist/intelligence/sessionMemory.d.ts +66 -0
  68. package/dist/intelligence/sessionMemory.js +284 -0
  69. package/dist/intelligence/sessionMemory.test.d.ts +2 -0
  70. package/dist/intelligence/sessionMemory.test.js +62 -0
  71. package/dist/intelligence/trail.d.ts +64 -0
  72. package/dist/intelligence/trail.js +233 -0
  73. package/dist/intelligence/trail.test.d.ts +2 -0
  74. package/dist/intelligence/trail.test.js +110 -0
  75. package/dist/intelligence/vision.d.ts +24 -0
  76. package/dist/intelligence/vision.js +151 -0
  77. package/dist/lifecycle/mission_manager.d.ts +16 -0
  78. package/dist/lifecycle/mission_manager.js +44 -0
  79. package/dist/overlay/bubble.d.ts +16 -0
  80. package/dist/overlay/bubble.js +37 -0
  81. package/dist/overlay/ghost.d.ts +35 -0
  82. package/dist/overlay/ghost.js +508 -0
  83. package/dist/overlay/injector.d.ts +22 -0
  84. package/dist/overlay/injector.js +198 -0
  85. package/dist/plugin/interface.d.ts +134 -0
  86. package/dist/plugin/interface.js +1 -0
  87. package/dist/plugin/loader.d.ts +18 -0
  88. package/dist/plugin/loader.js +155 -0
  89. package/dist/plugin/registry.d.ts +50 -0
  90. package/dist/plugin/registry.js +234 -0
  91. package/dist/security/blacklist.d.ts +24 -0
  92. package/dist/security/blacklist.js +80 -0
  93. package/dist/security/filter.d.ts +2 -0
  94. package/dist/security/filter.js +20 -0
  95. package/dist/security/guard.d.ts +8 -0
  96. package/dist/security/guard.js +35 -0
  97. package/dist/security/ratelimit.d.ts +11 -0
  98. package/dist/security/ratelimit.js +57 -0
  99. package/dist/security/secrets.d.ts +75 -0
  100. package/dist/security/secrets.js +365 -0
  101. package/dist/sensors/actionability.d.ts +81 -0
  102. package/dist/sensors/actionability.js +303 -0
  103. package/dist/sensors/guard.d.ts +25 -0
  104. package/dist/sensors/guard.js +168 -0
  105. package/dist/sensors/mutations.d.ts +23 -0
  106. package/dist/sensors/mutations.js +93 -0
  107. package/dist/sensors/network.d.ts +67 -0
  108. package/dist/sensors/network.js +193 -0
  109. package/dist/sensors/operation_monitor.d.ts +80 -0
  110. package/dist/sensors/operation_monitor.js +203 -0
  111. package/dist/sensors/pacing.d.ts +44 -0
  112. package/dist/sensors/pacing.js +111 -0
  113. package/dist/sensors/thoughts.d.ts +2 -0
  114. package/dist/sensors/thoughts.js +59 -0
  115. package/dist/sensors/wait.d.ts +12 -0
  116. package/dist/sensors/wait.js +44 -0
  117. package/dist/server.d.ts +7 -0
  118. package/dist/server.js +1687 -0
  119. package/dist/state/agent.d.ts +36 -0
  120. package/dist/state/agent.js +101 -0
  121. package/dist/state/cache.d.ts +16 -0
  122. package/dist/state/cache.js +92 -0
  123. package/dist/state/cache.test.d.ts +2 -0
  124. package/dist/state/cache.test.js +76 -0
  125. package/dist/state/delta.d.ts +71 -0
  126. package/dist/state/delta.js +234 -0
  127. package/dist/state/delta.test.d.ts +2 -0
  128. package/dist/state/delta.test.js +279 -0
  129. package/dist/state/groups.d.ts +15 -0
  130. package/dist/state/groups.js +80 -0
  131. package/dist/state/session.d.ts +30 -0
  132. package/dist/state/session.js +155 -0
  133. package/dist/tools/accessibility.d.ts +14 -0
  134. package/dist/tools/accessibility.js +32 -0
  135. package/dist/tools/ask.d.ts +14 -0
  136. package/dist/tools/ask.js +49 -0
  137. package/dist/tools/automation.d.ts +36 -0
  138. package/dist/tools/automation.js +218 -0
  139. package/dist/tools/browser_detect.d.ts +13 -0
  140. package/dist/tools/browser_detect.js +73 -0
  141. package/dist/tools/burst.d.ts +35 -0
  142. package/dist/tools/burst.js +344 -0
  143. package/dist/tools/canvas.d.ts +25 -0
  144. package/dist/tools/canvas.js +132 -0
  145. package/dist/tools/captcha.d.ts +38 -0
  146. package/dist/tools/captcha.js +209 -0
  147. package/dist/tools/click.d.ts +36 -0
  148. package/dist/tools/click.js +561 -0
  149. package/dist/tools/click_and_wait.d.ts +33 -0
  150. package/dist/tools/click_and_wait.js +149 -0
  151. package/dist/tools/clipboard.d.ts +29 -0
  152. package/dist/tools/clipboard.js +279 -0
  153. package/dist/tools/console.d.ts +21 -0
  154. package/dist/tools/console.js +107 -0
  155. package/dist/tools/cookies.d.ts +67 -0
  156. package/dist/tools/cookies.js +121 -0
  157. package/dist/tools/design_extract.d.ts +109 -0
  158. package/dist/tools/design_extract.js +641 -0
  159. package/dist/tools/dialog.d.ts +24 -0
  160. package/dist/tools/dialog.js +109 -0
  161. package/dist/tools/drag.d.ts +15 -0
  162. package/dist/tools/drag.js +29 -0
  163. package/dist/tools/emulation.d.ts +33 -0
  164. package/dist/tools/emulation.js +69 -0
  165. package/dist/tools/eval.d.ts +20 -0
  166. package/dist/tools/eval.js +92 -0
  167. package/dist/tools/fill.d.ts +41 -0
  168. package/dist/tools/fill.js +667 -0
  169. package/dist/tools/find.d.ts +30 -0
  170. package/dist/tools/find.js +60 -0
  171. package/dist/tools/geo.d.ts +19 -0
  172. package/dist/tools/geo.js +29 -0
  173. package/dist/tools/github/analyze.d.ts +4 -0
  174. package/dist/tools/github/analyze.js +113 -0
  175. package/dist/tools/github/cache.d.ts +21 -0
  176. package/dist/tools/github/cache.js +28 -0
  177. package/dist/tools/github/index.d.ts +4 -0
  178. package/dist/tools/github/index.js +4 -0
  179. package/dist/tools/github/issues.d.ts +9 -0
  180. package/dist/tools/github/issues.js +88 -0
  181. package/dist/tools/github/pr.d.ts +5 -0
  182. package/dist/tools/github/pr.js +88 -0
  183. package/dist/tools/github/rateLimiter.d.ts +4 -0
  184. package/dist/tools/github/rateLimiter.js +46 -0
  185. package/dist/tools/github/read.d.ts +34 -0
  186. package/dist/tools/github/read.js +74 -0
  187. package/dist/tools/github/repoContext.d.ts +20 -0
  188. package/dist/tools/github/repoContext.js +129 -0
  189. package/dist/tools/highlight.d.ts +20 -0
  190. package/dist/tools/highlight.js +140 -0
  191. package/dist/tools/hover.d.ts +19 -0
  192. package/dist/tools/hover.js +57 -0
  193. package/dist/tools/inspect.d.ts +17 -0
  194. package/dist/tools/inspect.js +125 -0
  195. package/dist/tools/integration.test.d.ts +2 -0
  196. package/dist/tools/integration.test.js +96 -0
  197. package/dist/tools/key.d.ts +25 -0
  198. package/dist/tools/key.js +173 -0
  199. package/dist/tools/macro_compiler.d.ts +7 -0
  200. package/dist/tools/macro_compiler.js +82 -0
  201. package/dist/tools/mouse.d.ts +21 -0
  202. package/dist/tools/mouse.js +62 -0
  203. package/dist/tools/navigate.d.ts +16 -0
  204. package/dist/tools/navigate.js +188 -0
  205. package/dist/tools/network_intercept.d.ts +17 -0
  206. package/dist/tools/network_intercept.js +151 -0
  207. package/dist/tools/office/index.d.ts +9 -0
  208. package/dist/tools/office/index.js +7 -0
  209. package/dist/tools/office/list.d.ts +15 -0
  210. package/dist/tools/office/list.js +41 -0
  211. package/dist/tools/office/parse.d.ts +20 -0
  212. package/dist/tools/office/parse.js +172 -0
  213. package/dist/tools/pdf.d.ts +22 -0
  214. package/dist/tools/pdf.js +42 -0
  215. package/dist/tools/performance.d.ts +12 -0
  216. package/dist/tools/performance.js +47 -0
  217. package/dist/tools/performance_test.d.ts +70 -0
  218. package/dist/tools/performance_test.js +128 -0
  219. package/dist/tools/plan.d.ts +39 -0
  220. package/dist/tools/plan.js +161 -0
  221. package/dist/tools/pointer.d.ts +35 -0
  222. package/dist/tools/pointer.js +522 -0
  223. package/dist/tools/read.d.ts +30 -0
  224. package/dist/tools/read.js +488 -0
  225. package/dist/tools/recording.d.ts +45 -0
  226. package/dist/tools/recording.js +124 -0
  227. package/dist/tools/response.d.ts +129 -0
  228. package/dist/tools/response.js +5 -0
  229. package/dist/tools/scrape.d.ts +24 -0
  230. package/dist/tools/scrape.js +140 -0
  231. package/dist/tools/screenshot.d.ts +40 -0
  232. package/dist/tools/screenshot.js +79 -0
  233. package/dist/tools/scroll.d.ts +23 -0
  234. package/dist/tools/scroll.js +196 -0
  235. package/dist/tools/select.d.ts +13 -0
  236. package/dist/tools/select.js +217 -0
  237. package/dist/tools/snapshot.d.ts +27 -0
  238. package/dist/tools/snapshot.js +99 -0
  239. package/dist/tools/storage.d.ts +18 -0
  240. package/dist/tools/storage.js +29 -0
  241. package/dist/tools/tabs.d.ts +77 -0
  242. package/dist/tools/tabs.js +342 -0
  243. package/dist/tools/thought.d.ts +16 -0
  244. package/dist/tools/thought.js +79 -0
  245. package/dist/tools/type.d.ts +65 -0
  246. package/dist/tools/type.js +1061 -0
  247. package/dist/tools/type_secret.d.ts +23 -0
  248. package/dist/tools/type_secret.js +229 -0
  249. package/dist/tools/upload.d.ts +19 -0
  250. package/dist/tools/upload.js +103 -0
  251. package/dist/tools/video_compiler.d.ts +24 -0
  252. package/dist/tools/video_compiler.js +199 -0
  253. package/dist/tools/viewport.d.ts +44 -0
  254. package/dist/tools/viewport.js +230 -0
  255. package/dist/tools/wait.d.ts +19 -0
  256. package/dist/tools/wait.js +202 -0
  257. package/dist/tools/wait_for.d.ts +23 -0
  258. package/dist/tools/wait_for.js +30 -0
  259. package/dist/tools/windows.d.ts +22 -0
  260. package/dist/tools/windows.js +57 -0
  261. package/dist/ui/index.d.ts +1 -0
  262. package/dist/ui/index.js +12 -0
  263. package/docs/DEV_MODE.md +68 -0
  264. package/docs/DOCUMENTATION.md +532 -0
  265. package/docs/PLUGINS.md +538 -0
  266. package/docs/TOOLS.md +104 -0
  267. package/docs/dev_plugin.md +310 -0
  268. package/ext-dist/background.js +467 -0
  269. package/ext-dist/content.js +3089 -0
  270. package/ext-dist/dashboard.html +583 -0
  271. package/ext-dist/hud/hud.js +73 -0
  272. package/ext-dist/hud/index.html +20 -0
  273. package/ext-dist/manifest.json +77 -0
  274. package/ext-dist/sidepanel/sidepanel.js +25 -0
  275. package/ext-dist/sidepanel.html +547 -0
  276. package/ext-dist/styles.css +218 -0
  277. package/icon.png +0 -0
  278. package/mcp.json +796 -0
  279. package/package.json +113 -0
  280. package/server.json +23 -0
  281. package/smithery.yaml +11 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Weavetab — cdp/extension.ts
3
+ * Extension detection and health-check layer.
4
+ * All extension-dependent tools should call requireExtension() before proceeding.
5
+ * Weavetab by fy2ne
6
+ */
7
+ import CDP from "chrome-remote-interface";
8
+ /**
9
+ * Check whether the Weavetab extension is loaded in the current page.
10
+ * Uses DOM markers instead of JS globals because content scripts run in
11
+ * an ISOLATED world (MV3) — window.__Weavetab is invisible from MAIN world.
12
+ */
13
+ export declare function isExtensionLoaded(session: CDP.Client): Promise<boolean>;
14
+ /**
15
+ * Throws a clear, actionable error if the extension is not loaded.
16
+ */
17
+ export declare function requireExtension(session: CDP.Client): Promise<void>;
18
+ /**
19
+ * Full health check — which extension features are available.
20
+ */
21
+ export declare function extensionHealth(session: CDP.Client): Promise<{
22
+ loaded: boolean;
23
+ version?: string;
24
+ features: string[];
25
+ }>;
26
+ /**
27
+ * Suppress all visual overlays/borders the extension draws on interactive elements.
28
+ * Call this before taking screenshots or when clean rendering is needed.
29
+ * Default behavior for browser_map — overlays should only appear via browser_highlight.
30
+ */
31
+ export declare function suppressOverlays(session: CDP.Client): Promise<void>;
32
+ /**
33
+ * Re-enable visual overlays for debugging purposes.
34
+ * Only call this if user explicitly passes show_overlays: true to browser_map.
35
+ */
36
+ export declare function restoreOverlays(session: CDP.Client): Promise<void>;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Check whether the Weavetab extension is loaded in the current page.
3
+ * Uses DOM markers instead of JS globals because content scripts run in
4
+ * an ISOLATED world (MV3) — window.__Weavetab is invisible from MAIN world.
5
+ */
6
+ export async function isExtensionLoaded(session) {
7
+ try {
8
+ const { result } = await session.Runtime.evaluate({
9
+ expression: `!!(
10
+ document.getElementById('wt-extension-marker') ||
11
+ document.querySelector('weavetab-host') ||
12
+ document.querySelector('[data-wt-internal]') ||
13
+ document.getElementById('__Weavetab_cursor_host') ||
14
+ document.getElementById('wt-ui-styles')
15
+ )`,
16
+ returnByValue: true,
17
+ });
18
+ return result?.value === true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ /**
25
+ * Throws a clear, actionable error if the extension is not loaded.
26
+ */
27
+ export async function requireExtension(session) {
28
+ let loaded = await isExtensionLoaded(session);
29
+ if (!loaded) {
30
+ try {
31
+ // Auto-recovery: if marker is missing, the page might be in a broken state.
32
+ // Attempt a single reload before failing the tool call.
33
+ await session.Page.reload({ ignoreCache: false });
34
+ await new Promise(r => setTimeout(r, 2000)); // wait for content script injection
35
+ loaded = await isExtensionLoaded(session);
36
+ }
37
+ catch { /* ignore reload errors */ }
38
+ }
39
+ if (!loaded) {
40
+ throw new Error("[!] Weavetab Extension not detected on this page. " +
41
+ "Make sure the extension is installed (from ext-dist/) and the page has been loaded/refreshed. " +
42
+ "Some tools require the extension for full functionality.");
43
+ }
44
+ }
45
+ /**
46
+ * Full health check — which extension features are available.
47
+ */
48
+ export async function extensionHealth(session) {
49
+ try {
50
+ // Use DOM markers + fallback JS checks via postMessage bridge
51
+ const loaded = await isExtensionLoaded(session);
52
+ const { result } = await session.Runtime.evaluate({
53
+ expression: `(function() {
54
+ const features = [];
55
+ if (document.querySelector('weavetab-host') || document.getElementById('__Weavetab_cursor_host')) features.push('cursor');
56
+ if (document.querySelector('[data-wt-internal]') || document.getElementById('wt-ui-styles')) features.push('styles');
57
+ if (document.querySelector('.wt-bubble')) features.push('bubble');
58
+ if (document.getElementById('wt-plan-panel')) features.push('plan');
59
+ return JSON.stringify({
60
+ loaded: ${loaded},
61
+ features
62
+ });
63
+ })()`,
64
+ returnByValue: true,
65
+ });
66
+ if (result?.value) {
67
+ return JSON.parse(result.value);
68
+ }
69
+ }
70
+ catch { /* ignore */ }
71
+ return { loaded: false, features: [] };
72
+ }
73
+ /**
74
+ * Suppress all visual overlays/borders the extension draws on interactive elements.
75
+ * Call this before taking screenshots or when clean rendering is needed.
76
+ * Default behavior for browser_map — overlays should only appear via browser_highlight.
77
+ */
78
+ export async function suppressOverlays(session) {
79
+ try {
80
+ await session.Runtime.evaluate({
81
+ expression: `(function() {
82
+ // Remove any existing Weavetab overlay elements
83
+ document.querySelectorAll('[data-Weavetab-overlay]').forEach(el => el.remove());
84
+ // Hide fallback visual elements
85
+ var sr = document.getElementById('Weavetab-root');
86
+ if (sr && sr.shadowRoot) {
87
+ var els = sr.shadowRoot.querySelectorAll('#wt-fallback-cursor, #wt-fallback-hud, #wt-fallback-thought');
88
+ els.forEach(function(el){ el.style.display = 'none'; });
89
+ }
90
+ // Post a message for the content script (isolated world)
91
+ window.postMessage({ type: 'Weavetab_SUPPRESS_VISUALS', suppress: true }, '*');
92
+ })()`,
93
+ returnByValue: false,
94
+ });
95
+ }
96
+ catch { /* non-fatal */ }
97
+ }
98
+ /**
99
+ * Re-enable visual overlays for debugging purposes.
100
+ * Only call this if user explicitly passes show_overlays: true to browser_map.
101
+ */
102
+ export async function restoreOverlays(session) {
103
+ try {
104
+ await session.Runtime.evaluate({
105
+ expression: `(function() {
106
+ // Restore fallback visual elements
107
+ var sr = document.getElementById('Weavetab-root');
108
+ if (sr && sr.shadowRoot) {
109
+ var els = sr.shadowRoot.querySelectorAll('#wt-fallback-cursor, #wt-fallback-hud, #wt-fallback-thought');
110
+ els.forEach(function(el){ el.style.display = ''; });
111
+ }
112
+ window.postMessage({ type: 'Weavetab_SUPPRESS_VISUALS', suppress: false }, '*');
113
+ })()`,
114
+ returnByValue: false,
115
+ });
116
+ }
117
+ catch { /* non-fatal */ }
118
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ // File deprecated and replaced by bridge.ts
@@ -0,0 +1,11 @@
1
+ import CDP from "chrome-remote-interface";
2
+ /**
3
+ * sendToExtension is the only bridge to communicate from Node (CLI)
4
+ * to the Weavetab Chrome Extension via CDP executeScript.
5
+ * Messages are queued and batched with backpressure.
6
+ * Returns true if the message was queued, false if dropped.
7
+ */
8
+ export declare function sendToExtension(session: CDP.Client, message: object): Promise<boolean>;
9
+ export declare function getFromExtension<T = any>(session: CDP.Client, message: object, retries?: number): Promise<T | null>;
10
+ export declare function hideWeaveTabOverlay(session: CDP.Client): Promise<void>;
11
+ export declare function restoreWeaveTabOverlay(session: CDP.Client): Promise<void>;
@@ -0,0 +1,195 @@
1
+ import { logDebug } from "../audit/logger.js";
2
+ const MAX_QUEUE_SIZE = 50;
3
+ const BATCH_INTERVAL_MS = 10; // Reduced from 20ms for faster message delivery
4
+ const messageQueues = new Map();
5
+ const queueTimers = new Map();
6
+ async function drainQueue(session, targetId) {
7
+ const queue = messageQueues.get(targetId);
8
+ if (!queue || queue.length === 0)
9
+ return;
10
+ const batch = queue.splice(0, Math.min(queue.length, 10));
11
+ const messages = batch.map((q, i) => {
12
+ const msg = q.message;
13
+ // Add dedup _id using queue entry's id (unique) so content.js __wt_processedMsgIds works
14
+ if (typeof msg === 'object' && msg !== null) {
15
+ msg._id = q.id + (i > 0 ? '-' + i : '');
16
+ }
17
+ return msg;
18
+ });
19
+ try {
20
+ const msgData = JSON.stringify(messages.length === 1 ? messages[0] : messages);
21
+ await session.Runtime.evaluate({
22
+ expression: `
23
+ try {
24
+ window.postMessage({ type: 'Weavetab-msg-in', data: ${msgData} }, '*');
25
+ } catch(e) {}
26
+ `
27
+ });
28
+ batch.forEach(q => q.resolve(true));
29
+ }
30
+ catch (e) {
31
+ logDebug(`[Weavetab] Failed to send batch (${batch.length} msgs)`, e);
32
+ batch.forEach(q => {
33
+ q.attempts++;
34
+ if (q.attempts < 3) {
35
+ queue.push(q); // re-queue for retry
36
+ }
37
+ else {
38
+ q.resolve(false);
39
+ }
40
+ });
41
+ }
42
+ // Schedule next drain if messages remain
43
+ if (queue.length > 0) {
44
+ const timer = setTimeout(() => drainQueue(session, targetId), BATCH_INTERVAL_MS);
45
+ queueTimers.set(targetId, timer);
46
+ }
47
+ else {
48
+ const t = queueTimers.get(targetId);
49
+ if (t) {
50
+ clearTimeout(t);
51
+ queueTimers.delete(targetId);
52
+ }
53
+ }
54
+ }
55
+ function enqueueMessage(session, targetId, message) {
56
+ return new Promise((resolve) => {
57
+ let queue = messageQueues.get(targetId);
58
+ if (!queue) {
59
+ queue = [];
60
+ messageQueues.set(targetId, queue);
61
+ }
62
+ // Backpressure: if queue is full, reject oldest message to make room
63
+ if (queue.length >= MAX_QUEUE_SIZE) {
64
+ const dropped = queue.shift();
65
+ if (dropped) {
66
+ logDebug(`[Weavetab] Backpressure: dropping oldest message (${dropped.id})`);
67
+ dropped.resolve(false);
68
+ }
69
+ }
70
+ const qMsg = {
71
+ id: Math.random().toString(36).slice(2, 10),
72
+ message,
73
+ resolve,
74
+ timestamp: Date.now(),
75
+ attempts: 0,
76
+ };
77
+ queue.push(qMsg);
78
+ // Start drain timer if not already running
79
+ if (!queueTimers.has(targetId)) {
80
+ const timer = setTimeout(() => drainQueue(session, targetId), BATCH_INTERVAL_MS);
81
+ queueTimers.set(targetId, timer);
82
+ }
83
+ });
84
+ }
85
+ // ─── Public API ───────────────────────────────────────────────────────────────
86
+ function getTargetId(session) {
87
+ return session._targetId || session.targetId || "default";
88
+ }
89
+ /**
90
+ * sendToExtension is the only bridge to communicate from Node (CLI)
91
+ * to the Weavetab Chrome Extension via CDP executeScript.
92
+ * Messages are queued and batched with backpressure.
93
+ * Returns true if the message was queued, false if dropped.
94
+ */
95
+ export async function sendToExtension(session, message) {
96
+ try {
97
+ const targetId = getTargetId(session);
98
+ return await enqueueMessage(session, targetId, message);
99
+ }
100
+ catch (e) {
101
+ logDebug("[Weavetab] Failed to sendToExtension", e);
102
+ return false;
103
+ }
104
+ }
105
+ export async function getFromExtension(session, message, retries = 0) {
106
+ for (let attempt = 0; attempt <= retries; attempt++) {
107
+ try {
108
+ const { result } = await session.Runtime.evaluate({
109
+ expression: `new Promise((resolve) => {
110
+ try {
111
+ const id = Math.random().toString();
112
+ const listener = (e) => {
113
+ if (e.detail && e.detail.id === id) {
114
+ window.removeEventListener('Weavetab-msg-out', listener);
115
+ resolve(JSON.stringify(e.detail.data));
116
+ }
117
+ };
118
+ window.addEventListener('Weavetab-msg-out', listener);
119
+ window.dispatchEvent(new CustomEvent('Weavetab-msg-in', { detail: Object.assign({}, ${JSON.stringify(message)}, { id }) }));
120
+
121
+ // Fallback timeout — 100ms fail-fast (reduced from 150ms)
122
+ setTimeout(() => {
123
+ window.removeEventListener('Weavetab-msg-out', listener);
124
+ resolve("null");
125
+ }, 100);
126
+ } catch (e) {
127
+ resolve("null");
128
+ }
129
+ })`,
130
+ awaitPromise: true,
131
+ returnByValue: true
132
+ });
133
+ if (result && result.value && result.value !== "null") {
134
+ return JSON.parse(result.value);
135
+ }
136
+ // Got null — if we have retries left, try again
137
+ if (attempt < retries) {
138
+ logDebug(`[Weavetab] Extension did not respond (attempt ${attempt + 1}/${retries + 1}), retrying...`);
139
+ continue;
140
+ }
141
+ }
142
+ catch (e) {
143
+ if (attempt < retries) {
144
+ logDebug(`[Weavetab] getFromExtension error (attempt ${attempt + 1}/${retries + 1}): ${e instanceof Error ? e.message : e}`);
145
+ continue;
146
+ }
147
+ logDebug("[Weavetab] Failed to getFromExtension after retries", e);
148
+ }
149
+ }
150
+ return null;
151
+ }
152
+ export async function hideWeaveTabOverlay(session) {
153
+ await session.Runtime.evaluate({
154
+ expression: `(function(){
155
+ var existing = document.getElementById('__wt_hide_overlay_styles');
156
+ if (existing) return; // already hidden
157
+ var style = document.createElement('style');
158
+ style.id = '__wt_hide_overlay_styles';
159
+ // Cover ALL Weavetab overlay elements so they never appear in screenshots/PDFs/snapshots:
160
+ // - [id^="Weavetab-"] : Weavetab-root shadow host, any legacy Weavetab- elements
161
+ // - [id^="__Weavetab_"] : __Weavetab_cursor_host (ghost cursor)
162
+ // - [id^="wt-"] : wt-* legacy element IDs
163
+ // - [data-wt-internal] : HUD iframe host (closed shadow DOM mount)
164
+ // - [data-wt-ignore] : any element flagged as agent-invisible
165
+ // - .wt-bubble : thought bubble / action text popups
166
+ // - #__wt_ask_bridge : question input bridge element
167
+ // - #wt-plan-dashboard : mission plan dashboard
168
+ // - #wt-debug-overlay : debug overlay panel
169
+ // - #wt-main-container : WeaveTab OS panel (todos + chat)
170
+ style.textContent = [
171
+ '[id^="Weavetab-"]',
172
+ '[id^="__Weavetab_"]',
173
+ '[id^="wt-"]',
174
+ '[data-wt-internal]',
175
+ '[data-wt-ignore]',
176
+ '.wt-bubble',
177
+ '#__wt_ask_bridge',
178
+ '#wt-plan-dashboard',
179
+ '#wt-debug-overlay',
180
+ '#wt-main-container',
181
+ ].join(', ') + ' { display: none !important; visibility: hidden !important; opacity: 0 !important; }';
182
+ document.head.appendChild(style);
183
+ })()`,
184
+ returnByValue: false,
185
+ }).catch(() => { });
186
+ }
187
+ export async function restoreWeaveTabOverlay(session) {
188
+ await session.Runtime.evaluate({
189
+ expression: `(function(){
190
+ var style = document.getElementById('__wt_hide_overlay_styles');
191
+ if (style) style.remove();
192
+ })()`,
193
+ returnByValue: false,
194
+ }).catch(() => { });
195
+ }
@@ -0,0 +1,12 @@
1
+ declare class LabelCache {
2
+ private maxEntries;
3
+ private cache;
4
+ constructor(maxEntries?: number);
5
+ private getKey;
6
+ get(url: string, label: string): string | null;
7
+ set(url: string, label: string, ref: string): void;
8
+ invalidate(url: string): void;
9
+ private evict;
10
+ }
11
+ export declare const labelCache: LabelCache;
12
+ export {};
@@ -0,0 +1,51 @@
1
+ // src/cdp/label_cache.ts
2
+ // In-memory LRU cache to map labels to Ref IDs to speed up repeated browser_click calls
3
+ class LabelCache {
4
+ maxEntries;
5
+ cache;
6
+ constructor(maxEntries = 200) {
7
+ this.maxEntries = maxEntries;
8
+ this.cache = new Map();
9
+ }
10
+ getKey(url, label) {
11
+ return `${url}:::${label}`;
12
+ }
13
+ get(url, label) {
14
+ const key = this.getKey(url, label);
15
+ const entry = this.cache.get(key);
16
+ if (!entry)
17
+ return null;
18
+ // Update timestamp for LRU
19
+ entry.timestamp = Date.now();
20
+ return entry.ref;
21
+ }
22
+ set(url, label, ref) {
23
+ const key = this.getKey(url, label);
24
+ if (this.cache.size >= this.maxEntries) {
25
+ this.evict();
26
+ }
27
+ this.cache.set(key, { ref, timestamp: Date.now() });
28
+ }
29
+ invalidate(url) {
30
+ const prefix = `${url}:::`;
31
+ for (const key of this.cache.keys()) {
32
+ if (key.startsWith(prefix)) {
33
+ this.cache.delete(key);
34
+ }
35
+ }
36
+ }
37
+ evict() {
38
+ let oldestKey = null;
39
+ let oldestTime = Infinity;
40
+ for (const [key, entry] of this.cache.entries()) {
41
+ if (entry.timestamp < oldestTime) {
42
+ oldestTime = entry.timestamp;
43
+ oldestKey = key;
44
+ }
45
+ }
46
+ if (oldestKey) {
47
+ this.cache.delete(oldestKey);
48
+ }
49
+ }
50
+ }
51
+ export const labelCache = new LabelCache();
@@ -0,0 +1,33 @@
1
+ export interface LockData {
2
+ pid?: number;
3
+ port: number;
4
+ profileDir: string;
5
+ browserPath: string;
6
+ mode: "session" | "attach";
7
+ pending?: boolean;
8
+ timestamp?: number;
9
+ fingerprint?: {
10
+ launchTimestamp: number;
11
+ userAgent?: string;
12
+ cdpVersion?: string;
13
+ };
14
+ }
15
+ declare function acquireMutex(): Promise<() => Promise<void>>;
16
+ export declare function writeLockSafe(data: LockData): Promise<void>;
17
+ export declare function writeLock(data: LockData): Promise<void>;
18
+ export declare function readLock(): LockData | null;
19
+ export declare function clearLock(): void;
20
+ /**
21
+ * Safe variant: only clears the lock if it was written by THIS process (process.pid).
22
+ * Prevents a second MCP server instance from wiping the first instance's browser lock
23
+ * during its retry loop, which would allow a race condition where two browsers spawn.
24
+ */
25
+ export declare function clearLockIfOwned(): void;
26
+ export declare function isProcessRunning(pid: number): boolean;
27
+ /**
28
+ * Two-factor lock validation: PID alive AND port accepting TCP connections.
29
+ * Prevents PID-reuse false-positives after a crash.
30
+ */
31
+ export declare function isLockValid(lock: LockData): Promise<boolean>;
32
+ export declare function isLockPending(lock: LockData): boolean;
33
+ export { acquireMutex };
@@ -0,0 +1,206 @@
1
+ import { logDebug } from "../audit/logger.js";
2
+ /**
3
+ * Weavetab — cdp/lock.ts
4
+ * Crash-safe, mutex-protected lock file for browser process tracking.
5
+ * Uses proper-lockfile for OS-level advisory locking to prevent race conditions.
6
+ * Weavetab by fy2ne
7
+ */
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import * as os from "node:os";
11
+ import * as net from "node:net";
12
+ import { lock } from "proper-lockfile";
13
+ const LOCK_DIR = path.join(os.homedir(), ".weavetab-system", "locks");
14
+ const LOCK_FILE = path.join(LOCK_DIR, ".weavetab.lock");
15
+ const MUTEX_FILE = path.join(LOCK_DIR, ".weavetab.lock.mutex");
16
+ // ─── In-process serialization queue ──────────────────────────────────────────
17
+ // When multiple MCP tool calls run in parallel inside the same Node process they
18
+ // all race to acquireMutex() simultaneously. The OS-level proper-lockfile handles
19
+ // cross-process contention, but within the same process concurrent callers share
20
+ // the same lockfile handle and can trigger "Lock is already released" errors.
21
+ // This queue serializes callers at the JS level before they touch the filesystem.
22
+ let _inProcessMutexQueue = Promise.resolve();
23
+ // Re-entrancy tracking. getOrConnect() acquires the mutex and then calls
24
+ // launchWithBridge(), which internally calls writeLock() → acquireMutex()
25
+ // again (nested). The serialization queue would block the nested call on the
26
+ // outer slot, which only releases after the launch finishes — a self-deadlock.
27
+ // Track how many regions this process currently holds and only touch the OS
28
+ // file lock + queue slot on the outermost acquisition / release.
29
+ let _heldDepth = 0;
30
+ let _heldFileRelease = null;
31
+ let _heldSlotResolve = null;
32
+ async function acquireMutex() {
33
+ // Re-entrant fast path: this process already holds the mutex.
34
+ if (_heldDepth > 0) {
35
+ _heldDepth++;
36
+ return async () => {
37
+ _heldDepth--;
38
+ };
39
+ }
40
+ // Enqueue this caller — it will only proceed once all prior callers finish.
41
+ let resolveSlot;
42
+ const slot = new Promise(resolve => { resolveSlot = resolve; });
43
+ const prevQueue = _inProcessMutexQueue;
44
+ _inProcessMutexQueue = _inProcessMutexQueue.then(() => slot);
45
+ await prevQueue; // Wait for all prior callers to fully release
46
+ fs.mkdirSync(LOCK_DIR, { recursive: true });
47
+ if (!fs.existsSync(MUTEX_FILE)) {
48
+ try {
49
+ fs.writeFileSync(MUTEX_FILE, String(process.pid), "utf-8");
50
+ }
51
+ catch { /* race/non-fatal */ }
52
+ }
53
+ let fileRelease = null;
54
+ try {
55
+ fileRelease = await lock(MUTEX_FILE, {
56
+ retries: {
57
+ retries: 25,
58
+ minTimeout: 200,
59
+ maxTimeout: 1000,
60
+ factor: 1.1,
61
+ },
62
+ });
63
+ // Write current PID to mutex file for diagnostics/HUD
64
+ try {
65
+ fs.writeFileSync(MUTEX_FILE, String(process.pid), "utf-8");
66
+ }
67
+ catch { /* non-fatal */ }
68
+ }
69
+ catch (err) {
70
+ resolveSlot(); // Unblock the queue even on failure
71
+ throw new Error(`[Weavetab] Could not acquire launch mutex after retries: ${err?.message || err}`);
72
+ }
73
+ // Mark this process as the current holder so nested calls skip the queue.
74
+ _heldDepth = 1;
75
+ _heldFileRelease = fileRelease;
76
+ _heldSlotResolve = resolveSlot;
77
+ // Return a release function that releases both the file lock and the in-process slot
78
+ return async () => {
79
+ _heldDepth--;
80
+ if (_heldDepth > 0)
81
+ return; // nested regions still active
82
+ _heldDepth = 0;
83
+ const rel = _heldFileRelease;
84
+ const slotResolve = _heldSlotResolve;
85
+ _heldFileRelease = null;
86
+ _heldSlotResolve = null;
87
+ try {
88
+ if (rel)
89
+ await rel();
90
+ }
91
+ catch { /* already released — safe to ignore */ }
92
+ if (slotResolve)
93
+ slotResolve(); // Let the next queued caller proceed
94
+ };
95
+ }
96
+ // ─── Public API ───────────────────────────────────────────────────────────────
97
+ export async function writeLockSafe(data) {
98
+ const release = await acquireMutex();
99
+ try {
100
+ fs.mkdirSync(LOCK_DIR, { recursive: true });
101
+ fs.writeFileSync(LOCK_FILE, JSON.stringify(data, null, 2), "utf-8");
102
+ }
103
+ finally {
104
+ await release();
105
+ }
106
+ }
107
+ export async function writeLock(data) {
108
+ await writeLockSafe(data);
109
+ }
110
+ export function readLock() {
111
+ if (!fs.existsSync(LOCK_FILE))
112
+ return null;
113
+ try {
114
+ return JSON.parse(fs.readFileSync(LOCK_FILE, "utf-8"));
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ export function clearLock() {
121
+ try {
122
+ if (fs.existsSync(LOCK_FILE))
123
+ fs.unlinkSync(LOCK_FILE);
124
+ }
125
+ catch (e) {
126
+ logDebug(`[Weavetab] [!] Failed to clear lock file: ${e}`);
127
+ }
128
+ try {
129
+ if (fs.existsSync(MUTEX_FILE))
130
+ fs.unlinkSync(MUTEX_FILE);
131
+ }
132
+ catch { /* ignore */ }
133
+ try {
134
+ const lockDir = MUTEX_FILE + ".lock";
135
+ if (fs.existsSync(lockDir))
136
+ fs.rmSync(lockDir, { recursive: true, force: true });
137
+ }
138
+ catch { /* ignore */ }
139
+ }
140
+ /**
141
+ * Safe variant: only clears the lock if it was written by THIS process (process.pid).
142
+ * Prevents a second MCP server instance from wiping the first instance's browser lock
143
+ * during its retry loop, which would allow a race condition where two browsers spawn.
144
+ */
145
+ export function clearLockIfOwned() {
146
+ try {
147
+ const lock = readLock();
148
+ if (!lock || lock.pid !== process.pid) {
149
+ logDebug(`[Weavetab] clearLockIfOwned: skipping — lock owned by PID ${lock?.pid ?? 'none'}, this PID is ${process.pid}`);
150
+ return;
151
+ }
152
+ clearLock();
153
+ }
154
+ catch (e) {
155
+ logDebug(`[Weavetab] clearLockIfOwned error: ${e}`);
156
+ }
157
+ }
158
+ export function isProcessRunning(pid) {
159
+ try {
160
+ process.kill(pid, 0);
161
+ return true;
162
+ }
163
+ catch {
164
+ return false;
165
+ }
166
+ }
167
+ /**
168
+ * Two-factor lock validation: PID alive AND port accepting TCP connections.
169
+ * Prevents PID-reuse false-positives after a crash.
170
+ */
171
+ export async function isLockValid(lock) {
172
+ // Factor 1: PID alive
173
+ if (lock.pid && !isProcessRunning(lock.pid)) {
174
+ logDebug(`[Weavetab CDP] Lock invalid: Process ${lock.pid} is not running.`);
175
+ return false;
176
+ }
177
+ // Factor 2: TCP port accepting connections
178
+ return new Promise((resolve) => {
179
+ const socket = new net.Socket();
180
+ const timeout = setTimeout(() => {
181
+ logDebug(`[Weavetab CDP] Lock invalid: Timeout connecting to port ${lock.port}.`);
182
+ socket.destroy();
183
+ resolve(false);
184
+ }, 1000); // Reduced timeout to 1s for faster validation
185
+ socket.connect(lock.port, "127.0.0.1", () => {
186
+ clearTimeout(timeout);
187
+ socket.destroy();
188
+ resolve(true);
189
+ });
190
+ socket.on("error", (err) => {
191
+ logDebug(`[Weavetab CDP] Lock invalid: Error connecting to port ${lock.port} - ${err.message}`);
192
+ clearTimeout(timeout);
193
+ resolve(false);
194
+ });
195
+ });
196
+ }
197
+ export function isLockPending(lock) {
198
+ if (!lock.pending)
199
+ return false;
200
+ // If it's been pending for more than 15 seconds, consider it stale/failed
201
+ if (lock.timestamp && Date.now() - lock.timestamp > 15000) {
202
+ return false;
203
+ }
204
+ return true;
205
+ }
206
+ export { acquireMutex };