@weavetab/mcp 2.5.0-beta.5 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/AGENTS.md +49 -49
  2. package/ARCHITECTURE.md +590 -590
  3. package/README.md +216 -100
  4. package/SECURITY.md +207 -207
  5. package/TROUBLESHOOTING.md +373 -365
  6. package/dist/anticaptcha/detector.js +395 -395
  7. package/dist/anticaptcha/solver.js +130 -130
  8. package/dist/cdp/bridge.js +73 -73
  9. package/dist/cdp/connector.js +46 -22
  10. package/dist/cdp/extension.js +34 -34
  11. package/dist/cdp/helpers.js +174 -174
  12. package/dist/cdp/pruner.js +195 -195
  13. package/dist/cdp/ref_cache.js +66 -66
  14. package/dist/cdp/security.js +10 -10
  15. package/dist/cdp/verify.js +57 -57
  16. package/dist/cdp/walker.js +563 -525
  17. package/dist/cli/plugin.js +26 -3
  18. package/dist/config/loader.d.ts +0 -3
  19. package/dist/config/loader.js +0 -9
  20. package/dist/index.js +0 -4
  21. package/dist/intelligence/intent.js +81 -81
  22. package/dist/intelligence/sessionMemory.js +2 -2
  23. package/dist/overlay/ghost.js +22 -22
  24. package/dist/overlay/injector.js +89 -89
  25. package/dist/plugin/extension_bridge.js +54 -54
  26. package/dist/plugin/interface.d.ts +7 -0
  27. package/dist/plugin/loader.js +3 -4
  28. package/dist/plugin/schema.d.ts +11 -0
  29. package/dist/plugin/schema.js +59 -0
  30. package/dist/security/guard.js +18 -18
  31. package/dist/sensors/actionability.js +167 -167
  32. package/dist/sensors/guard.js +29 -29
  33. package/dist/sensors/network.js +15 -17
  34. package/dist/server.d.ts +1 -1
  35. package/dist/server.js +173 -144
  36. package/dist/state/delta.js +6 -7
  37. package/dist/tools/automation.js +85 -85
  38. package/dist/tools/canvas.js +48 -25
  39. package/dist/tools/checkpoint.js +94 -35
  40. package/dist/tools/click.js +52 -41
  41. package/dist/tools/clipboard.js +80 -74
  42. package/dist/tools/design_extract.js +441 -441
  43. package/dist/tools/dialog.js +13 -13
  44. package/dist/tools/emulation.d.ts +2 -0
  45. package/dist/tools/emulation.js +7 -0
  46. package/dist/tools/fill.js +344 -347
  47. package/dist/tools/find.js +78 -52
  48. package/dist/tools/highlight.js +67 -67
  49. package/dist/tools/inspect.js +54 -25
  50. package/dist/tools/key.js +42 -35
  51. package/dist/tools/navigate.d.ts +1 -1
  52. package/dist/tools/navigate.js +35 -35
  53. package/dist/tools/office/parse.js +62 -18
  54. package/dist/tools/pdf.d.ts +9 -0
  55. package/dist/tools/pdf.js +7 -4
  56. package/dist/tools/performance.d.ts +11 -4
  57. package/dist/tools/performance.js +94 -42
  58. package/dist/tools/performance_test.d.ts +2 -2
  59. package/dist/tools/performance_test.js +69 -69
  60. package/dist/tools/pointer.js +116 -116
  61. package/dist/tools/read.js +99 -67
  62. package/dist/tools/recording.js +9 -0
  63. package/dist/tools/scrape.js +146 -146
  64. package/dist/tools/screenshot.d.ts +2 -0
  65. package/dist/tools/screenshot.js +46 -4
  66. package/dist/tools/scroll.d.ts +3 -1
  67. package/dist/tools/scroll.js +13 -15
  68. package/dist/tools/select.d.ts +3 -0
  69. package/dist/tools/select.js +131 -124
  70. package/dist/tools/snapshot.js +8 -6
  71. package/dist/tools/storage.d.ts +5 -1
  72. package/dist/tools/storage.js +16 -0
  73. package/dist/tools/tabs.d.ts +7 -5
  74. package/dist/tools/tabs.js +79 -26
  75. package/dist/tools/thought.js +8 -8
  76. package/dist/tools/type.js +274 -274
  77. package/dist/tools/type_secret.js +23 -23
  78. package/dist/tools/upload.js +3 -3
  79. package/dist/tools/video_compiler.js +9 -0
  80. package/dist/tools/wait.js +35 -35
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/docs/DOCUMENTATION.md +583 -585
  84. package/docs/PLUGINS.md +139 -121
  85. package/docs/TOOLS.md +3 -4
  86. package/docs/architecture/blind-injection.md +83 -83
  87. package/docs/architecture/overview.md +49 -49
  88. package/docs/configuration/configuration.md +39 -27
  89. package/docs/configuration/folders.md +62 -55
  90. package/docs/desktop-automation/electron-apps.md +225 -214
  91. package/docs/get-started/installation.md +1 -1
  92. package/docs/get-started/introduction.md +60 -60
  93. package/docs/get-started/quickstart.md +2 -2
  94. package/docs/get-started/setup-wizard.md +1 -1
  95. package/docs/intelligence/site-profiles.md +151 -151
  96. package/docs/tools/tools.md +582 -631
  97. package/docs/troubleshooting/trouble-loop.md +31 -31
  98. package/ext-dist/background.js +415 -415
  99. package/ext-dist/content.js +2527 -2527
  100. package/ext-dist/dashboard.html +583 -583
  101. package/ext-dist/hud/hud.js +3 -3
  102. package/ext-dist/hud/index.html +20 -20
  103. package/ext-dist/manifest.json +73 -73
  104. package/ext-dist/styles.css +223 -223
  105. package/mcp.json +796 -805
  106. package/package.json +8 -8
  107. package/server.json +23 -23
  108. package/smithery.yaml +7 -11
  109. package/dist/cdp/walker.test.d.ts +0 -2
  110. package/dist/cdp/walker.test.js +0 -172
  111. package/dist/intelligence/sessionMemory.test.d.ts +0 -2
  112. package/dist/intelligence/sessionMemory.test.js +0 -62
  113. package/dist/intelligence/trail.test.d.ts +0 -2
  114. package/dist/intelligence/trail.test.js +0 -110
  115. package/dist/state/cache.test.d.ts +0 -2
  116. package/dist/state/cache.test.js +0 -76
  117. package/dist/state/delta.test.d.ts +0 -2
  118. package/dist/state/delta.test.js +0 -279
  119. package/dist/state/groups.d.ts +0 -15
  120. package/dist/state/groups.js +0 -80
  121. package/dist/tools/accessibility.d.ts +0 -14
  122. package/dist/tools/accessibility.js +0 -32
  123. package/dist/tools/ask.d.ts +0 -14
  124. package/dist/tools/ask.js +0 -49
  125. package/dist/tools/click_and_wait.d.ts +0 -33
  126. package/dist/tools/click_and_wait.js +0 -149
  127. package/dist/tools/drag.d.ts +0 -15
  128. package/dist/tools/drag.js +0 -29
  129. package/dist/tools/geo.d.ts +0 -19
  130. package/dist/tools/geo.js +0 -29
  131. package/dist/tools/github/analyze.d.ts +0 -4
  132. package/dist/tools/github/analyze.js +0 -113
  133. package/dist/tools/github/cache.d.ts +0 -21
  134. package/dist/tools/github/cache.js +0 -28
  135. package/dist/tools/github/index.d.ts +0 -19
  136. package/dist/tools/github/index.js +0 -40
  137. package/dist/tools/github/issues.d.ts +0 -9
  138. package/dist/tools/github/issues.js +0 -88
  139. package/dist/tools/github/pr.d.ts +0 -5
  140. package/dist/tools/github/pr.js +0 -88
  141. package/dist/tools/github/rateLimiter.d.ts +0 -4
  142. package/dist/tools/github/rateLimiter.js +0 -46
  143. package/dist/tools/github/read.d.ts +0 -34
  144. package/dist/tools/github/read.js +0 -74
  145. package/dist/tools/github/repoContext.d.ts +0 -20
  146. package/dist/tools/github/repoContext.js +0 -129
  147. package/dist/tools/hover.d.ts +0 -19
  148. package/dist/tools/hover.js +0 -57
  149. package/dist/tools/integration.test.d.ts +0 -2
  150. package/dist/tools/integration.test.js +0 -96
  151. package/dist/tools/macro_compiler.d.ts +0 -7
  152. package/dist/tools/macro_compiler.js +0 -82
  153. package/dist/tools/mouse.d.ts +0 -21
  154. package/dist/tools/mouse.js +0 -62
  155. package/dist/tools/response.d.ts +0 -129
  156. package/dist/tools/response.js +0 -5
  157. package/dist/tools/wait_for.d.ts +0 -23
  158. package/dist/tools/wait_for.js +0 -30
  159. package/dist/tools/windows.d.ts +0 -22
  160. package/dist/tools/windows.js +0 -57
