@weavetab/mcp 2.5.0-beta.0 → 2.5.0-beta.2

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 (98) hide show
  1. package/AGENTS.md +1 -0
  2. package/LICENSE +2 -2
  3. package/README.md +208 -43
  4. package/SECURITY.md +3 -3
  5. package/TROUBLESHOOTING.md +1 -1
  6. package/dist/cdp/bridge.d.ts +10 -0
  7. package/dist/cdp/bridge.js +288 -68
  8. package/dist/cdp/connector.d.ts +5 -3
  9. package/dist/cdp/connector.js +188 -74
  10. package/dist/cdp/extension.js +0 -1
  11. package/dist/cdp/helpers.js +0 -2
  12. package/dist/cdp/lock.d.ts +9 -6
  13. package/dist/cdp/lock.js +72 -59
  14. package/dist/cdp/omni_broker.d.ts +1 -0
  15. package/dist/cdp/omni_broker.js +12 -6
  16. package/dist/cdp/profiles.d.ts +10 -0
  17. package/dist/cdp/profiles.js +65 -0
  18. package/dist/cdp/walker.js +50 -0
  19. package/dist/cli/plugin.d.ts +0 -2
  20. package/dist/cli/plugin.js +31 -683
  21. package/dist/cli/updater.js +2 -2
  22. package/dist/config/loader.d.ts +25 -6
  23. package/dist/config/loader.js +133 -20
  24. package/dist/index.js +48 -52
  25. package/dist/lifecycle/mission_manager.d.ts +0 -1
  26. package/dist/lifecycle/mission_manager.js +0 -3
  27. package/dist/overlay/ghost.d.ts +4 -0
  28. package/dist/overlay/ghost.js +118 -94
  29. package/dist/overlay/injector.d.ts +1 -1
  30. package/dist/overlay/injector.js +1 -15
  31. package/dist/plugin/extension_bridge.d.ts +40 -0
  32. package/dist/plugin/extension_bridge.js +158 -0
  33. package/dist/plugin/interface.d.ts +78 -16
  34. package/dist/plugin/loader.d.ts +4 -0
  35. package/dist/plugin/loader.js +178 -57
  36. package/dist/plugin/registry.js +11 -1
  37. package/dist/plugin/storage.d.ts +51 -0
  38. package/dist/plugin/storage.js +164 -0
  39. package/dist/server.js +217 -151
  40. package/dist/state/session.d.ts +2 -2
  41. package/dist/state/session.js +33 -13
  42. package/dist/tools/browser_detect.d.ts +2 -1
  43. package/dist/tools/browser_detect.js +6 -5
  44. package/dist/tools/burst.d.ts +1 -0
  45. package/dist/tools/burst.js +4 -6
  46. package/dist/tools/click.js +23 -28
  47. package/dist/tools/clipboard.js +31 -24
  48. package/dist/tools/navigate.d.ts +1 -1
  49. package/dist/tools/navigate.js +28 -10
  50. package/dist/tools/read.js +1 -1
  51. package/dist/tools/tabs.d.ts +10 -10
  52. package/dist/tools/tabs.js +47 -39
  53. package/dist/tools/type.js +2 -2
  54. package/docs/DOCUMENTATION.md +63 -6
  55. package/docs/architecture/blind-injection.md +20 -0
  56. package/docs/architecture/dual-injection.md +32 -0
  57. package/docs/architecture/overview.md +49 -0
  58. package/docs/architecture/security.md +35 -0
  59. package/docs/architecture/transport.md +37 -0
  60. package/docs/captcha-stealth/captcha-solver.md +75 -0
  61. package/docs/captcha-stealth/stealth-physics.md +60 -0
  62. package/docs/cli/cli-commands.md +44 -0
  63. package/docs/cli/cli-flags.md +20 -0
  64. package/docs/cli/cli-introduction.md +20 -0
  65. package/docs/cli/cli-scripting.md +32 -0
  66. package/docs/cli/g-ide/ide-antigravity.md +24 -0
  67. package/docs/cli/g-ide/ide-claude-code.md +24 -0
  68. package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
  69. package/docs/cli/g-ide/ide-cursor.md +31 -0
  70. package/docs/cli/g-ide/ide-generic.md +34 -0
  71. package/docs/cli/g-ide/ide-opencode.md +24 -0
  72. package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
  73. package/docs/cli/g-ide/ide-windsurf.md +24 -0
  74. package/docs/configuration/config-locations.md +24 -0
  75. package/docs/configuration/configuration.md +73 -0
  76. package/docs/configuration/env-variables.md +19 -0
  77. package/docs/configuration/folders.md +54 -0
  78. package/docs/desktop-automation/electron-apps.md +162 -0
  79. package/docs/get-started/installation.md +47 -0
  80. package/docs/get-started/introduction.md +60 -0
  81. package/docs/get-started/quickstart.md +54 -0
  82. package/docs/get-started/setup-wizard.md +26 -0
  83. package/docs/skills-ecosystem/skills-overview.md +35 -0
  84. package/docs/skills-ecosystem/skills-recipes.md +36 -0
  85. package/docs/tools/tools.md +293 -0
  86. package/docs/troubleshooting/trouble-browser.md +27 -0
  87. package/docs/troubleshooting/trouble-config.md +20 -0
  88. package/docs/troubleshooting/trouble-loop.md +31 -0
  89. package/ext-dist/content.js +126 -101
  90. package/mcp.json +8 -15
  91. package/package.json +11 -5
  92. package/server.json +3 -3
  93. package/dist/tools/plan.d.ts +0 -39
  94. package/dist/tools/plan.js +0 -161
  95. package/docs/DEV_MODE.md +0 -68
  96. package/docs/PLUGINS.md +0 -538
  97. package/docs/TOOLS.md +0 -104
  98. package/docs/dev_plugin.md +0 -310
