@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,342 @@
1
+ import { logDebug } from "../audit/logger.js";
2
+ import CDP from "chrome-remote-interface";
3
+ import { logAction } from "../audit/logger.js";
4
+ import { connectToTab, getPort, getTabList } from "../cdp/connector.js";
5
+ import { sessionRegistry } from "../state/session.js";
6
+ import { clearTabCache } from "../state/cache.js";
7
+ import { readLock } from "../cdp/lock.js";
8
+ import { resetLoopCounter } from "../intelligence/trail.js";
9
+ import { getConfigAgentId, getConfigMaxTabs } from "../config/loader.js";
10
+ /** Resolve the active CDP port — live > lock file */
11
+ function resolvePort(_config) {
12
+ const port = getPort() || readLock()?.port;
13
+ if (!port)
14
+ throw new Error("[!] No active browser session. Run browser_go first.");
15
+ return port;
16
+ }
17
+ const IDLE_THRESHOLD_MS = 30_000;
18
+ const lastActivityMs = new Map();
19
+ // Phase 3: Semantic Tab Registry
20
+ const semanticScores = new Map();
21
+ const lockedTabIds = new Set();
22
+ export function recordTabActivity(targetId) {
23
+ lastActivityMs.set(targetId, Date.now());
24
+ }
25
+ export function lockTab(targetId) {
26
+ lockedTabIds.add(targetId);
27
+ logAction("browser_tabs", targetId, `Locked tab to prevent auto-close`);
28
+ }
29
+ export function unlockTab(targetId) {
30
+ lockedTabIds.delete(targetId);
31
+ }
32
+ // Score a tab based on title/url to determine importance (1-100)
33
+ // Higher score = More critical = Less likely to be closed
34
+ function calculateSemanticScore(url, title) {
35
+ let score = 10; // base score
36
+ const t = (title + " " + url).toLowerCase();
37
+ if (t.includes("login") || t.includes("sign in") || t.includes("auth"))
38
+ score += 60;
39
+ if (t.includes("checkout") || t.includes("payment") || t.includes("cart"))
40
+ score += 70;
41
+ if (t.includes("dashboard") || t.includes("admin"))
42
+ score += 40;
43
+ if (t.includes("mail") || t.includes("inbox"))
44
+ score += 30;
45
+ return Math.min(score, 100);
46
+ }
47
+ export async function evaluateTabSemantics(targetId) {
48
+ try {
49
+ const client = await connectToTab(targetId);
50
+ const result = await client.session.Runtime.evaluate({
51
+ expression: `JSON.stringify({ title: document.title, url: location.href })`,
52
+ returnByValue: true
53
+ });
54
+ if (result && result.result.value) {
55
+ const data = JSON.parse(result.result.value);
56
+ const score = calculateSemanticScore(data.url, data.title);
57
+ semanticScores.set(targetId, score);
58
+ // Auto-lock highly critical tabs
59
+ if (score >= 70)
60
+ lockTab(targetId);
61
+ }
62
+ }
63
+ catch (e) {
64
+ // Silently fail if page isn't ready
65
+ }
66
+ }
67
+ function isIdleTab(targetId, activeTargetId) {
68
+ if (targetId === activeTargetId)
69
+ return false;
70
+ if (lockedTabIds.has(targetId))
71
+ return false;
72
+ const lastSeen = lastActivityMs.get(targetId) ?? 0;
73
+ return Date.now() - lastSeen > IDLE_THRESHOLD_MS;
74
+ }
75
+ // ─── Auto-close oldest, lowest-score idle tab ────────────────────────────────
76
+ async function enforceMaxTabs(config) {
77
+ const port = resolvePort(config);
78
+ const pages = await getTabList(getConfigAgentId(config));
79
+ if (pages.length < getConfigMaxTabs(config))
80
+ return;
81
+ const activeTargetId = sessionRegistry.getActive(getConfigAgentId(config))?.targetId;
82
+ // Find oldest idle tab, weighted by semantic score
83
+ const idleCandidates = pages
84
+ .filter((t) => isIdleTab(t.id, activeTargetId))
85
+ .sort((a, b) => {
86
+ const scoreA = semanticScores.get(a.id) ?? 10;
87
+ const scoreB = semanticScores.get(b.id) ?? 10;
88
+ if (scoreA !== scoreB)
89
+ return scoreA - scoreB; // Lower score closed first
90
+ return (lastActivityMs.get(a.id) ?? 0) - (lastActivityMs.get(b.id) ?? 0); // Older closed first
91
+ });
92
+ if (idleCandidates.length === 0) {
93
+ logDebug("[Weavetab] maxTabs reached but no closable idle tab found.");
94
+ return;
95
+ }
96
+ const victim = idleCandidates[0];
97
+ logDebug(`[Weavetab] maxTabs (${getConfigMaxTabs(config)}) reached — closing lowest-score tab: ${victim.id} (${victim.title})`);
98
+ await CDP.Close({ host: "127.0.0.1", port, id: victim.id });
99
+ clearTabCache(victim.id);
100
+ sessionRegistry.unregister(victim.id);
101
+ lastActivityMs.delete(victim.id);
102
+ semanticScores.delete(victim.id);
103
+ lockedTabIds.delete(victim.id);
104
+ logAction("browser_tabs", victim.id, `auto-closed idle tab: ${victim.title}`);
105
+ }
106
+ // ─── Tools ────────────────────────────────────────────────────────────────────
107
+ export async function WeavetabOpen(url, config, reuse = true) {
108
+ const port = resolvePort(config);
109
+ // Tab reuse: if same URL is already open, focus it instead of duplicating
110
+ if (reuse && url) {
111
+ try {
112
+ const targets = await getTabList(getConfigAgentId(config));
113
+ const existing = targets.find((t) => t.url === url);
114
+ if (existing) {
115
+ const client = await connectToTab(existing.id);
116
+ await client.session.Target.activateTarget({ targetId: existing.id });
117
+ sessionRegistry.setActive(existing.id, getConfigAgentId(config));
118
+ recordTabActivity(existing.id);
119
+ logAction("browser_tabs", url, `reused existing tab ${existing.id}`);
120
+ return { success: true, tabId: existing.id, url, reused: true };
121
+ }
122
+ }
123
+ catch { /* fall through to open new */ }
124
+ }
125
+ await enforceMaxTabs(config);
126
+ const bctxId = sessionRegistry.getBrowserContextIdForAgent(getConfigAgentId(config));
127
+ const metaSession = await CDP({ host: "127.0.0.1", port });
128
+ const { targetId } = await metaSession.Target.createTarget({ url, browserContextId: bctxId });
129
+ await metaSession.close();
130
+ recordTabActivity(targetId);
131
+ await connectToTab(targetId, port, getConfigAgentId(config), bctxId);
132
+ await evaluateTabSemantics(targetId);
133
+ logAction("browser_tabs", url, `opened ${targetId}`);
134
+ return { success: true, tabId: targetId, url };
135
+ }
136
+ export async function WeavetabSwitch(tabId, config) {
137
+ const client = await connectToTab(tabId);
138
+ const startSwitch = Date.now();
139
+ await client.session.Target.activateTarget({ targetId: tabId });
140
+ sessionRegistry.setActive(tabId, getConfigAgentId(config));
141
+ recordTabActivity(tabId);
142
+ const { frameTree } = await client.session.Page.getFrameTree();
143
+ let title = tabId;
144
+ try {
145
+ const titleRes = await client.session.Runtime.evaluate({ expression: 'document.title', returnByValue: true });
146
+ if (titleRes?.result?.value) {
147
+ title = titleRes.result.value;
148
+ }
149
+ }
150
+ catch { }
151
+ const url = frameTree.frame.url;
152
+ const cost = Date.now() - startSwitch;
153
+ logAction("browser_tabs", tabId, `switched to ${title} (cost: ${cost}ms)`);
154
+ resetLoopCounter("tab_switch");
155
+ return { success: true, tab_id: tabId, title, url, contextSwitchCostMs: cost };
156
+ }
157
+ export async function WeavetabList(config) {
158
+ const targets = await getTabList(getConfigAgentId(config));
159
+ const activeTargetId = sessionRegistry.getActive(getConfigAgentId(config))?.targetId;
160
+ const tabs = targets
161
+ .map((t) => ({
162
+ id: t.id,
163
+ title: t.title,
164
+ url: t.url,
165
+ score: semanticScores.get(t.id) ?? 10,
166
+ health: sessionRegistry.getHealth(t.id),
167
+ status: t.id === activeTargetId
168
+ ? "active"
169
+ : lockedTabIds.has(t.id)
170
+ ? "locked"
171
+ : isIdleTab(t.id, activeTargetId)
172
+ ? "idle"
173
+ : "loading",
174
+ }));
175
+ return { tabs };
176
+ }
177
+ export async function WeavetabClose(tabId, config, force = false) {
178
+ const score = semanticScores.get(tabId) ?? 10;
179
+ if (score >= 70 && !force) {
180
+ throw new Error(`[!] Tab Armor block: This tab is semantically protected (score: ${score}). It likely contains a login, checkout, or critical work. Do not close it! If the user explicitly requested this closure, pass force: true.`);
181
+ }
182
+ const port = resolvePort(config);
183
+ await CDP.Close({ host: "127.0.0.1", port, id: tabId });
184
+ clearTabCache(tabId);
185
+ sessionRegistry.unregister(tabId);
186
+ lastActivityMs.delete(tabId);
187
+ semanticScores.delete(tabId);
188
+ lockedTabIds.delete(tabId);
189
+ logAction("browser_tabs", tabId, "closed");
190
+ return { success: true, tab_id: tabId };
191
+ }
192
+ /**
193
+ * Batch close multiple tabs at once — saves multiple round-trips.
194
+ */
195
+ export async function WeavetabCloseBatch(tabIds, config, force = false) {
196
+ const closed = [];
197
+ const skipped = [];
198
+ for (const tabId of tabIds) {
199
+ try {
200
+ await WeavetabClose(tabId, config, force);
201
+ closed.push(tabId);
202
+ }
203
+ catch {
204
+ skipped.push(tabId);
205
+ }
206
+ }
207
+ return { success: true, closed, skipped };
208
+ }
209
+ /**
210
+ * Consolidate: deduplicate tabs by URL, close duplicates.
211
+ */
212
+ export async function WeavetabConsolidate(config) {
213
+ const port = resolvePort(config);
214
+ const targets = await CDP.List({ host: "127.0.0.1", port });
215
+ const pages = targets.filter((t) => t.type === "page");
216
+ const tabsBefore = pages.length;
217
+ const seenUrls = new Set();
218
+ let closedDuplicates = 0;
219
+ const activeTargetId = sessionRegistry.getActive(getConfigAgentId(config))?.targetId;
220
+ for (const page of pages) {
221
+ if (page.id === activeTargetId) {
222
+ seenUrls.add(page.url);
223
+ continue;
224
+ }
225
+ if (seenUrls.has(page.url) && page.url !== "about:blank" && page.url !== "") {
226
+ // Duplicate URL — close it
227
+ try {
228
+ await CDP.Close({ host: "127.0.0.1", port, id: page.id });
229
+ clearTabCache(page.id);
230
+ sessionRegistry.unregister(page.id);
231
+ lastActivityMs.delete(page.id);
232
+ semanticScores.delete(page.id);
233
+ lockedTabIds.delete(page.id);
234
+ closedDuplicates++;
235
+ }
236
+ catch { /* skip */ }
237
+ }
238
+ else {
239
+ seenUrls.add(page.url);
240
+ }
241
+ }
242
+ const afterTargets = await CDP.List({ host: "127.0.0.1", port });
243
+ const tabsAfter = afterTargets.filter((t) => t.type === "page").length;
244
+ logAction("browser_tabs", "consolidate", `${tabsBefore}→${tabsAfter} (closed ${closedDuplicates} duplicates)`);
245
+ return { success: true, tabs_before: tabsBefore, tabs_after: tabsAfter, closed_duplicates: closedDuplicates };
246
+ }
247
+ /**
248
+ * Heal: check all tabs for crashed state and reload them.
249
+ */
250
+ export async function WeavetabHeal(config) {
251
+ const port = resolvePort(config);
252
+ const targets = await CDP.List({ host: "127.0.0.1", port });
253
+ const pages = targets.filter((t) => t.type === "page");
254
+ let healed = 0;
255
+ const healthSummary = {};
256
+ for (const page of pages) {
257
+ const health = sessionRegistry.getHealth(page.id);
258
+ healthSummary[page.id] = health;
259
+ if (health === "dead") {
260
+ // Dead tab — try reloading via CDP directly
261
+ try {
262
+ const crashSession = await CDP({ host: "127.0.0.1", port, target: page.id });
263
+ await crashSession.Page.reload({ ignoreCache: true });
264
+ await crashSession.close().catch(() => { });
265
+ sessionRegistry.beatHeartbeat(page.id);
266
+ healed++;
267
+ }
268
+ catch { /* can't heal this one */ }
269
+ continue;
270
+ }
271
+ try {
272
+ const client = await connectToTab(page.id);
273
+ const { result } = await client.session.Runtime.evaluate({
274
+ expression: `document.readyState`,
275
+ returnByValue: true,
276
+ });
277
+ if (!result?.value || result.value === "uninitialized") {
278
+ await client.session.Page.reload({ ignoreCache: true });
279
+ healed++;
280
+ }
281
+ }
282
+ catch {
283
+ try {
284
+ const crashSession = await CDP({ host: "127.0.0.1", port, target: page.id });
285
+ await crashSession.Page.reload({ ignoreCache: true });
286
+ await crashSession.close().catch(() => { });
287
+ healed++;
288
+ }
289
+ catch { /* can't heal this one */ }
290
+ }
291
+ }
292
+ logAction("browser_tabs", "heal", `healed ${healed}/${pages.length} tabs`);
293
+ return { success: true, healed, tabs_checked: pages.length, health_summary: healthSummary };
294
+ }
295
+ export async function weaveWindowList(config) {
296
+ const port = resolvePort(config);
297
+ const targets = await CDP.List({ host: "127.0.0.1", port });
298
+ const pages = targets.filter((t) => t.type === "page");
299
+ // Group by process — each unique window has its own set of tabs
300
+ // We approximate via CDP Target.getTargets windowId if available
301
+ const windows = [];
302
+ const seenWindowIds = new Set();
303
+ for (const page of pages) {
304
+ try {
305
+ const session = await CDP({ host: "127.0.0.1", port, target: page.id });
306
+ try {
307
+ const { windowId, bounds } = await session.Browser.getWindowForTarget({ targetId: page.id });
308
+ if (!seenWindowIds.has(windowId)) {
309
+ seenWindowIds.add(windowId);
310
+ const activeId = sessionRegistry.getActive(getConfigAgentId(config))?.targetId;
311
+ windows.push({
312
+ windowId,
313
+ state: bounds.windowState || "normal",
314
+ bounds: { left: bounds.left, top: bounds.top, width: bounds.width, height: bounds.height },
315
+ focused: page.id === activeId,
316
+ });
317
+ }
318
+ }
319
+ finally {
320
+ await session.close().catch(() => { });
321
+ }
322
+ }
323
+ catch { /* skip inaccessible targets */ }
324
+ }
325
+ return { windows };
326
+ }
327
+ export async function weaveFocusWindow(windowId, config) {
328
+ const port = resolvePort(config);
329
+ const targets = await CDP.List({ host: "127.0.0.1", port });
330
+ const page = targets.find((t) => t.type === "page");
331
+ if (!page)
332
+ throw new Error("No page target found to focus window");
333
+ const session = await CDP({ host: "127.0.0.1", port, target: page.id });
334
+ try {
335
+ await session.Browser.setWindowBounds({ windowId, bounds: { windowState: "normal" } });
336
+ logAction("browser_tabs", String(windowId), "focused window");
337
+ }
338
+ finally {
339
+ await session.close().catch(() => { });
340
+ }
341
+ return { success: true, windowId };
342
+ }
@@ -0,0 +1,16 @@
1
+ import CDP from "chrome-remote-interface";
2
+ export interface ThoughtOptions {
3
+ action: "feedback" | "question";
4
+ message: string;
5
+ input?: "sentence" | "paragraph";
6
+ timeout?: number;
7
+ }
8
+ export interface ThoughtResult {
9
+ success: boolean;
10
+ response?: string;
11
+ timeout?: boolean;
12
+ cancelled?: boolean;
13
+ status?: "answered" | "cancelled" | "timeout";
14
+ error?: string;
15
+ }
16
+ export declare function browserThought(session: CDP.Client, args: ThoughtOptions): Promise<ThoughtResult>;
@@ -0,0 +1,79 @@
1
+ // src/tools/thought.ts
2
+ // browser_thought — agent-to-user communication via green thought bubbles
3
+ // Merged: browser_feedback (one-way) + browser_ask (two-way with input)
4
+ import { logAction } from "../audit/logger.js";
5
+ import { sendToExtension } from "../cdp/helpers.js";
6
+ function sleep(ms) {
7
+ return new Promise(r => setTimeout(r, ms));
8
+ }
9
+ export async function browserThought(session, args) {
10
+ const { action, message, input, timeout } = args;
11
+ logAction("browser_thought", action, message);
12
+ if (action === "feedback") {
13
+ await sendToExtension(session, {
14
+ type: 'Weavetab_STATE',
15
+ hud: { action: message },
16
+ stickyFeedback: true,
17
+ }).catch(() => { });
18
+ // Forward to sidepanel thought stream
19
+ await sendToExtension(session, {
20
+ type: 'WT_THOUGHT',
21
+ text: message,
22
+ kind: 'thought',
23
+ }).catch(() => { });
24
+ return { success: true };
25
+ }
26
+ if (action === "question") {
27
+ const QUESTION_TIMEOUT_MS = Math.min((timeout || 20), 20) * 1000;
28
+ await sendToExtension(session, {
29
+ type: 'Weavetab_STATE',
30
+ hud: { action: message },
31
+ stickyFeedback: true,
32
+ ask: {
33
+ input: input || "sentence",
34
+ timeout: Math.min((timeout || 20), 20),
35
+ },
36
+ }).catch(() => { });
37
+ const start = Date.now();
38
+ while (Date.now() - start < QUESTION_TIMEOUT_MS + 5000) {
39
+ await sleep(500);
40
+ try {
41
+ const { result } = await session.Runtime.evaluate({
42
+ expression: `(() => { try {
43
+ var el = document.getElementById('__wt_ask_bridge');
44
+ if (!el || el.dataset.status === 'pending') return null;
45
+ return JSON.stringify({ status: el.dataset.status, result: el.dataset.result });
46
+ } catch(e) { return null; } })()`,
47
+ returnByValue: true,
48
+ });
49
+ if (result?.value) {
50
+ const data = JSON.parse(result.value);
51
+ if (data.status === 'submitted') {
52
+ logAction("browser_thought", "answered", data.result);
53
+ await session.Runtime.evaluate({
54
+ expression: `(function() {
55
+ var el = document.getElementById('__wt_ask_bridge');
56
+ if (el) el.remove();
57
+ })()`
58
+ }).catch(() => { });
59
+ return { success: true, response: data.result, status: "answered" };
60
+ }
61
+ if (data.status === 'cancelled') {
62
+ logAction("browser_thought", "cancelled", "");
63
+ return { success: true, response: "", timeout: true, cancelled: true, status: "cancelled" };
64
+ }
65
+ if (data.status === 'timeout') {
66
+ logAction("browser_thought", "timeout", "");
67
+ return { success: true, response: "", timeout: true, status: "timeout" };
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ // polling error — retry
73
+ }
74
+ }
75
+ logAction("browser_thought", "timeout", "");
76
+ return { success: true, response: "", timeout: true, status: "timeout" };
77
+ }
78
+ return { success: false, error: `Unknown action: ${action}` };
79
+ }
@@ -0,0 +1,65 @@
1
+ import CDP from "chrome-remote-interface";
2
+ import type { Config } from "../config/loader.js";
3
+ import type { WeaveError } from "./click.js";
4
+ export interface TypeResult {
5
+ success: true;
6
+ typed_into: string;
7
+ ref: string;
8
+ length: number;
9
+ execution_ms: number;
10
+ tab_id: string;
11
+ url: string;
12
+ title: string;
13
+ /** Only present when include_map:true was passed or url_changed is true. */
14
+ page_map?: string;
15
+ /** true if any DOM elements changed after typing. */
16
+ dom_changed: boolean;
17
+ /** true if dom_changed is false — the page did not respond to this action. */
18
+ stuck_warning: boolean;
19
+ /** Guidance when stuck_warning is true. */
20
+ stuck_hint?: string;
21
+ /** Sniper warning if multiple matches were found. */
22
+ sniper_warning?: string;
23
+ /** true if a high-confidence memory hit was used; false/absent if DOM was resolved normally. */
24
+ memory_hit?: boolean;
25
+ /** Rich delta showing exactly what changed */
26
+ delta?: any;
27
+ /** SEMANTIC RESPONSE CONTRACT FIELDS */
28
+ confirmed: boolean;
29
+ strategy: string;
30
+ elementState?: Record<string, unknown>;
31
+ visibleFeedback?: string[];
32
+ suggestedNext?: string;
33
+ }
34
+ /**
35
+ * Detect the JavaScript framework used by the current page
36
+ * This helps determine the best input event strategy
37
+ */
38
+ export declare function detectFramework(session: CDP.Client): Promise<'react' | 'vue' | 'angular' | 'vanilla'>;
39
+ /**
40
+ * Framework-aware text injection.
41
+ * React/Vue controlled inputs require the native prototype setter + InputEvent
42
+ * to trigger synthetic event reconciliation. Firing a plain Event('input') AFTER
43
+ * an insertText causes a second state update — the source of triplication.
44
+ *
45
+ * Returns true when the native-setter path was taken (caller skips redundant
46
+ * post-type event dispatch).
47
+ */
48
+ export declare function frameworkAwareType(session: CDP.Client, objectId: string, text: string, framework: 'react' | 'vue' | 'angular' | 'vanilla'): Promise<boolean>;
49
+ export declare function weaveType(session: CDP.Client, args: {
50
+ id?: string;
51
+ label?: string;
52
+ intent?: string;
53
+ backendNodeId?: number;
54
+ x?: number;
55
+ y?: number;
56
+ text: string;
57
+ clearFirst?: boolean;
58
+ paste?: boolean;
59
+ /** Typing profile: 'normal' (default) | 'fast' | 'deliberate'. Auto-selected if omitted. */
60
+ profile?: "fast" | "normal" | "deliberate";
61
+ /** Set true to include the full updated page_map in the response. Default false. */
62
+ include_map?: boolean;
63
+ fast?: boolean;
64
+ noRetry?: boolean;
65
+ }, config: Config): Promise<TypeResult | WeaveError>;