@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,149 @@
1
+ // src/tools/click_and_wait.ts
2
+ // Consolidated tool: find element + click + wait for response in one CDP round-trip
3
+ // Eliminates the common pattern: browser_map → browser_click → browser_wait
4
+ import { buildActionMap, findBestMatch, safeSelector, toMarkdownTable } from "../cdp/walker.js";
5
+ import { recordAction, trackToolCall, detectStuckLoop } from "../intelligence/trail.js";
6
+ import { waitForDOMStable } from "../sensors/guard.js";
7
+ import { waitForNetworkIdle } from "../sensors/wait.js";
8
+ import { ghostMove, ghostClick } from "../overlay/ghost.js";
9
+ import { cacheActionMap, getCachedActionMap } from "../state/cache.js";
10
+ import { storeSnapshot, computeDelta } from "../state/delta.js";
11
+ import { sessionRegistry } from "../state/session.js";
12
+ import { getConfigAgentId } from "../config/loader.js";
13
+ import { operationMonitor } from "../sensors/operation_monitor.js";
14
+ /**
15
+ * Consolidated tool that combines element resolution, clicking, and waiting.
16
+ * Eliminates 2-3 tool calls in the common pattern: find → click → wait
17
+ */
18
+ export async function weaveClickAndWait(session, args, config) {
19
+ const { id, label, intent, x, y, waitAfter = 500, waitForNetwork = true, includeMap = false, fast = false, tab_id } = args;
20
+ const { Page, DOM, Input } = session;
21
+ const startMs = Date.now();
22
+ const targetId = sessionRegistry.getActive(getConfigAgentId(config))?.targetId ?? "default";
23
+ const operationId = `clickAndWait-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
24
+ operationMonitor.startOperation(operationId, 'browser_click_and_wait');
25
+ // Handle tab switching if tab_id is provided
26
+ let targetSession = session;
27
+ if (tab_id) {
28
+ const { connectToTab } = await import("../cdp/connector.js");
29
+ const client = await connectToTab(tab_id);
30
+ targetSession = client.session;
31
+ }
32
+ // Check for stuck loop before processing
33
+ const stuckCheck = detectStuckLoop();
34
+ if (stuckCheck.stuck) {
35
+ return {
36
+ success: false,
37
+ error_code: "STUCK_LOOP",
38
+ message: stuckCheck.diagnosis || "Agent is stuck in a loop",
39
+ suggestion: "Try a different approach or reset the loop counter",
40
+ };
41
+ }
42
+ // Get initial URL
43
+ const initialUrl = (await targetSession.Page.getFrameTree()).frameTree.frame.url;
44
+ // Build or get cached action map
45
+ let map = getCachedActionMap(targetId, initialUrl);
46
+ if (!map) {
47
+ map = await buildActionMap(targetSession, { lite: true }, targetId);
48
+ cacheActionMap(targetId, map);
49
+ }
50
+ await storeSnapshot(targetId, initialUrl, map.elements);
51
+ // Resolve element
52
+ let element;
53
+ if (id) {
54
+ element = map.elements.find((el) => el.id === id || el.id.endsWith(id));
55
+ }
56
+ if (!element && label) {
57
+ element = findBestMatch(map.elements, label);
58
+ }
59
+ if (!element && intent) {
60
+ element = findBestMatch(map.elements, intent);
61
+ }
62
+ if (!element && !x && !y) {
63
+ return {
64
+ success: false,
65
+ error_code: "ELEMENT_NOT_FOUND",
66
+ message: `Element not found with id="${id}", label="${label}", or intent="${intent}"`,
67
+ suggestion: "Try browser_map to see available elements, or use x/y coordinates",
68
+ };
69
+ }
70
+ const clickX = x ?? element?.x;
71
+ const clickY = y ?? element?.y;
72
+ const selector = element ? safeSelector(element) : undefined;
73
+ if (!clickX || !clickY) {
74
+ return {
75
+ success: false,
76
+ error_code: "NO_COORDINATES",
77
+ message: "Could not determine click coordinates",
78
+ suggestion: "Provide explicit x/y coordinates or ensure element has position data",
79
+ };
80
+ }
81
+ // Perform click with ghost move
82
+ await ghostMove(targetSession, clickX, clickY, config, element?.label || "target");
83
+ await ghostClick(targetSession, clickX, clickY, { pointerType: 'mouse' });
84
+ await new Promise(r => setTimeout(r, 30 + Math.random() * 40));
85
+ await targetSession.Input.dispatchMouseEvent({
86
+ type: 'mouseReleased',
87
+ x: clickX,
88
+ y: clickY,
89
+ button: 'left',
90
+ clickCount: 1,
91
+ pointerType: 'mouse'
92
+ });
93
+ // Wait for response
94
+ const waitStart = Date.now();
95
+ if (waitForNetwork) {
96
+ await Promise.all([
97
+ waitForDOMStable(targetSession, 50, 300),
98
+ waitForNetworkIdle(targetSession, waitAfter, waitAfter * 2).catch(() => { })
99
+ ]);
100
+ }
101
+ else {
102
+ await new Promise(r => setTimeout(r, waitAfter));
103
+ }
104
+ const waitedMs = Date.now() - waitStart;
105
+ // Check result
106
+ const finalUrl = (await targetSession.Page.getFrameTree()).frameTree.frame.url;
107
+ const urlChanged = finalUrl !== initialUrl;
108
+ let newMap = map;
109
+ let domChanged = false;
110
+ if (urlChanged || includeMap) {
111
+ newMap = await buildActionMap(targetSession, { lite: true }, targetId);
112
+ cacheActionMap(targetId, newMap);
113
+ const delta = computeDelta(targetId, initialUrl, newMap.elements);
114
+ domChanged = delta ? delta.added.length + delta.removed.length + delta.changed.length > 0 : urlChanged;
115
+ }
116
+ else {
117
+ const delta = computeDelta(targetId, initialUrl, map.elements);
118
+ domChanged = delta ? delta.added.length + delta.removed.length + delta.changed.length > 0 : false;
119
+ }
120
+ const executionMs = Date.now() - startMs;
121
+ // Track tool call
122
+ trackToolCall("browser_click_and_wait", element?.id || id || label || "coordinates", {
123
+ success: true,
124
+ urlChanged
125
+ });
126
+ recordAction({
127
+ timestamp: new Date().toISOString(),
128
+ tool: "browser_click_and_wait",
129
+ input: { ref: element?.id || id || label, waitAfter, waitForNetwork },
130
+ result: { success: true, urlChanged, domChanged, waitedMs },
131
+ });
132
+ operationMonitor.endOperation(operationId);
133
+ const result = {
134
+ success: true,
135
+ clicked: element?.label || "coordinates",
136
+ ref: element?.id || id || "coordinates",
137
+ execution_ms: executionMs,
138
+ tab_id: targetId,
139
+ url: finalUrl,
140
+ title: newMap?.title || "",
141
+ url_changed: urlChanged,
142
+ ...(includeMap ? { page_map: newMap ? toMarkdownTable(newMap.elements, finalUrl, newMap.title) : "" } : {}),
143
+ dom_changed: domChanged,
144
+ stuck_warning: false,
145
+ waited_ms: waitedMs,
146
+ network_idle: waitForNetwork,
147
+ };
148
+ return result;
149
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Weavetab — tools/clipboard.ts
3
+ * browser_clipboard: Read and write text + images to the system/browser clipboard.
4
+ * Uses synthetic mouse click to grant user activation before clipboard API calls.
5
+ * Weavetab by fy2ne
6
+ */
7
+ import CDP from "chrome-remote-interface";
8
+ import type { Config } from "../config/loader.js";
9
+ export interface ClipboardOptions {
10
+ action: "read" | "write";
11
+ /** Text content to write (required for text write, optional if dataUrl is provided) */
12
+ text?: string;
13
+ /** Base64 data URL for image write (e.g. "data:image/png;base64,iVBOR...") */
14
+ dataUrl?: string;
15
+ /** MIME type override (auto-detected from dataUrl by default) */
16
+ mimeType?: string;
17
+ /**
18
+ * When true, skips the (5,5) user-activation click before clipboard write.
19
+ * Set this when the calling context already has input focus (e.g. browser_type)
20
+ * to prevent blurring the focused field before Ctrl+V.
21
+ */
22
+ skipActivation?: boolean;
23
+ }
24
+ export declare function browserClipboard(session: CDP.Client, options: ClipboardOptions, _config: Config): Promise<{
25
+ success: boolean;
26
+ text?: string;
27
+ dataUrl?: string;
28
+ message: string;
29
+ }>;
@@ -0,0 +1,279 @@
1
+ import { ghostClick } from "../overlay/ghost.js";
2
+ export async function browserClipboard(session, options, _config) {
3
+ const { action, text, dataUrl, mimeType } = options;
4
+ // ── Dispatch a synthetic click to grant transient user activation ──
5
+ async function grantActivation() {
6
+ try {
7
+ await ghostClick(session, 5, 5);
8
+ await session.Input.dispatchMouseEvent({ type: "mouseReleased", x: 5, y: 5, button: "left", clickCount: 1 });
9
+ }
10
+ catch {
11
+ // Non-critical — proceed anyway
12
+ }
13
+ }
14
+ // Detect non-secure context (file://, http://) up front. The Clipboard API is
15
+ // undefined there, and raw CDP ops can hang or throw -32001. Surface a typed
16
+ // error instead.
17
+ async function isSecureContext() {
18
+ try {
19
+ const { result } = await session.Runtime.evaluate({
20
+ expression: "window.isSecureContext === true",
21
+ returnByValue: true,
22
+ timeout: 1500,
23
+ });
24
+ return result?.value === true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ if (action === "write") {
31
+ const hasText = text !== undefined;
32
+ const hasImage = dataUrl !== undefined;
33
+ if (!hasText && !hasImage) {
34
+ throw new Error("Provide 'text' and/or 'dataUrl' for write action");
35
+ }
36
+ // ── Grant user activation only for standalone clipboard writes ───────────
37
+ // When called from browser_type, the input field is already focused.
38
+ // Clicking (5,5) here would blur the field before Ctrl+V, causing the
39
+ // phantom-click + wipe + write-to-BODY issue.
40
+ if (!options.skipActivation) {
41
+ await grantActivation();
42
+ }
43
+ const secureCtx = await isSecureContext();
44
+ if (hasImage) {
45
+ if (!secureCtx) {
46
+ throw new Error("browser_clipboard: image write requires a secure context (https or chrome://). Current page is non-secure (file:// or http://).");
47
+ }
48
+ const mime = mimeType || (dataUrl.includes(';') ? dataUrl.split(';')[0].split(':')[1] : "image/png");
49
+ const res = await session.Runtime.evaluate({
50
+ expression: `(async function() {
51
+ try {
52
+ const resp = await fetch(${JSON.stringify(dataUrl)});
53
+ const blob = await resp.blob();
54
+ await navigator.clipboard.write([
55
+ new ClipboardItem({ ${JSON.stringify(mime)}: blob })
56
+ ]);
57
+ return "ok";
58
+ } catch(e) {
59
+ return "ERR:" + e.message;
60
+ }
61
+ })()`,
62
+ awaitPromise: true,
63
+ returnByValue: true
64
+ });
65
+ const val = res.result?.value;
66
+ if (val === "ok") {
67
+ return { success: true, message: hasText ? "Text and image copied to clipboard" : "Image copied to clipboard" };
68
+ }
69
+ throw new Error("Failed to write image to clipboard: " + (val || "unknown"));
70
+ }
71
+ // Text-only write
72
+ const res = await session.Runtime.evaluate({
73
+ expression: `(async function() {
74
+ try {
75
+ ${secureCtx ? `await navigator.clipboard.writeText(${JSON.stringify(text)});` : `throw new Error('non-secure');`}
76
+ return true;
77
+ } catch(e) {
78
+ const ta = document.createElement('textarea');
79
+ ta.value = ${JSON.stringify(text)};
80
+ ta.style.position = 'fixed'; ta.style.opacity = '0';
81
+ document.body.appendChild(ta);
82
+ ta.focus(); ta.select();
83
+ const ok = document.execCommand('copy');
84
+ document.body.removeChild(ta);
85
+ return ok;
86
+ }
87
+ })()`,
88
+ awaitPromise: true,
89
+ returnByValue: true
90
+ });
91
+ if (res.result?.value === true) {
92
+ return { success: true, message: "Copied to clipboard" };
93
+ }
94
+ if (!secureCtx) {
95
+ throw new Error("browser_clipboard: write failed on a non-secure context (file:// or http://). The Clipboard API is unavailable here; navigate to an https page and retry.");
96
+ }
97
+ throw new Error("Failed to write to clipboard (page may not be focused)");
98
+ }
99
+ // ── Read action ──────────────────────────────────────────────────
100
+ // 4-layer clipboard read strategy:
101
+ // Layer 1: CDP mouse click on body + page script click() for user activation
102
+ // Layer 2: navigator.clipboard.read() API
103
+ // Layer 3: Ctrl+V via trusted CDP key event → hidden textarea
104
+ // Layer 4: document.execCommand('paste') fallback
105
+ async function grantUserActivation() {
106
+ try {
107
+ const { result: vp } = await session.Runtime.evaluate({
108
+ expression: '({w: window.innerWidth, h: window.innerHeight})',
109
+ returnByValue: true
110
+ });
111
+ const cx = Math.floor((vp?.value?.w || 800) / 2);
112
+ const cy = Math.floor((vp?.value?.h || 600) / 2);
113
+ await ghostClick(session, cx, cy);
114
+ await session.Input.dispatchMouseEvent({ type: "mouseReleased", x: cx, y: cy, button: "left", clickCount: 1 });
115
+ await session.Runtime.evaluate({ expression: 'document.body.click()' }).catch(() => { });
116
+ }
117
+ catch { /* non-critical */ }
118
+ }
119
+ async function readViaCdpPaste() {
120
+ const { result } = await session.Runtime.evaluate({
121
+ expression: `(function() {
122
+ try {
123
+ var ta = document.createElement('textarea');
124
+ ta.style.cssText = 'position:fixed;top:-999px;left:-999px;opacity:0;width:1px;height:1px';
125
+ document.body.appendChild(ta);
126
+ ta.focus();
127
+ ta.select();
128
+ return true;
129
+ } catch(e) { return false; }
130
+ })()`,
131
+ returnByValue: true
132
+ });
133
+ if (result?.value !== true)
134
+ return null;
135
+ await session.Input.dispatchKeyEvent({ type: "rawKeyDown", key: "v", windowsVirtualKeyCode: 86, nativeVirtualKeyCode: 86, modifiers: 2 });
136
+ await session.Input.dispatchKeyEvent({ type: "keyUp", key: "v", windowsVirtualKeyCode: 86, nativeVirtualKeyCode: 86, modifiers: 2 });
137
+ await new Promise(r => setTimeout(r, 100));
138
+ const { result: valRes } = await session.Runtime.evaluate({
139
+ expression: `(function() {
140
+ var ta = document.querySelector('textarea[style*="top:-999px"]');
141
+ if (!ta) return null;
142
+ var v = ta.value;
143
+ ta.remove();
144
+ return v || null;
145
+ })()`,
146
+ returnByValue: true
147
+ });
148
+ return valRes?.value || null;
149
+ }
150
+ await grantUserActivation();
151
+ const secureCtx = await isSecureContext();
152
+ const originRes = await session.Runtime.evaluate({ expression: "window.location.origin" }).catch(() => null);
153
+ if (originRes && originRes.result.value && secureCtx) {
154
+ await session.Browser.grantPermissions({
155
+ origin: originRes.result.value,
156
+ permissions: ["clipboardReadWrite", "clipboardSanitizedWrite"]
157
+ }).catch(() => { });
158
+ }
159
+ const READ_TIMEOUT_MS = 2000;
160
+ let readResult = null;
161
+ // Layer 1+2: Try clipboard API with user activation (secure contexts only)
162
+ const apiRead = async () => {
163
+ if (!secureCtx)
164
+ return null;
165
+ const res = await session.Runtime.evaluate({
166
+ expression: `(async function() {
167
+ try {
168
+ if (typeof ClipboardItem !== 'undefined') {
169
+ var items = await navigator.clipboard.read();
170
+ var textVal = null, dataUrlVal = null;
171
+ for (var i = 0; i < items.length; i++) {
172
+ var item = items[i];
173
+ var types = item.types;
174
+ for (var j = 0; j < types.length; j++) {
175
+ var type = types[j];
176
+ var blob = await item.getType(type);
177
+ if (type.indexOf('text/') === 0) {
178
+ textVal = await blob.text();
179
+ } else if (type.indexOf('image/') === 0) {
180
+ dataUrlVal = await new Promise(function(rsv) {
181
+ var r = new FileReader();
182
+ r.onload = function() { rsv(r.result); };
183
+ r.readAsDataURL(blob);
184
+ });
185
+ }
186
+ }
187
+ }
188
+ if (textVal || dataUrlVal) return JSON.stringify({ text: textVal, dataUrl: dataUrlVal });
189
+ }
190
+ var t = await navigator.clipboard.readText();
191
+ if (t) return JSON.stringify({ text: t });
192
+ return null;
193
+ } catch(e) {
194
+ return 'ERR:' + e.message;
195
+ }
196
+ })()`,
197
+ awaitPromise: true,
198
+ returnByValue: true,
199
+ timeout: READ_TIMEOUT_MS
200
+ });
201
+ if (res.exceptionDetails)
202
+ return null;
203
+ const raw = res.result?.value;
204
+ if (!raw || raw.startsWith('ERR:'))
205
+ return null;
206
+ return raw;
207
+ };
208
+ try {
209
+ readResult = await Promise.race([
210
+ apiRead(),
211
+ new Promise(r => setTimeout(() => r(null), READ_TIMEOUT_MS))
212
+ ]);
213
+ }
214
+ catch (e) {
215
+ // If API read fails, fall through to CDP paste method
216
+ readResult = null;
217
+ }
218
+ // Layer 3: Ctrl+V via CDP key dispatch (trusted event — no user activation needed)
219
+ if (!readResult) {
220
+ try {
221
+ const pasteVal = await Promise.race([
222
+ readViaCdpPaste(),
223
+ new Promise(r => setTimeout(() => r(null), 1000))
224
+ ]);
225
+ if (pasteVal) {
226
+ readResult = JSON.stringify({ text: pasteVal });
227
+ }
228
+ }
229
+ catch {
230
+ readResult = null;
231
+ }
232
+ }
233
+ // Layer 4: execCommand fallback
234
+ if (!readResult) {
235
+ try {
236
+ const evalResult = await Promise.race([
237
+ session.Runtime.evaluate({
238
+ expression: `(function() {
239
+ try {
240
+ var ta = document.createElement('textarea');
241
+ ta.style.cssText = 'position:fixed;top:-999px;left:-999px;opacity:0';
242
+ document.body.appendChild(ta);
243
+ ta.focus();
244
+ var ok = document.execCommand('paste');
245
+ var v = ta.value;
246
+ ta.remove();
247
+ return ok && v ? JSON.stringify({ text: v }) : null;
248
+ } catch(e) { return null; }
249
+ })()`,
250
+ returnByValue: true
251
+ }),
252
+ new Promise(r => setTimeout(() => r({ result: { value: null } }), 500))
253
+ ]);
254
+ const result = evalResult;
255
+ readResult = result?.result?.value || null;
256
+ }
257
+ catch {
258
+ readResult = null;
259
+ }
260
+ }
261
+ if (!readResult) {
262
+ if (!secureCtx) {
263
+ return { success: false, message: "Clipboard is empty or inaccessible. Current page is a non-secure context (file:// or http://) where the Clipboard API is unavailable; trusted CDP paste also returned nothing. Navigate to an https page and retry." };
264
+ }
265
+ return { success: true, message: "Clipboard is empty or inaccessible (browser may require user permission)" };
266
+ }
267
+ try {
268
+ const obj = JSON.parse(readResult);
269
+ return {
270
+ success: true,
271
+ text: obj.text || undefined,
272
+ dataUrl: obj.dataUrl || undefined,
273
+ message: obj.text && obj.dataUrl ? "Read text and image from clipboard" : obj.dataUrl ? "Read image from clipboard" : "Read from clipboard"
274
+ };
275
+ }
276
+ catch {
277
+ return { success: true, text: readResult, message: "Read from clipboard" };
278
+ }
279
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Weavetab — tools/console.ts
3
+ * browser_console: Buffer last 50 console entries and exception events.
4
+ * Weavetab by fy2ne
5
+ */
6
+ import CDP from "chrome-remote-interface";
7
+ import type { Config } from "../config/loader.js";
8
+ export interface ConsoleEntry {
9
+ type: string;
10
+ text: string;
11
+ timestamp: number;
12
+ url?: string;
13
+ }
14
+ export declare function initConsoleBuffering(session: CDP.Client, targetId: string): void;
15
+ export interface ConsoleOptions {
16
+ action: "read" | "clear";
17
+ }
18
+ export declare function browserConsole(session: CDP.Client, options: ConsoleOptions, _config: Config, targetId: string): Promise<{
19
+ success: boolean;
20
+ logs: ConsoleEntry[];
21
+ }>;
@@ -0,0 +1,107 @@
1
+ import { SessionSecretTracker } from "../security/secrets.js";
2
+ /**
3
+ * Server-side noise filter (Q4 decision).
4
+ * Suppresses Weavetab-internal log spam before it enters the agent-visible buffer.
5
+ * Extension-side gating is the long-term fix (when ext-dist/ is rebuilt), but
6
+ * this filter acts as a reliable safety net for any messages that escape.
7
+ *
8
+ * Pattern matching is case-insensitive substring. Keep the list minimal.
9
+ */
10
+ const NOISE_PATTERNS = [
11
+ /Weavetab_GROUP_STATE/i, // Extension broadcast: idle/busy state changes
12
+ /cursor host missing/i, // Extension watchdog: fires every 5s on some pages
13
+ /feedback panel missing/i, // Extension watchdog: harmless recovery notice
14
+ /\[Weavetab\] Cursor host/i, // Same watchdog, alternate prefix
15
+ /wt-cursor-trail/i, // Extension CSS transition noise
16
+ /__wt_push_analytics/i, // Analytics ping echo (already fire-and-forget)
17
+ ];
18
+ /** Dedup: suppress the same message within this window (ms) */
19
+ const DEDUP_WINDOW_MS = 10_000;
20
+ function isNoise(text) {
21
+ for (const pattern of NOISE_PATTERNS) {
22
+ if (pattern.test(text))
23
+ return true;
24
+ }
25
+ return false;
26
+ }
27
+ const consoleBuffers = new Map();
28
+ /** Per-tab last-seen text for deduplication */
29
+ const lastSeenText = new Map();
30
+ export function initConsoleBuffering(session, targetId) {
31
+ if (consoleBuffers.has(targetId))
32
+ return;
33
+ const buffer = [];
34
+ consoleBuffers.set(targetId, buffer);
35
+ const push = (entry) => {
36
+ // Server-side noise suppression
37
+ if (isNoise(entry.text))
38
+ return;
39
+ // Deduplication: same text within 10s window
40
+ const last = lastSeenText.get(targetId);
41
+ if (last && last.text === entry.text && Date.now() - last.at < DEDUP_WINDOW_MS)
42
+ return;
43
+ lastSeenText.set(targetId, { text: entry.text, at: Date.now() });
44
+ buffer.push(entry);
45
+ if (buffer.length > 50)
46
+ buffer.shift();
47
+ };
48
+ session.Runtime.on("consoleAPICalled", (event) => {
49
+ const text = event.args.map((a) => a.value || a.description || "").join(" ");
50
+ push({ type: event.type, text, timestamp: event.timestamp });
51
+ });
52
+ session.Runtime.on("exceptionThrown", (event) => {
53
+ push({
54
+ type: "exception",
55
+ text: event.exceptionDetails.text + (event.exceptionDetails.exception?.description || ""),
56
+ timestamp: event.timestamp,
57
+ url: event.exceptionDetails.url
58
+ });
59
+ });
60
+ // Clear the per-tab buffer when the MAIN frame navigates to a new document,
61
+ // so site A's logs don't leak into site B's console reads.
62
+ session.Page.on("frameNavigated", (event) => {
63
+ const frame = event.frame;
64
+ if (frame && !frame.parentId) {
65
+ const buf = consoleBuffers.get(targetId);
66
+ if (buf)
67
+ buf.length = 0;
68
+ lastSeenText.delete(targetId);
69
+ }
70
+ });
71
+ }
72
+ export async function browserConsole(session, options, _config, targetId) {
73
+ if (options.action === "clear") {
74
+ await session.Runtime.evaluate({
75
+ expression: `if (window.__consoleBuffer) { window.__consoleBuffer.length = 0; window.__consoleCursor = 0; }`
76
+ }).catch(() => { });
77
+ const buf = consoleBuffers.get(targetId);
78
+ if (buf)
79
+ buf.length = 0;
80
+ return { success: true, logs: [] };
81
+ }
82
+ const res = await session.Runtime.evaluate({
83
+ expression: `window.__Weavetab_readConsole({ since: 'last' })`,
84
+ returnByValue: true
85
+ }).catch(() => null);
86
+ let logs = [];
87
+ if (res?.result?.value) {
88
+ logs = res.result.value.map((l) => ({
89
+ type: l.level,
90
+ text: l.text,
91
+ timestamp: l.time
92
+ }));
93
+ }
94
+ else {
95
+ // Fallback to CDP buffer
96
+ const buf = consoleBuffers.get(targetId);
97
+ if (buf) {
98
+ logs = [...buf];
99
+ buf.length = 0; // Simulate 'since: last'
100
+ }
101
+ }
102
+ const redactedLogs = logs.map(l => ({
103
+ ...l,
104
+ text: typeof l.text === 'string' ? SessionSecretTracker.redact(targetId, l.text) : l.text
105
+ }));
106
+ return { success: true, logs: redactedLogs };
107
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Weavetab — tools/cookies.ts
3
+ * browser_cookies: Read and inject browser cookies via the CDP Network domain.
4
+ *
5
+ * Use this tool to:
6
+ * - GET: Read all cookies for the current page's URL (for inspection or debugging)
7
+ * - SET: Inject a cookie to pre-authenticate a session without logging in manually
8
+ * - DELETE: Remove a specific cookie by name and domain
9
+ *
10
+ * This is the correct tool for session pre-authentication. Do NOT use browser_eval
11
+ * for cookie manipulation — this tool uses the proper CDP Network domain which
12
+ * works correctly with HttpOnly and Secure cookies.
13
+ * Weavetab by fy2ne
14
+ */
15
+ import CDP from "chrome-remote-interface";
16
+ import type { Config } from "../config/loader.js";
17
+ export interface CookieEntry {
18
+ name: string;
19
+ value: string;
20
+ domain: string;
21
+ path: string;
22
+ expires: number;
23
+ httpOnly: boolean;
24
+ secure: boolean;
25
+ sameSite?: string;
26
+ }
27
+ export interface CookiesGetResult {
28
+ success: true;
29
+ action: "get";
30
+ cookies: CookieEntry[];
31
+ count: number;
32
+ found?: boolean;
33
+ message?: string;
34
+ }
35
+ export interface CookiesSetResult {
36
+ success: true;
37
+ action: "set";
38
+ name: string;
39
+ }
40
+ export interface CookiesDeleteResult {
41
+ success: true;
42
+ action: "delete";
43
+ name: string;
44
+ }
45
+ export type CookiesResult = CookiesGetResult | CookiesSetResult | CookiesDeleteResult;
46
+ export interface CookieSetOptions {
47
+ name: string;
48
+ value: string;
49
+ domain?: string;
50
+ path?: string;
51
+ expires?: number;
52
+ httpOnly?: boolean;
53
+ secure?: boolean;
54
+ sameSite?: "Strict" | "Lax" | "None";
55
+ }
56
+ export declare function browserCookies(session: CDP.Client, args: {
57
+ action: "get" | "set" | "delete";
58
+ name?: string;
59
+ value?: string;
60
+ domain?: string;
61
+ path?: string;
62
+ expires?: number;
63
+ httpOnly?: boolean;
64
+ secure?: boolean;
65
+ sameSite?: "Strict" | "Lax" | "None";
66
+ url?: string;
67
+ }, _config: Config): Promise<CookiesResult>;