@@ -7,9 +7,9 @@ import { logDebug } from "../audit/logger.js";
7
7
  */
8
8
  import CDP from "chrome-remote-interface";
9
9
  import { blockStorageAccess } from "../security/guard.js";
10
- import { launchWithBridge, injectStealthScript } from "./bridge.js";
10
+ import { launchWithBridge, launchElectronApp, injectStealthScript } from "./bridge.js";
11
11
  import { setupOverlay, ensureVisualLayer } from "../overlay/injector.js";
12
- import { onConfigChange, saveConfig, loadConfigSync, getCachedConfig, getConfigAgentId, getConfigCookies, getConfigProfileDir, getConfigMode } from "../config/loader.js";
12
+ import { onConfigChange, saveConfig, loadConfigSync, getCachedConfig, getConfigAgentId, getConfigCookies, getConfigProfileDir, getConfigMode, getElectronAppConfig, getConfigExtension } from "../config/loader.js";
13
13
  import { sessionRegistry } from "../state/session.js";
14
14
  import { initConsoleBuffering } from "../tools/console.js";
15
15
  import { registerMutationListener, clearTabCache } from "../state/cache.js";
@@ -95,12 +95,21 @@ async function waitForPageReady(session) {
95
95
  });
96
96
  }
97
97
  // ─── Session init (overlay + re-injection) ───────────────────────────────────