@@ -1,415 +1,415 @@
1
- const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
2
-
3
- // HUD toggle command handler
4
-
5
- // HUD toggle command handler
6
- browserAPI.commands.onCommand.addListener(function(command) {
7
- if (command === 'toggle-hud') {
8
- browserAPI.tabs.query({ active: true, currentWindow: true }, function(tabs) {
9
- if (tabs[0] && tabs[0].id) {
10
- browserAPI.tabs.sendMessage(tabs[0].id, { type: 'TOGGLE_HUD' }).catch(function(){});
11
- }
12
- });
13
- }
14
- if (command === 'toggle-debug') {
15
- browserAPI.tabs.query({ active: true, currentWindow: true }, function(tabs) {
16
- if (tabs[0] && tabs[0].id) {
17
- browserAPI.tabs.sendMessage(tabs[0].id, { type: 'TOGGLE_DEBUG' }).catch(function(){});
18
- }
19
- });
20
- }
21
- });
22
-
23
- // Runtime state
24
- const controlledTabs = new Set();
25
- const anchorTabs = new Set();
26
- let currentGroupState = 'idle';
27
- let activeSessionTabId = null;
28
-
29
- // State color mapping — always blue, never yellow
30
- const STATE_COLORS = {
31
- idle: 'grey',
32
- weaving: 'blue',
33
- stuck: 'blue', // Keep blue, never yellow
34
- };
35
-
36
- const STATE_BADGE = {
37
- idle: { text: '', color: null },
38
- weaving: { text: '\u25CF', color: '#3B82F6' },
39
- stuck: { text: '\u25CF', color: '#3B82F6' }, // blue dot, not red
40
- };
41
-
42
-
43
- browserAPI.tabs.onCreated.addListener(async (tab) => {
44
- if (!controlledTabs.has(tab.id)) return;
45
- // Ungroup immediately — prevents Chrome from keeping this tab in any group
46
- try { browserAPI.tabs.ungroup(tab.id).catch(() => {}); } catch(e) {}
47
- });
48
-
49
- browserAPI.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
50
- if (!controlledTabs.has(tabId)) return;
51
- if (changeInfo.status === 'complete') {
52
- // Ungroup on update to handle cases where Chrome re-groups after navigation
53
- try { browserAPI.tabs.ungroup(tabId).catch(() => {}); } catch(e) {}
54
- browserAPI.tabs.sendMessage(tabId, { type: 'Weavetab_GROUP_STATE', state: currentGroupState, isActive: tabId === activeSessionTabId }).catch(() => {});
55
- }
56
- });
57
-
58
-
59
- // Per-tab network interception using declarativeNetRequest
60
- async function enableIntercept(tabId, pattern, mockStatus, mockBody) {
61
- try {
62
- await browserAPI.declarativeNetRequest.updateSessionRules({
63
- addRules: [{
64
- id: tabId,
65
- priority: 1,
66
- condition: { urlFilter: pattern, tabIds: [tabId] },
67
- action: {
68
- type: 'redirect',
69
- redirect: {
70
- url: `data:application/json,${encodeURIComponent(mockBody)}`
71
- }
72
- }
73
- }]
74
- });
75
- return { success: true };
76
- } catch (e) {
77
- console.error("Failed to enable intercept:", e);
78
- return { success: false, error: e.message };
79
- }
80
- }
81
-
82
- async function disableIntercept(tabId) {
83
- try {
84
- await browserAPI.declarativeNetRequest.updateSessionRules({
85
- removeRuleIds: [tabId]
86
- });
87
- return { success: true };
88
- } catch (e) {
89
- console.error("Failed to disable intercept:", e);
90
- return { success: false, error: e.message };
91
- }
92
- }
93
-
94
- // Stale state guard: if no Weavetab_GROUP_STATE message for 90s, reset all controlled tabs to idle
95
- let __wt_stateTimeout = null;
96
-
97
- function resetToIdle() {
98
- if (currentGroupState === 'idle') return;
99
- currentGroupState = 'idle';
100
- activeSessionTabId = null;
101
- controlledTabs.forEach(tabId => {
102
- browserAPI.tabs.sendMessage(tabId, { type: 'Weavetab_GROUP_STATE', state: 'idle', isActive: false }).catch(() => {});
103
- });
104
- if (browserAPI.action) {
105
- browserAPI.action.setBadgeText({ text: '' }).catch(() => {});
106
- }
107
- }
108
-
109
- function refreshStateTimeout() {
110
- if (__wt_stateTimeout) clearTimeout(__wt_stateTimeout);
111
- __wt_stateTimeout = setTimeout(resetToIdle, 120000); // 2 min stale guard
112
- }
113
-
114
- // Bridge: relay state from CLI (via CDP executeScript) to content scripts
115
- browserAPI.runtime.onMessage.addListener((message, sender, sendResponse) => {
116
- // ── Forward tool call events to active tabs ──
117
- if (message.type === 'TOOL_CALL_EVENT') {
118
- controlledTabs.forEach(tabId => {
119
- browserAPI.tabs.sendMessage(tabId, message).catch(() => {});
120
- });
121
- }
122
- // ── Thought routing: relay to HUD in controlled tabs ──
123
- if (message.type === 'WT_THOUGHT') {
124
- controlledTabs.forEach(tabId => {
125
- browserAPI.tabs.sendMessage(tabId, { type: 'WT_THOUGHT', text: message.text, kind: message.kind }).catch(() => {});
126
- });
127
- }
128
-
129
- // ── User messaging ──
130
- if (message.type === 'WT_USER_NOTE') {
131
- const payload = {
132
- type: 'WT_USER_NOTE_INJECT',
133
- text: message.text,
134
- snipe: message.snipe
135
- };
136
- // Query active tab in current window and send message immediately
137
- browserAPI.tabs.query({ active: true, currentWindow: true }, function(tabs) {
138
- if (tabs[0] && tabs[0].id) {
139
- browserAPI.tabs.sendMessage(tabs[0].id, payload).catch(function(){});
140
- }
141
- });
142
- // Send to activeSessionTabId if set
143
- if (activeSessionTabId) {
144
- browserAPI.tabs.sendMessage(activeSessionTabId, payload).catch(function(){});
145
- }
146
- // Fallback to all controlled tabs
147
- controlledTabs.forEach(tabId => {
148
- browserAPI.tabs.sendMessage(tabId, payload).catch(function(){});
149
- });
150
- }
151
-
152
- if (message.type === 'WT_TOGGLE_MASTER_VISUALS') {
153
- browserAPI.storage.local.set({ wt_master_visuals: message.enabled }).catch(() => {});
154
- controlledTabs.forEach(tabId => {
155
- browserAPI.tabs.sendMessage(tabId, { type: 'WT_TOGGLE_MASTER_VISUALS', enabled: message.enabled }).catch(() => {});
156
- });
157
- sendResponse({ ok: true });
158
- }
159
-
160
- if (message.type === 'Weavetab_STATE') {
161
- controlledTabs.forEach(tabId => {
162
- browserAPI.tabs.sendMessage(tabId, message).catch(() => {});
163
- });
164
- sendResponse({ ok: true });
165
- }
166
- if (message.type === 'Weavetab_CONFIG') {
167
- const { groupTabs } = message;
168
- browserAPI.storage.local.set({
169
- weaveGroupEnabled: groupTabs !== false,
170
- }).catch(() => {});
171
- sendResponse({ ok: true });
172
- }
173
- if (message.type === 'Weavetab_GROUP_STATE') {
174
- const { state, word } = message;
175
- currentGroupState = state;
176
- refreshStateTimeout();
177
- const color = STATE_COLORS[state] || 'blue';
178
- const badge = STATE_BADGE[state] || STATE_BADGE.idle;
179
-
180
- // Track which tab relayed this state update (the active session tab)
181
- if (sender.tab) activeSessionTabId = sender.tab.id;
182
- // Per-tab badge
183
- if (browserAPI.action) {
184
- const ids = Array.from(controlledTabs);
185
- ids.forEach(tid => {
186
- if (badge.text) {
187
- browserAPI.action.setBadgeText({ tabId: tid, text: badge.text }).catch(() => {});
188
- if (badge.color) {
189
- browserAPI.action.setBadgeBackgroundColor({ tabId: tid, color: badge.color }).catch(() => {});
190
- }
191
- } else {
192
- browserAPI.action.setBadgeText({ tabId: tid, text: '' }).catch(() => {});
193
- }
194
- });
195
- }
196
-
197
- // Broadcast state to all controlled tabs so they update their favicons
198
- controlledTabs.forEach(tabId => {
199
- browserAPI.tabs.sendMessage(tabId, { type: 'Weavetab_GROUP_STATE', state, isActive: tabId === activeSessionTabId }).catch(() => {});
200
- });
201
-
202
- sendResponse({ ok: true });
203
- }
204
- if (message.type === 'REGISTER_TAB') {
205
- // Skip about:blank and chrome:// pages
206
- if (sender.tab.url === 'about:blank' || sender.tab.url.startsWith('chrome://')) {
207
- sendResponse({ ok: true, skipped: true });
208
- return;
209
- }
210
- controlledTabs.add(sender.tab.id);
211
- refreshStateTimeout();
212
-
213
- // Immediately inform the tab of the current group state so it can set its favicon
214
- browserAPI.tabs.sendMessage(sender.tab.id, { type: 'Weavetab_GROUP_STATE', state: currentGroupState, isActive: sender.tab.id === activeSessionTabId }).catch(() => {});
215
-
216
- updateBadge();
217
- sendResponse({ ok: true });
218
- }
219
-
220
- if (message.type === 'NETWORK_INTERCEPT_ENABLE') {
221
- enableIntercept(sender.tab.id, message.pattern, message.mockStatus, message.mockBody)
222
- .then(res => sendResponse(res));
223
- return true;
224
- }
225
- if (message.type === 'NETWORK_INTERCEPT_DISABLE') {
226
- disableIntercept(sender.tab.id)
227
- .then(res => sendResponse(res));
228
- return true;
229
- }
230
- if (message.type === 'GET_ANCHOR_STATUS') {
231
- sendResponse({ isAnchor: anchorTabs.has(sender.tab.id) });
232
- return true;
233
- }
234
- if (message.type === 'SET_ANCHOR_TAB') {
235
- if (message.anchor) anchorTabs.add(sender.tab.id);
236
- else anchorTabs.delete(sender.tab.id);
237
- sendResponse({ ok: true });
238
- }
239
-
240
- // ── User feedback relay: content script → storage queue ───────────────────
241
- // CDP polls window.__Weavetab.popUserFeedback() or window.__wt_chat_queue which reads from this queue.
242
- if (message.type === 'USER_FEEDBACK' || message.type === 'WT_USER_NOTE') {
243
- if (message.text || message.snipe) {
244
- const noteEntry = {
245
- text: message.text || '',
246
- snipe: message.snipe || null,
247
- ts: message.ts || Date.now(),
248
- tabId: sender.tab && sender.tab.id
249
- };
250
-
251
- browserAPI.storage.local.get('__wt_feedback_queue', function(res) {
252
- const queue = res['__wt_feedback_queue'] || [];
253
- queue.push(noteEntry);
254
- // Cap queue at 20 entries
255
- if (queue.length > 20) queue.splice(0, queue.length - 20);
256
- browserAPI.storage.local.set({ '__wt_feedback_queue': queue });
257
- });
258
-
259
- // Forward directly to active session tab content script for immediate DOM queue push & interrupt
260
- browserAPI.tabs.query({ active: true, currentWindow: true }, (tabs) => {
261
- if (tabs[0]?.id) {
262
- browserAPI.tabs.sendMessage(tabs[0].id, {
263
- type: 'WT_SNIPE_INJECT',
264
- entry: noteEntry
265
- }).catch(() => {});
266
- }
267
- });
268
- }
269
- sendResponse({ ok: true });
270
- }
271
-
272
-
273
- // Alert tab: mark tabs with alert dialogs waiting + write to storage so CDP can see it
274
- if (message.type === 'TAB_ALERT_WAITING') {
275
- const alertTabId = sender.tab && sender.tab.id;
276
- if (alertTabId) {
277
- browserAPI.tabs.sendMessage(alertTabId, { type: 'Weavetab_GROUP_STATE', state: 'weaving', isActive: false }).catch(() => {});
278
- // Write alert state to storage — CDP polls __Weavetab.getAlertState()
279
- browserAPI.storage.local.set({ '__wt_alert_state': { pending: true, tabId: alertTabId, ts: Date.now() } });
280
- }
281
- sendResponse({ ok: true });
282
- }
283
- if (message.type === 'TAB_ALERT_CLEARED') {
284
- const alertTabId = sender.tab && sender.tab.id;
285
- if (alertTabId) {
286
- browserAPI.tabs.sendMessage(alertTabId, { type: 'Weavetab_GROUP_STATE', state: 'idle', isActive: false }).catch(() => {});
287
- browserAPI.storage.local.set({ '__wt_alert_state': { pending: false, tabId: alertTabId, ts: Date.now() } });
288
- }
289
- sendResponse({ ok: true });
290
- }
291
- if (message.type === 'RELOAD_EXTENSION') {
292
- sendResponse({ ok: true });
293
- browserAPI.runtime.reload();
294
- }
295
-
296
-
297
- });
298
-
299
- browserAPI.tabs.onRemoved.addListener((tabId) => {
300
- if (controlledTabs.has(tabId)) {
301
- controlledTabs.delete(tabId);
302
- updateBadge();
303
- }
304
- anchorTabs.delete(tabId);
305
- });
306
-
307
-
308
- // Startup cleanup: Ungroup all Weavetab tabs and clear badges
309
- browserAPI.runtime.onInstalled.addListener(() => {
310
- browserAPI.tabs.query({}, (tabs) => {
311
- const ids = tabs.map(t => t.id).filter(id => id !== undefined);
312
- if (ids.length > 0) {
313
- browserAPI.tabs.ungroup(ids).catch(() => {});
314
- }
315
- });
316
- });
317
-
318
- function updateBadge() {
319
- if (!browserAPI.action) return;
320
- const ids = Array.from(controlledTabs);
321
- if (ids.length === 0) {
322
- browserAPI.action.setBadgeText({ text: '' }).catch(() => {});
323
- } else {
324
- ids.forEach(tid => {
325
- // Just set the dot, but don't group
326
- browserAPI.action.setBadgeText({ tabId: tid, text: '\u25CF' }).catch(() => {});
327
- browserAPI.action.setBadgeBackgroundColor({ tabId: tid, color: '#3B82F6' }).catch(() => {});
328
- });
329
- }
330
- }
331
-
332
- // On startup: close stale blank tabs — but NEVER the last remaining tab.
333
- // Weavetab launches Chrome on about:blank, so removing the only tab closes
334
- // the browser window (graceful shutdown ~1s after launch → ECONNREFUSED loop).
335
- browserAPI.runtime.onStartup.addListener(async () => {
336
- try {
337
- const blanks = await browserAPI.tabs.query({ url: 'about:blank' });
338
- if (blanks.length === 0) return;
339
- const all = await browserAPI.tabs.query({});
340
- // Keep one tab alive if every tab would otherwise be closed.
341
- const mustKeep = blanks.length === all.length ? 1 : 0;
342
- blanks.slice(0, blanks.length - mustKeep).forEach(t =>
343
- browserAPI.tabs.remove(t.id).catch(() => {})
344
- );
345
- } catch (e) {
346
- // Non-fatal: never let tab cleanup crash the service worker or close the browser.
347
- }
348
- });
349
-
350
- // Network blocking for speed (delayed to avoid 'No SW' on cold start)
351
- // Guard: only run if the service worker is still alive (not being terminated)
352
- setTimeout(function() {
353
- // Check the runtime is still valid before calling declarativeNetRequest
354
- try {
355
- if (!browserAPI.runtime || !browserAPI.runtime.id) return;
356
- browserAPI.declarativeNetRequest.updateDynamicRules({
357
- removeRuleIds: [1, 2, 3, 4],
358
- addRules: [
359
- { id: 1, priority: 1, action: { type: 'block' }, condition: { urlFilter: '*google-analytics.com*', resourceTypes: ['script', 'xmlhttprequest', 'ping'] } },
360
- { id: 2, priority: 1, action: { type: 'block' }, condition: { urlFilter: '*doubleclick.net*', resourceTypes: ['script', 'xmlhttprequest', 'ping', 'image', 'sub_frame'] } },
361
- { id: 3, priority: 1, action: { type: 'block' }, condition: { urlFilter: '*facebook.net/en_US/fbevents.js*', resourceTypes: ['script'] } }
362
- // NOTE: GTM blocking removed — some sites bundle critical resources through GTM
363
- ]
364
- }).catch(function(e) {
365
- // Non-fatal — log for debugging only
366
- console.warn('[Weavetab] Dynamic rules update failed (non-fatal):', e && e.message);
367
- });
368
- } catch(e) {
369
- // Service worker terminated or runtime invalidated — safe to ignore
370
- }
371
- }, 1000);
372
-
373
- // ─── Omnibox: Weavetab:// protocol via browserAPI.omnibox ─────────────────────
374
-
375
- const DASHBOARD_URL = browserAPI.runtime.getURL('dashboard.html');
376
-
377
- browserAPI.omnibox.onInputStarted.addListener(() => {
378
- browserAPI.omnibox.setDefaultSuggestion({
379
- description: 'Open Weavetab Dashboard <url>chrome-extension://...</url>'
380
- });
381
- });
382
-
383
- browserAPI.omnibox.onInputChanged.addListener((text, suggest) => {
384
- const suggestions = [];
385
- const lower = text.toLowerCase();
386
- if (!lower || lower === 'dashboard' || 'dashboard'.startsWith(lower)) {
387
- suggestions.push({
388
- content: 'dashboard',
389
- description: 'Open Weavetab Dashboard'
390
- });
391
- }
392
- if ('help'.startsWith(lower)) {
393
- suggestions.push({
394
- content: 'help',
395
- description: 'Weavetab Help & Documentation'
396
- });
397
- }
398
- suggest(suggestions);
399
- });
400
-
401
- browserAPI.omnibox.onInputEntered.addListener((text, disposition) => {
402
- const branch = text && text !== 'dashboard' ? encodeURIComponent(text) : 'default';
403
- const url = DASHBOARD_URL + `?branch=${branch}`;
404
- switch (disposition) {
405
- case 'currentTab':
406
- browserAPI.tabs.update({ url });
407
- break;
408
- case 'newForegroundTab':
409
- browserAPI.tabs.create({ url });
410
- break;
411
- case 'newBackgroundTab':
412
- browserAPI.tabs.create({ url, active: false });
413
- break;
414
- }
415
- });
1
+ const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
2
+
3
+ // HUD toggle command handler
4
+
5
+ // HUD toggle command handler
6
+ browserAPI.commands.onCommand.addListener(function(command) {
7
+ if (command === 'toggle-hud') {
8
+ browserAPI.tabs.query({ active: true, currentWindow: true }, function(tabs) {
9
+ if (tabs[0] && tabs[0].id) {
10
+ browserAPI.tabs.sendMessage(tabs[0].id, { type: 'TOGGLE_HUD' }).catch(function(){});
11
+ }
12
+ });
13
+ }
14
+ if (command === 'toggle-debug') {
15
+ browserAPI.tabs.query({ active: true, currentWindow: true }, function(tabs) {
16
+ if (tabs[0] && tabs[0].id) {
17
+ browserAPI.tabs.sendMessage(tabs[0].id, { type: 'TOGGLE_DEBUG' }).catch(function(){});
18
+ }
19
+ });
20
+ }
21
+ });
22
+
23
+ // Runtime state
24
+ const controlledTabs = new Set();
25
+ const anchorTabs = new Set();
26
+ let currentGroupState = 'idle';
27
+ let activeSessionTabId = null;
28
+
29
+ // State color mapping — always blue, never yellow
30
+ const STATE_COLORS = {
31
+ idle: 'grey',
32
+ weaving: 'blue',
33
+ stuck: 'blue', // Keep blue, never yellow
34
+ };
35
+
36
+ const STATE_BADGE = {
37
+ idle: { text: '', color: null },
38
+ weaving: { text: '\u25CF', color: '#3B82F6' },
39
+ stuck: { text: '\u25CF', color: '#3B82F6' }, // blue dot, not red
40
+ };
41
+
42
+
43
+ browserAPI.tabs.onCreated.addListener(async (tab) => {
44
+ if (!controlledTabs.has(tab.id)) return;
45
+ // Ungroup immediately — prevents Chrome from keeping this tab in any group
46
+ try { browserAPI.tabs.ungroup(tab.id).catch(() => {}); } catch(e) {}
47
+ });
48
+
49
+ browserAPI.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
50
+ if (!controlledTabs.has(tabId)) return;
51
+ if (changeInfo.status === 'complete') {
52
+ // Ungroup on update to handle cases where Chrome re-groups after navigation
53
+ try { browserAPI.tabs.ungroup(tabId).catch(() => {}); } catch(e) {}
54
+ browserAPI.tabs.sendMessage(tabId, { type: 'Weavetab_GROUP_STATE', state: currentGroupState, isActive: tabId === activeSessionTabId }).catch(() => {});
55
+ }
56
+ });
57
+
58
+
59
+ // Per-tab network interception using declarativeNetRequest
60
+ async function enableIntercept(tabId, pattern, mockStatus, mockBody) {
61
+ try {
62
+ await browserAPI.declarativeNetRequest.updateSessionRules({
63
+ addRules: [{
64
+ id: tabId,
65
+ priority: 1,
66
+ condition: { urlFilter: pattern, tabIds: [tabId] },
67
+ action: {
68
+ type: 'redirect',
69
+ redirect: {
70
+ url: `data:application/json,${encodeURIComponent(mockBody)}`
71
+ }
72
+ }
73
+ }]
74
+ });
75
+ return { success: true };
76
+ } catch (e) {
77
+ console.error("Failed to enable intercept:", e);
78
+ return { success: false, error: e.message };
79
+ }
80
+ }
81
+
82
+ async function disableIntercept(tabId) {
83
+ try {
84
+ await browserAPI.declarativeNetRequest.updateSessionRules({
85
+ removeRuleIds: [tabId]
86
+ });
87
+ return { success: true };
88
+ } catch (e) {
89
+ console.error("Failed to disable intercept:", e);
90
+ return { success: false, error: e.message };
91
+ }
92
+ }
93
+
94
+ // Stale state guard: if no Weavetab_GROUP_STATE message for 90s, reset all controlled tabs to idle
95
+ let __wt_stateTimeout = null;
96
+
97
+ function resetToIdle() {
98
+ if (currentGroupState === 'idle') return;
99
+ currentGroupState = 'idle';
100
+ activeSessionTabId = null;
101
+ controlledTabs.forEach(tabId => {
102
+ browserAPI.tabs.sendMessage(tabId, { type: 'Weavetab_GROUP_STATE', state: 'idle', isActive: false }).catch(() => {});
103
+ });
104
+ if (browserAPI.action) {
105
+ browserAPI.action.setBadgeText({ text: '' }).catch(() => {});
106
+ }
107
+ }
108
+
109
+ function refreshStateTimeout() {
110
+ if (__wt_stateTimeout) clearTimeout(__wt_stateTimeout);
111
+ __wt_stateTimeout = setTimeout(resetToIdle, 120000); // 2 min stale guard
112
+ }
113
+
114
+ // Bridge: relay state from CLI (via CDP executeScript) to content scripts
115
+ browserAPI.runtime.onMessage.addListener((message, sender, sendResponse) => {
116
+ // ── Forward tool call events to active tabs ──
117
+ if (message.type === 'TOOL_CALL_EVENT') {
118
+ controlledTabs.forEach(tabId => {
119
+ browserAPI.tabs.sendMessage(tabId, message).catch(() => {});
120
+ });
121
+ }
122
+ // ── Thought routing: relay to HUD in controlled tabs ──
123
+ if (message.type === 'WT_THOUGHT') {
124
+ controlledTabs.forEach(tabId => {
125
+ browserAPI.tabs.sendMessage(tabId, { type: 'WT_THOUGHT', text: message.text, kind: message.kind }).catch(() => {});
126
+ });
127
+ }
128
+
129
+ // ── User messaging ──
130
+ if (message.type === 'WT_USER_NOTE') {
131
+ const payload = {
132
+ type: 'WT_USER_NOTE_INJECT',
133
+ text: message.text,
134
+ snipe: message.snipe
135
+ };
136
+ // Query active tab in current window and send message immediately
137
+ browserAPI.tabs.query({ active: true, currentWindow: true }, function(tabs) {
138
+ if (tabs[0] && tabs[0].id) {
139
+ browserAPI.tabs.sendMessage(tabs[0].id, payload).catch(function(){});
140
+ }
141
+ });
142
+ // Send to activeSessionTabId if set
143
+ if (activeSessionTabId) {
144
+ browserAPI.tabs.sendMessage(activeSessionTabId, payload).catch(function(){});
145
+ }
146
+ // Fallback to all controlled tabs
147
+ controlledTabs.forEach(tabId => {
148
+ browserAPI.tabs.sendMessage(tabId, payload).catch(function(){});
149
+ });
150
+ }
151
+
152
+ if (message.type === 'WT_TOGGLE_MASTER_VISUALS') {
153
+ browserAPI.storage.local.set({ wt_master_visuals: message.enabled }).catch(() => {});
154
+ controlledTabs.forEach(tabId => {
155
+ browserAPI.tabs.sendMessage(tabId, { type: 'WT_TOGGLE_MASTER_VISUALS', enabled: message.enabled }).catch(() => {});
156
+ });
157
+ sendResponse({ ok: true });
158
+ }
159
+
160
+ if (message.type === 'Weavetab_STATE') {
161
+ controlledTabs.forEach(tabId => {
162
+ browserAPI.tabs.sendMessage(tabId, message).catch(() => {});
163
+ });
164
+ sendResponse({ ok: true });
165
+ }
166
+ if (message.type === 'Weavetab_CONFIG') {
167
+ const { groupTabs } = message;
168
+ browserAPI.storage.local.set({
169
+ weaveGroupEnabled: groupTabs !== false,
170
+ }).catch(() => {});
171
+ sendResponse({ ok: true });
172
+ }
173
+ if (message.type === 'Weavetab_GROUP_STATE') {
174
+ const { state, word } = message;
175
+ currentGroupState = state;
176
+ refreshStateTimeout();
177
+ const color = STATE_COLORS[state] || 'blue';
178
+ const badge = STATE_BADGE[state] || STATE_BADGE.idle;
179
+
180
+ // Track which tab relayed this state update (the active session tab)
181
+ if (sender.tab) activeSessionTabId = sender.tab.id;
182
+ // Per-tab badge
183
+ if (browserAPI.action) {
184
+ const ids = Array.from(controlledTabs);
185
+ ids.forEach(tid => {
186
+ if (badge.text) {
187
+ browserAPI.action.setBadgeText({ tabId: tid, text: badge.text }).catch(() => {});
188
+ if (badge.color) {
189
+ browserAPI.action.setBadgeBackgroundColor({ tabId: tid, color: badge.color }).catch(() => {});
190
+ }
191
+ } else {
192
+ browserAPI.action.setBadgeText({ tabId: tid, text: '' }).catch(() => {});
193
+ }
194
+ });
195
+ }
196
+
197
+ // Broadcast state to all controlled tabs so they update their favicons
198
+ controlledTabs.forEach(tabId => {
199
+ browserAPI.tabs.sendMessage(tabId, { type: 'Weavetab_GROUP_STATE', state, isActive: tabId === activeSessionTabId }).catch(() => {});
200
+ });
201
+
202
+ sendResponse({ ok: true });
203
+ }
204
+ if (message.type === 'REGISTER_TAB') {
205
+ // Skip about:blank and chrome:// pages
206
+ if (sender.tab.url === 'about:blank' || sender.tab.url.startsWith('chrome://')) {
207
+ sendResponse({ ok: true, skipped: true });
208
+ return;
209
+ }
210
+ controlledTabs.add(sender.tab.id);
211
+ refreshStateTimeout();
212
+
213
+ // Immediately inform the tab of the current group state so it can set its favicon
214
+ browserAPI.tabs.sendMessage(sender.tab.id, { type: 'Weavetab_GROUP_STATE', state: currentGroupState, isActive: sender.tab.id === activeSessionTabId }).catch(() => {});
215
+
216
+ updateBadge();
217
+ sendResponse({ ok: true });
218
+ }
219
+
220
+ if (message.type === 'NETWORK_INTERCEPT_ENABLE') {
221
+ enableIntercept(sender.tab.id, message.pattern, message.mockStatus, message.mockBody)
222
+ .then(res => sendResponse(res));
223
+ return true;
224
+ }
225
+ if (message.type === 'NETWORK_INTERCEPT_DISABLE') {
226
+ disableIntercept(sender.tab.id)
227
+ .then(res => sendResponse(res));
228
+ return true;
229
+ }
230
+ if (message.type === 'GET_ANCHOR_STATUS') {
231
+ sendResponse({ isAnchor: anchorTabs.has(sender.tab.id) });
232
+ return true;
233
+ }
234
+ if (message.type === 'SET_ANCHOR_TAB') {
235
+ if (message.anchor) anchorTabs.add(sender.tab.id);
236
+ else anchorTabs.delete(sender.tab.id);
237
+ sendResponse({ ok: true });
238
+ }
239
+
240
+ // ── User feedback relay: content script → storage queue ───────────────────
241
+ // CDP polls window.__Weavetab.popUserFeedback() or window.__wt_chat_queue which reads from this queue.
242
+ if (message.type === 'USER_FEEDBACK' || message.type === 'WT_USER_NOTE') {
243
+ if (message.text || message.snipe) {
244
+ const noteEntry = {
245
+ text: message.text || '',
246
+ snipe: message.snipe || null,
247
+ ts: message.ts || Date.now(),
248
+ tabId: sender.tab && sender.tab.id
249
+ };
250
+
251
+ browserAPI.storage.local.get('__wt_feedback_queue', function(res) {
252
+ const queue = res['__wt_feedback_queue'] || [];
253
+ queue.push(noteEntry);
254
+ // Cap queue at 20 entries
255
+ if (queue.length > 20) queue.splice(0, queue.length - 20);
256
+ browserAPI.storage.local.set({ '__wt_feedback_queue': queue });
257
+ });
258
+
259
+ // Forward directly to active session tab content script for immediate DOM queue push & interrupt
260
+ browserAPI.tabs.query({ active: true, currentWindow: true }, (tabs) => {
261
+ if (tabs[0]?.id) {
262
+ browserAPI.tabs.sendMessage(tabs[0].id, {
263
+ type: 'WT_SNIPE_INJECT',
264
+ entry: noteEntry
265
+ }).catch(() => {});
266
+ }
267
+ });
268
+ }
269
+ sendResponse({ ok: true });
270
+ }
271
+
272
+
273
+ // Alert tab: mark tabs with alert dialogs waiting + write to storage so CDP can see it
274
+ if (message.type === 'TAB_ALERT_WAITING') {
275
+ const alertTabId = sender.tab && sender.tab.id;
276
+ if (alertTabId) {
277
+ browserAPI.tabs.sendMessage(alertTabId, { type: 'Weavetab_GROUP_STATE', state: 'weaving', isActive: false }).catch(() => {});
278
+ // Write alert state to storage — CDP polls __Weavetab.getAlertState()
279
+ browserAPI.storage.local.set({ '__wt_alert_state': { pending: true, tabId: alertTabId, ts: Date.now() } });
280
+ }
281
+ sendResponse({ ok: true });
282
+ }
283
+ if (message.type === 'TAB_ALERT_CLEARED') {
284
+ const alertTabId = sender.tab && sender.tab.id;
285
+ if (alertTabId) {
286
+ browserAPI.tabs.sendMessage(alertTabId, { type: 'Weavetab_GROUP_STATE', state: 'idle', isActive: false }).catch(() => {});
287
+ browserAPI.storage.local.set({ '__wt_alert_state': { pending: false, tabId: alertTabId, ts: Date.now() } });
288
+ }
289
+ sendResponse({ ok: true });
290
+ }
291
+ if (message.type === 'RELOAD_EXTENSION') {
292
+ sendResponse({ ok: true });
293
+ browserAPI.runtime.reload();
294
+ }
295
+
296
+
297
+ });
298
+
299
+ browserAPI.tabs.onRemoved.addListener((tabId) => {
300
+ if (controlledTabs.has(tabId)) {
301
+ controlledTabs.delete(tabId);
302
+ updateBadge();
303
+ }
304
+ anchorTabs.delete(tabId);
305
+ });
306
+
307
+
308
+ // Startup cleanup: Ungroup all Weavetab tabs and clear badges
309
+ browserAPI.runtime.onInstalled.addListener(() => {
310
+ browserAPI.tabs.query({}, (tabs) => {
311
+ const ids = tabs.map(t => t.id).filter(id => id !== undefined);
312
+ if (ids.length > 0) {
313
+ browserAPI.tabs.ungroup(ids).catch(() => {});
314
+ }
315
+ });
316
+ });
317
+
318
+ function updateBadge() {
319
+ if (!browserAPI.action) return;
320
+ const ids = Array.from(controlledTabs);
321
+ if (ids.length === 0) {
322
+ browserAPI.action.setBadgeText({ text: '' }).catch(() => {});
323
+ } else {
324
+ ids.forEach(tid => {
325
+ // Just set the dot, but don't group
326
+ browserAPI.action.setBadgeText({ tabId: tid, text: '\u25CF' }).catch(() => {});
327
+ browserAPI.action.setBadgeBackgroundColor({ tabId: tid, color: '#3B82F6' }).catch(() => {});
328
+ });
329
+ }
330
+ }
331
+
332
+ // On startup: close stale blank tabs — but NEVER the last remaining tab.
333
+ // Weavetab launches Chrome on about:blank, so removing the only tab closes
334
+ // the browser window (graceful shutdown ~1s after launch → ECONNREFUSED loop).
335
+ browserAPI.runtime.onStartup.addListener(async () => {
336
+ try {
337
+ const blanks = await browserAPI.tabs.query({ url: 'about:blank' });
338
+ if (blanks.length === 0) return;
339
+ const all = await browserAPI.tabs.query({});
340
+ // Keep one tab alive if every tab would otherwise be closed.
341
+ const mustKeep = blanks.length === all.length ? 1 : 0;
342
+ blanks.slice(0, blanks.length - mustKeep).forEach(t =>
343
+ browserAPI.tabs.remove(t.id).catch(() => {})
344
+ );
345
+ } catch (e) {
346
+ // Non-fatal: never let tab cleanup crash the service worker or close the browser.
347
+ }
348
+ });
349
+
350
+ // Network blocking for speed (delayed to avoid 'No SW' on cold start)
351
+ // Guard: only run if the service worker is still alive (not being terminated)
352
+ setTimeout(function() {
353
+ // Check the runtime is still valid before calling declarativeNetRequest
354
+ try {
355
+ if (!browserAPI.runtime || !browserAPI.runtime.id) return;
356
+ browserAPI.declarativeNetRequest.updateDynamicRules({
357
+ removeRuleIds: [1, 2, 3, 4],
358
+ addRules: [
359
+ { id: 1, priority: 1, action: { type: 'block' }, condition: { urlFilter: '*google-analytics.com*', resourceTypes: ['script', 'xmlhttprequest', 'ping'] } },
360
+ { id: 2, priority: 1, action: { type: 'block' }, condition: { urlFilter: '*doubleclick.net*', resourceTypes: ['script', 'xmlhttprequest', 'ping', 'image', 'sub_frame'] } },
361
+ { id: 3, priority: 1, action: { type: 'block' }, condition: { urlFilter: '*facebook.net/en_US/fbevents.js*', resourceTypes: ['script'] } }
362
+ // NOTE: GTM blocking removed — some sites bundle critical resources through GTM
363
+ ]
364
+ }).catch(function(e) {
365
+ // Non-fatal — log for debugging only
366
+ console.warn('[Weavetab] Dynamic rules update failed (non-fatal):', e && e.message);
367
+ });
368
+ } catch(e) {
369
+ // Service worker terminated or runtime invalidated — safe to ignore
370
+ }
371
+ }, 1000);
372
+
373
+ // ─── Omnibox: Weavetab:// protocol via browserAPI.omnibox ─────────────────────
374
+
375
+ const DASHBOARD_URL = browserAPI.runtime.getURL('dashboard.html');
376
+
377
+ browserAPI.omnibox.onInputStarted.addListener(() => {
378
+ browserAPI.omnibox.setDefaultSuggestion({
379
+ description: 'Open Weavetab Dashboard <url>chrome-extension://...</url>'
380
+ });
381
+ });
382
+
383
+ browserAPI.omnibox.onInputChanged.addListener((text, suggest) => {
384
+ const suggestions = [];
385
+ const lower = text.toLowerCase();
386
+ if (!lower || lower === 'dashboard' || 'dashboard'.startsWith(lower)) {
387
+ suggestions.push({
388
+ content: 'dashboard',
389
+ description: 'Open Weavetab Dashboard'
390
+ });
391
+ }
392
+ if ('help'.startsWith(lower)) {
393
+ suggestions.push({
394
+ content: 'help',
395
+ description: 'Weavetab Help & Documentation'
396
+ });
397
+ }
398
+ suggest(suggestions);
399
+ });
400
+
401
+ browserAPI.omnibox.onInputEntered.addListener((text, disposition) => {
402
+ const branch = text && text !== 'dashboard' ? encodeURIComponent(text) : 'default';
403
+ const url = DASHBOARD_URL + `?branch=${branch}`;
404
+ switch (disposition) {
405
+ case 'currentTab':
406
+ browserAPI.tabs.update({ url });
407
+ break;
408
+ case 'newForegroundTab':
409
+ browserAPI.tabs.create({ url });
410
+ break;
411
+ case 'newBackgroundTab':
412
+ browserAPI.tabs.create({ url, active: false });
413
+ break;
414
+ }
415
+ });