98
- async function initSession(session, targetId, port, config) {
98
+ async function initSession(session, targetId, port, config, isElectron) {
99
99
  initDialogHandler(session);
100
100
  // Initialize Omni-Context Broker for iframes
101
101
  const { initOmniBroker } = await import("./omni_broker.js");
102
102
  await initOmniBroker(session, targetId, port);
103
- if (activeEngine === "chromium") {
103
+ if (isElectron) {
104
+ // Electron desktop app initialization
105
+ try {
106
+ await session.Page.addScriptToEvaluateOnNewDocument({ source: WT_REGISTRY_INIT_EXPR });
107
+ }
108
+ catch { /* non-fatal */ }
109
+ // Direct DOM minimal overlay injection without Chrome Extension
110
+ await ensureVisualLayer(session, config);
111
+ }
112
+ else if (activeEngine === "chromium") {
104
113
  // Only block cookies/storage when cookies mode is disabled
105
114
  if (!getConfigCookies(config)) {
106
115
  await blockStorageAccess(session);
@@ -142,7 +151,7 @@ async function initSession(session, targetId, port, config) {
142
151
  clearTabCache(targetId);
143
152
  // Invalidate URC on navigation — elements from the old page are stale
144
153
  urcInvalidate(targetId);
145
- if (activeEngine === "chromium") {
154
+ if (isElectron || activeEngine === "chromium") {
146
155
  try {
147
156
  await waitForPageReady(session);
148
157
  const cfg = getCachedConfig();
@@ -150,15 +159,12 @@ async function initSession(session, targetId, port, config) {
150
159
  if (clientRef)
151
160
  clientRef.overlayInstalled = false;
152
161
  await ensureVisualLayer(session, cfg, clientRef ?? undefined);
153
- // Re-inject active plan into the new page
154
- const { restoreActivePlanIntoPage } = await import("../tools/plan.js");
155
- await restoreActivePlanIntoPage(session);
156
162
  }
157
163
  catch { /* ignore */ }
158
164
  }
159
165
  });
160
- // Diagnostic: verify extension content script loaded
161
- if (activeEngine === "chromium") {
166
+ // Diagnostic: verify extension content script loaded (Standard browser only)
167
+ if (!isElectron && activeEngine === "chromium" && getConfigExtension(config) !== false) {
162
168
  try {
163
169
  // Check for multiple indicators that extension is loaded
164
170
  const { result: extCheck } = await session.Runtime.evaluate({
@@ -177,29 +183,12 @@ async function initSession(session, targetId, port, config) {
177
183
  else {
178
184
  logDebug(`[Weavetab] [!] Extension content script NOT detected — HUD/overlay may not work`);
179
185
  logDebug(`[Weavetab] [!] Try reloading page or check if extension loaded in browser://extensions`);
180
- // Try to inject a basic error indicator into the page
181
- try {
182
- await session.Runtime.evaluate({
183
- expression: `
184
- (function() {
185
- const div = document.createElement('div');
186
- div.style.cssText = 'position:fixed;top:10px;right:10px;background:red;color:white;padding:10px;z-index:999999;font-family:sans-serif;font-size:14px;';
187
- div.textContent = 'WeaveTab Extension Not Loaded';
188
- document.body.appendChild(div);
189
- setTimeout(() => div.remove(), 5000);
190
- })()
191
- `
192
- });
193
- }
194
- catch { /* ignore */ }
195
186
  }
196
187
  }
197
188
  catch (checkErr) {
198
189
  logDebug(`[Weavetab] [!] Extension health check failed: ${checkErr}`);
199
190
  }
200
- }
201
- // Push config + initial idle state to extension right away (Chrome only)
202
- if (activeEngine === "chromium") {
191
+ // Push config + initial idle state to extension right away (Chrome only)
203
192
  const { sendToExtension } = await import("../cdp/helpers.js");
204
193
  sendToExtension(session, {
205
194
  type: 'Weavetab_CONFIG',
@@ -210,8 +199,7 @@ async function initSession(session, targetId, port, config) {
210
199
  state: 'idle',
211
200
  }).catch(() => { });
212
201
  }
213
- // Analytics ping every 5s — stored per-tab so each interval can be
214
- // independently cancelled when the tab closes or cleanupAll() is called.
202
+ // Analytics ping every 30s
215
203
  const sessionStartTime = Date.now();
216
204
  const analyticsInterval = setInterval(async () => {
217
205
  try {
@@ -228,34 +216,135 @@ async function initSession(session, targetId, port, config) {
228
216
  }).catch(() => { });
229
217
  }
230
218
  catch { /* ignore */ }
231
- }, 30_000); // 30s interval — reduced from 5s to cut idle CDP overhead (6 round-trips/min → 1)
232
- // Register in the per-tab map so cleanupAll() and the session-close handler can
233
- // cancel it without relying on a single overwriteable global slot.
219
+ }, 30_000);
234
220
  analyticsIntervals.set(targetId, analyticsInterval);
235
- // unref() so this polling interval does not pin the Node.js event loop.
236
- // An orphaned MCP process (IDE disconnected, browser closed) would otherwise
237
- // stay alive indefinitely due to this 30s interval firing forever.
238
221
  analyticsInterval.unref();
239
222
  }
240
223
  // ─── Main connection entry point ──────────────────────────────────────────────
241
- export async function getOrConnect(config) {
242
- if (getConfigMode(config) === "attach") {
224
+ export async function getOrConnect(config, targetApp) {
225
+ if (getConfigMode(config) === "attach" && !targetApp) {
243
226
  throw new Error("[!] Attach mode (connect to your real browser) coming soon. Use session mode for now.");
244
227
  }
245
- const activeClient = sessionRegistry.getActive(getConfigAgentId(config));
228
+ // 1. Check existing active session for this agent & app
229
+ const activeClient = sessionRegistry.getActive(getConfigAgentId(config), targetApp);
246
230
  if (activeClient) {
247
231
  try {
248
232
  await activeClient.session.Browser.getVersion();
249
233
  return activeClient;
250
234
  }
251
235
  catch {
252
- logDebug("[Weavetab CDP] Connection lost. Reconnecting...");
236
+ logDebug(`[Weavetab CDP] Connection lost for ${targetApp || "browser"}. Reconnecting...`);
253
237
  clearProfileVerificationCache(activeClient.targetId);
254
238
  sessionRegistry.unregister(activeClient.targetId);
255
239
  }
256
240
  }
257
- const releaseMutex = await acquireMutex();
241
+ const releaseMutex = await acquireMutex(targetApp);
258
242
  try {
243
+ if (targetApp) {
244
+ // ──────────────── ELECTRON APP CONNECTION FLOW ────────────────
245
+ const appCfg = getElectronAppConfig(targetApp, config);
246
+ if (!appCfg) {
247
+ throw new Error(`[Weavetab] ✗ Electron app "${targetApp}" is not configured in config.json.\n` +
248
+ ` Please add an entry under "electron": { "${targetApp}": "C:\\\\path\\\\to\\\\app.exe" } in your config.`);
249
+ }
250
+ let port = null;
251
+ const sessionLock = readLock(targetApp);
252
+ if (sessionLock && sessionLock.mode === "session") {
253
+ if (isLockPending(sessionLock)) {
254
+ logDebug(`[Weavetab] Launch in progress for ${targetApp} by PID ${sessionLock.pid}. Waiting up to 5s...`);
255
+ for (let i = 0; i < 5; i++) {
256
+ await new Promise(r => setTimeout(r, 1000));
257
+ const updatedLock = readLock(targetApp);
258
+ if (!updatedLock || !isLockPending(updatedLock))
259
+ break;
260
+ }
261
+ }
262
+ const freshLock = readLock(targetApp);
263
+ if (freshLock && (await isLockValid(freshLock))) {
264
+ port = freshLock.port;
265
+ }
266
+ else {
267
+ clearLock(targetApp);
268
+ }
269
+ }
270
+ let targets = [];
271
+ if (port) {
272
+ try {
273
+ targets = await withCDPTimeout(CDP.List({ host: "127.0.0.1", port }), 3000, "CDP.List existing Electron");
274
+ }
275
+ catch {
276
+ port = null;
277
+ }
278
+ }
279
+ if (!port || targets.length === 0) {
280
+ logDebug(`[Weavetab CDP] Launching Electron app ${targetApp}...`);
281
+ const launchResult = await launchElectronApp(targetApp, config);
282
+ port = launchResult.port;
283
+ // Poll for target window availability (Electron may take up to 2-3s to render initial window)
284
+ for (let retry = 0; retry < 10; retry++) {
285
+ try {
286
+ targets = await withCDPTimeout(CDP.List({ host: "127.0.0.1", port }), 2000, "CDP.List Electron retry");
287
+ if (targets.length > 0)
288
+ break;
289
+ }
290
+ catch { /* wait */ }
291
+ await new Promise(r => setTimeout(r, 500));
292
+ }
293
+ }
294
+ let pageTarget = targets.find(t => t.type === "page" || t.type === "webview" || t.type === "app") ?? targets[0];
295
+ if (!pageTarget) {
296
+ throw new Error(`[Weavetab] ✗ No window or webview target found in Electron app "${targetApp}".`);
297
+ }
298
+ const targetId = pageTarget.id || pageTarget.targetId;
299
+ const session = await withCDPTimeout(CDP({ host: "127.0.0.1", port, target: targetId }), 5000, "CDP connect Electron target");
300
+ session.on("close", () => {
301
+ logDebug(`[Weavetab CDP] Electron session WebSocket closed (${targetApp}) — unregistering.`);
302
+ const iv = analyticsIntervals.get(targetId);
303
+ if (iv) {
304
+ clearInterval(iv);
305
+ analyticsIntervals.delete(targetId);
306
+ }
307
+ clearProfileVerificationCache(targetId);
308
+ import("./omni_broker.js").then(m => m.cleanupOmniBroker(targetId)).catch(() => { });
309
+ import("../tools/network_intercept.js").then(m => m.cleanupNetworkIntercept(session)).catch(() => { });
310
+ sessionRegistry.unregister(targetId);
311
+ });
312
+ session.on("error", (err) => {
313
+ logDebug(`[Weavetab CDP] Electron session WebSocket error (${targetApp}):`, err?.message ?? err);
314
+ });
315
+ // Validate connection health
316
+ try {
317
+ await session.Browser.getVersion();
318
+ }
319
+ catch {
320
+ throw new Error("Failed health check: CDP session connected to Electron app but getVersion failed.");
321
+ }
322
+ await Promise.all([
323
+ session.Accessibility.enable(),
324
+ session.DOM.enable(),
325
+ session.Page.enable(),
326
+ session.Runtime.enable(),
327
+ session.Network.enable().catch(() => { }),
328
+ session.Performance.enable().catch(() => { })
329
+ ]);
330
+ await wireUiConfigBinding(session);
331
+ await initSession(session, targetId, port, config, true);
332
+ await session.Target.setDiscoverTargets({ discover: true }).catch(() => { });
333
+ logDebug(`[Weavetab CDP] [OK] Active Electron target: ${pageTarget.title || pageTarget.url || targetApp}`);
334
+ const client = {
335
+ session,
336
+ targetId,
337
+ url: pageTarget.url || "",
338
+ title: pageTarget.title || targetApp,
339
+ overlayInstalled: false,
340
+ agentId: getConfigAgentId(config),
341
+ appName: targetApp,
342
+ isElectron: true,
343
+ };
344
+ sessionRegistry.register(targetId, client);
345
+ return client;
346
+ }
347
+ // ──────────────── STANDARD WEB BROWSER FLOW ────────────────
259
348
  const sessionLock = readLock();
260
349
  if (sessionLock && sessionLock.mode === "session") {
261
350
  if (isLockPending(sessionLock)) {
@@ -440,7 +529,7 @@ export async function getOrConnect(config) {
440
529
  if (activatedId) {
441
530
  const client = sessionRegistry.getAll().find(c => c.targetId === activatedId);
442
531
  if (client) {
443
- sessionRegistry.setActive(activatedId, client.agentId);
532
+ sessionRegistry.setActive(activatedId, client.agentId, client.appName);
444
533
  }
445
534
  }
446
535
  });
@@ -494,28 +583,34 @@ export async function getOrConnect(config) {
494
583
  await releaseMutex();
495
584
  }
496
585
  }
497
- export async function getTabList(agentId) {
586
+ export async function getTabList(agentId, targetApp) {
498
587
  const config = getCachedConfig();
499
- // Validate the lock before blindly connecting (same two-factor check that
500
- // getOrConnect() performs). A stale lock after a server restart would otherwise
501
- // produce raw ECONNREFUSED.
502
- let port = actualPort;
503
- if (!port) {
504
- const lock = readLock();
505
- if (lock && lock.mode === "session") {
506
- const valid = await isLockValid(lock);
507
- if (valid) {
508
- port = lock.port;
509
- }
510
- else {
511
- logDebug("[Weavetab CDP] getTabList: stale lock detected — clearing.");
512
- clearLock();
513
- port = null;
588
+ let port = null;
589
+ if (targetApp) {
590
+ const lock = readLock(targetApp);
591
+ if (lock && (await isLockValid(lock))) {
592
+ port = lock.port;
593
+ }
594
+ }
595
+ else {
596
+ port = actualPort;
597
+ if (!port) {
598
+ const lock = readLock();
599
+ if (lock && lock.mode === "session") {
600
+ const valid = await isLockValid(lock);
601
+ if (valid) {
602
+ port = lock.port;
603
+ }
604
+ else {
605
+ logDebug("[Weavetab CDP] getTabList: stale lock detected — clearing.");
606
+ clearLock();
607
+ port = null;
608
+ }
514
609
  }
515
610
  }
516
611
  }
517
612
  if (!port)
518
- throw new Error("[!] No active browser session. Run browser_go first.");
613
+ throw new Error(`[!] No active ${targetApp ? targetApp : "browser"} session. Run browser_go or launch app first.`);
519
614
  try {
520
615
  const metaSession = await withCDPTimeout(CDP({ host: "127.0.0.1", port }), 5000, "getTabList CDP");
521
616
  const { targetInfos } = await withCDPTimeout(metaSession.Target.getTargets(), 5000, "getTabList getTargets");
@@ -523,21 +618,21 @@ export async function getTabList(agentId) {
523
618
  const aid = agentId || getConfigAgentId(config);
524
619
  const bctxId = sessionRegistry.getBrowserContextIdForAgent(aid);
525
620
  return targetInfos
526
- .filter(t => t.type === "page" && (!bctxId || t.browserContextId === bctxId))
621
+ .filter(t => (t.type === "page" || t.type === "webview" || t.type === "app" || t.type === "other" || t.type === "iframe") && (!bctxId || t.browserContextId === bctxId))
527
622
  .map(t => ({
528
623
  id: t.targetId,
529
- title: t.title,
624
+ title: t.title || t.url || t.type,
530
625
  url: t.url,
531
626
  type: t.type,
532
627
  }));
533
628
  }
534
629
  catch {
535
- throw new Error(`[!] Weavetab cannot reach browser on port ${port}.`);
630
+ throw new Error(`[!] Weavetab cannot reach ${targetApp ? targetApp : "browser"} on port ${port}.`);
536
631
  }
537
632
  }
538
633
  // Only allow the UI to update cosmetic/dev settings via the binding.
539
634
  // Never allow overwriting security-critical fields (role, browserPath, block, allow, profileDir, etc.).
540
- const UI_ALLOWED_FIELDS = new Set(["devMode", "visuals", "darkMode"]);
635
+ const UI_ALLOWED_FIELDS = new Set(["devMode", "extension"]);
541
636
  async function wireUiConfigBinding(session) {
542
637
  try {
543
638
  await session.Runtime.addBinding({ name: "__wt_save_ui_config" });
@@ -565,21 +660,38 @@ async function wireUiConfigBinding(session) {
565
660
  }
566
661
  catch { /* already bound */ }
567
662
  }
568
- export async function connectToTab(targetId, port, agentId, browserContextId) {
663
+ export async function connectToTab(targetId, port, agentId, browserContextId, appName) {
569
664
  const existing = sessionRegistry.getAll().find(c => c.targetId === targetId);
570
665
  if (existing)
571
666
  return existing;
572
- const lock = readLock();
573
- const p = port || actualPort || lock?.port;
667
+ const lock = readLock(appName);
668
+ const p = port || (appName ? lock?.port : (actualPort || lock?.port));
574
669
  if (!p)
575
- throw new Error("[!] No active browser session. Run browser_go first.");
576
- const targets = await withCDPTimeout(CDP.List({ host: "127.0.0.1", port: p }), 5000, "connectToTab CDP.List");
577
- const t = targets.find(t => t.id === targetId);
578
- if (!t)
579
- throw new Error("Tab not found");
670
+ throw new Error(`[!] No active ${appName || "browser"} session. Run browser_go first.`);
671
+ let t = null;
672
+ try {
673
+ const targets = await withCDPTimeout(CDP.List({ host: "127.0.0.1", port: p }), 3000, "connectToTab CDP.List");
674
+ t = targets.find((item) => (item.id || item.targetId) === targetId);
675
+ }
676
+ catch { }
677
+ if (!t) {
678
+ try {
679
+ const metaSession = await withCDPTimeout(CDP({ host: "127.0.0.1", port: p }), 3000, "connectToTab meta CDP");
680
+ const { targetInfos } = await withCDPTimeout(metaSession.Target.getTargets(), 3000, "connectToTab meta getTargets");
681
+ await metaSession.close();
682
+ const ti = targetInfos.find(item => item.targetId === targetId);
683
+ if (ti) {
684
+ t = { id: ti.targetId, targetId: ti.targetId, url: ti.url, title: ti.title, type: ti.type };
685
+ }
686
+ }
687
+ catch { }
688
+ }
689
+ if (!t) {
690
+ t = { id: targetId, targetId, url: "", title: appName || "Tab", type: "page" };
691
+ }
580
692
  const session = await withCDPTimeout(CDP({ host: "127.0.0.1", port: p, target: targetId }), 5000, "connectToTab CDP");
581
693
  session.on("close", () => {
582
- logDebug("[Weavetab CDP] connectToTab session closed — unregistering.");
694
+ logDebug(`[Weavetab CDP] connectToTab session closed (${appName || "browser"}) — unregistering.`);
583
695
  // Cancel this tab's analytics interval so it doesn't outlive the session
584
696
  const iv = analyticsIntervals.get(targetId);
585
697
  if (iv) {
@@ -592,7 +704,7 @@ export async function connectToTab(targetId, port, agentId, browserContextId) {
592
704
  sessionRegistry.unregister(targetId);
593
705
  });
594
706
  session.on("error", (err) => {
595
- logDebug("[Weavetab CDP] connectToTab session error:", err?.message ?? err);
707
+ logDebug(`[Weavetab CDP] connectToTab session error (${appName || "browser"}):`, err?.message ?? err);
596
708
  });
597
709
  await Promise.all([
598
710
  session.Accessibility.enable(),
@@ -604,7 +716,7 @@ export async function connectToTab(targetId, port, agentId, browserContextId) {
604
716
  ]);
605
717
  await wireUiConfigBinding(session);
606
718
  const currentConfig = getCachedConfig();
607
- await initSession(session, targetId, p, currentConfig);
719
+ await initSession(session, targetId, p, currentConfig, !!appName);
608
720
  // NOTE: initSession() already installs a frameNavigated handler that handles
609
721
  // clearSnapshot, clearViewportCache, and ensureVisualLayer. Do NOT add a second
610
722
  // one here — it would double the CDP work on every navigation.
@@ -617,6 +729,8 @@ export async function connectToTab(targetId, port, agentId, browserContextId) {
617
729
  overlayInstalled: false,
618
730
  agentId: actualAgentId,
619
731
  browserContextId,
732
+ appName,
733
+ isElectron: !!appName,
620
734
  };
621
735
  sessionRegistry.register(targetId, client);
622
736
  return client;
@@ -55,7 +55,6 @@ export async function extensionHealth(session) {
55
55
  if (document.querySelector('weavetab-host') || document.getElementById('__Weavetab_cursor_host')) features.push('cursor');
56
56
  if (document.querySelector('[data-wt-internal]') || document.getElementById('wt-ui-styles')) features.push('styles');
57
57
  if (document.querySelector('.wt-bubble')) features.push('bubble');
58
- if (document.getElementById('wt-plan-panel')) features.push('plan');
59
58
  return JSON.stringify({
60
59
  loaded: ${loaded},
61
60
  features
@@ -164,7 +164,6 @@ export async function hideWeaveTabOverlay(session) {
164
164
  // - [data-wt-ignore] : any element flagged as agent-invisible
165
165
  // - .wt-bubble : thought bubble / action text popups
166
166
  // - #__wt_ask_bridge : question input bridge element
167
- // - #wt-plan-dashboard : mission plan dashboard
168
167
  // - #wt-debug-overlay : debug overlay panel
169
168
  // - #wt-main-container : WeaveTab OS panel (todos + chat)
170
169
  style.textContent = [
@@ -175,7 +174,6 @@ export async function hideWeaveTabOverlay(session) {
175
174
  '[data-wt-ignore]',
176
175
  '.wt-bubble',
177
176
  '#__wt_ask_bridge',
178
- '#wt-plan-dashboard',
179
177
  '#wt-debug-overlay',
180
178
  '#wt-main-container',
181
179
  ].join(', ') + ' { display: none !important; visibility: hidden !important; opacity: 0 !important; }';
@@ -4,6 +4,7 @@ export interface LockData {
4
4
  profileDir: string;
5
5
  browserPath: string;
6
6
  mode: "session" | "attach";
7
+ appName?: string;
7
8
  pending?: boolean;
8
9
  timestamp?: number;
9
10
  fingerprint?: {
@@ -12,17 +13,19 @@ export interface LockData {
12
13
  cdpVersion?: string;
13
14
  };
14
15
  }
15
- declare function acquireMutex(): Promise<() => Promise<void>>;
16
- export declare function writeLockSafe(data: LockData): Promise<void>;
17
- export declare function writeLock(data: LockData): Promise<void>;
18
- export declare function readLock(): LockData | null;
19
- export declare function clearLock(): void;
16
+ export declare function getLockFilePath(appName?: string): string;
17
+ export declare function getMutexFilePath(appName?: string): string;
18
+ declare function acquireMutex(appName?: string): Promise<() => Promise<void>>;
19
+ export declare function writeLockSafe(data: LockData, appName?: string): Promise<void>;
20
+ export declare function writeLock(data: LockData, appName?: string): Promise<void>;
21
+ export declare function readLock(appName?: string): LockData | null;
22
+ export declare function clearLock(appName?: string): void;
20
23
  /**
21
24
  * Safe variant: only clears the lock if it was written by THIS process (process.pid).
22
25
  * Prevents a second MCP server instance from wiping the first instance's browser lock
23
26
  * during its retry loop, which would allow a race condition where two browsers spawn.
24
27
  */
25
- export declare function clearLockIfOwned(): void;
28
+ export declare function clearLockIfOwned(appName?: string): void;
26
29
  export declare function isProcessRunning(pid: number): boolean;
27
30
  /**
28
31
  * Two-factor lock validation: PID alive AND port accepting TCP connections.