@weavetab/mcp 2.5.0-beta.1 → 2.5.0-beta.4
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.
- package/AGENTS.md +2 -1
- package/ARCHITECTURE.md +6 -6
- package/LICENSE +1 -1
- package/README.md +164 -802
- package/SECURITY.md +33 -22
- package/TROUBLESHOOTING.md +3 -3
- package/dist/cdp/bridge.d.ts +10 -0
- package/dist/cdp/bridge.js +285 -78
- package/dist/cdp/connector.d.ts +7 -3
- package/dist/cdp/connector.js +196 -75
- package/dist/cdp/helpers.d.ts +4 -0
- package/dist/cdp/helpers.js +181 -0
- package/dist/cdp/lock.d.ts +9 -6
- package/dist/cdp/lock.js +72 -59
- package/dist/cdp/omni_broker.d.ts +2 -1
- package/dist/cdp/omni_broker.js +13 -7
- package/dist/cdp/profiles.d.ts +10 -0
- package/dist/cdp/profiles.js +65 -0
- package/dist/cdp/walker.d.ts +25 -1
- package/dist/cdp/walker.js +354 -7
- package/dist/cli/plugin.d.ts +0 -2
- package/dist/cli/plugin.js +51 -696
- package/dist/cli/updater.d.ts +1 -1
- package/dist/cli/updater.js +27 -7
- package/dist/config/loader.d.ts +33 -6
- package/dist/config/loader.js +174 -22
- package/dist/desktop/discovery.d.ts +44 -0
- package/dist/desktop/discovery.js +364 -0
- package/dist/index.js +140 -64
- package/dist/intelligence/marks.d.ts +36 -0
- package/dist/intelligence/marks.js +30 -0
- package/dist/intelligence/patternCompiler.d.ts +118 -0
- package/dist/intelligence/patternCompiler.js +259 -0
- package/dist/intelligence/sessionMemory.d.ts +2 -0
- package/dist/intelligence/sessionMemory.js +18 -8
- package/dist/overlay/ghost.js +2 -2
- package/dist/overlay/injector.js +3 -5
- package/dist/plugin/extension_bridge.d.ts +40 -0
- package/dist/plugin/extension_bridge.js +158 -0
- package/dist/plugin/interface.d.ts +151 -15
- package/dist/plugin/loader.d.ts +19 -1
- package/dist/plugin/loader.js +331 -63
- package/dist/plugin/pipeline.d.ts +75 -0
- package/dist/plugin/pipeline.js +183 -0
- package/dist/plugin/registry.js +12 -1
- package/dist/plugin/storage.d.ts +51 -0
- package/dist/plugin/storage.js +164 -0
- package/dist/security/secrets.d.ts +1 -0
- package/dist/security/secrets.js +13 -0
- package/dist/sensors/actionability.js +1 -2
- package/dist/sensors/guard.js +4 -10
- package/dist/sensors/mutations.js +12 -0
- package/dist/sensors/operation_monitor.js +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +600 -236
- package/dist/state/agent.d.ts +1 -0
- package/dist/state/agent.js +3 -0
- package/dist/state/delta.d.ts +12 -4
- package/dist/state/delta.js +67 -14
- package/dist/state/session.d.ts +8 -2
- package/dist/state/session.js +60 -13
- package/dist/tools/browser_detect.d.ts +16 -2
- package/dist/tools/browser_detect.js +54 -6
- package/dist/tools/burst.d.ts +17 -7
- package/dist/tools/burst.js +240 -83
- package/dist/tools/checkpoint.d.ts +38 -0
- package/dist/tools/checkpoint.js +271 -0
- package/dist/tools/click.d.ts +1 -0
- package/dist/tools/click.js +97 -98
- package/dist/tools/clipboard.js +31 -24
- package/dist/tools/design_extract.js +1 -1
- package/dist/tools/eval.js +1 -1
- package/dist/tools/fill.d.ts +20 -1
- package/dist/tools/fill.js +199 -40
- package/dist/tools/find.d.ts +16 -0
- package/dist/tools/find.js +67 -44
- package/dist/tools/github/index.d.ts +19 -4
- package/dist/tools/github/index.js +40 -4
- package/dist/tools/highlight.d.ts +6 -2
- package/dist/tools/highlight.js +158 -72
- package/dist/tools/inspect.js +1 -1
- package/dist/tools/key.js +65 -56
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +57 -44
- package/dist/tools/pdf.js +21 -13
- package/dist/tools/pointer.d.ts +18 -3
- package/dist/tools/pointer.js +89 -1
- package/dist/tools/read.d.ts +28 -8
- package/dist/tools/read.js +230 -56
- package/dist/tools/scrape.js +67 -13
- package/dist/tools/screenshot.js +21 -16
- package/dist/tools/scroll.js +6 -11
- package/dist/tools/select.js +8 -2
- package/dist/tools/snapshot.js +3 -34
- package/dist/tools/tabs.d.ts +10 -10
- package/dist/tools/tabs.js +55 -39
- package/dist/tools/thought.d.ts +2 -0
- package/dist/tools/thought.js +13 -14
- package/dist/tools/type.d.ts +2 -0
- package/dist/tools/type.js +109 -60
- package/dist/tools/type_secret.js +42 -76
- package/dist/tools/upload.js +1 -1
- package/dist/tools/video_compiler.d.ts +1 -1
- package/dist/tools/video_compiler.js +11 -12
- package/dist/version.d.ts +5 -0
- package/dist/version.js +5 -0
- package/docs/DOCUMENTATION.md +82 -36
- package/docs/PLUGINS.md +82 -499
- package/docs/TOOLS.md +51 -99
- package/docs/architecture/blind-injection.md +83 -0
- package/docs/architecture/dual-injection.md +32 -0
- package/docs/architecture/overview.md +49 -0
- package/docs/architecture/security.md +35 -0
- package/docs/architecture/transport.md +37 -0
- package/docs/captcha-stealth/captcha-solver.md +75 -0
- package/docs/captcha-stealth/stealth-physics.md +60 -0
- package/docs/cli/cli-commands.md +44 -0
- package/docs/cli/cli-flags.md +20 -0
- package/docs/cli/cli-introduction.md +20 -0
- package/docs/cli/cli-scripting.md +32 -0
- package/docs/cli/g-ide/ide-antigravity.md +24 -0
- package/docs/cli/g-ide/ide-claude-code.md +24 -0
- package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
- package/docs/cli/g-ide/ide-cursor.md +31 -0
- package/docs/cli/g-ide/ide-generic.md +34 -0
- package/docs/cli/g-ide/ide-opencode.md +24 -0
- package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
- package/docs/cli/g-ide/ide-windsurf.md +24 -0
- package/docs/configuration/config-locations.md +24 -0
- package/docs/configuration/configuration.md +73 -0
- package/docs/configuration/env-variables.md +19 -0
- package/docs/configuration/folders.md +55 -0
- package/docs/desktop-automation/electron-apps.md +217 -0
- package/docs/get-started/installation.md +47 -0
- package/docs/get-started/introduction.md +60 -0
- package/docs/get-started/quickstart.md +54 -0
- package/docs/get-started/setup-wizard.md +26 -0
- package/docs/intelligence/site-profiles.md +151 -0
- package/docs/skills-ecosystem/skills-overview.md +35 -0
- package/docs/skills-ecosystem/skills-recipes.md +36 -0
- package/docs/tools/tools.md +631 -0
- package/docs/troubleshooting/trouble-browser.md +27 -0
- package/docs/troubleshooting/trouble-config.md +20 -0
- package/docs/troubleshooting/trouble-loop.md +31 -0
- package/ext-dist/background.js +10 -62
- package/ext-dist/content.js +333 -921
- package/ext-dist/hud/hud.js +9 -9
- package/ext-dist/manifest.json +2 -6
- package/ext-dist/styles.css +6 -1
- package/mcp.json +108 -72
- package/package.json +14 -9
- package/server.json +3 -3
- package/docs/DEV_MODE.md +0 -68
- package/docs/dev_plugin.md +0 -310
- package/ext-dist/sidepanel/sidepanel.js +0 -25
- package/ext-dist/sidepanel.html +0 -547
package/dist/cdp/connector.js
CHANGED
|
@@ -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";
|
|
@@ -17,6 +17,7 @@ import { recordTabActivity } from "../tools/tabs.js";
|
|
|
17
17
|
import { initDialogHandler } from "../tools/dialog.js";
|
|
18
18
|
import { WT_REGISTRY_INIT_EXPR } from "./walker.js";
|
|
19
19
|
import { urcInvalidate } from "./ref_cache.js";
|
|
20
|
+
import { marksInvalidate } from "../intelligence/marks.js";
|
|
20
21
|
import { readLock, clearLock, isLockValid, isLockPending, acquireMutex } from "./lock.js";
|
|
21
22
|
import { getSessionProfile } from "./profiles.js";
|
|
22
23
|
import { safelyKillWeavetabProcesses, verifyProfileIsolation, verifyBrowserFingerprint, clearProfileVerificationCache } from "./security.js";
|
|
@@ -95,12 +96,20 @@ async function waitForPageReady(session) {
|
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
// ─── Session init (overlay + re-injection) ───────────────────────────────────
|
|
98
|
-
async function initSession(session, targetId, port, config) {
|
|
99
|
+
async function initSession(session, targetId, port, config, isElectron) {
|
|
99
100
|
initDialogHandler(session);
|
|
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 (
|
|
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,8 @@ 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
|
-
|
|
154
|
+
marksInvalidate(targetId);
|
|
155
|
+
if (isElectron || activeEngine === "chromium") {
|
|
146
156
|
try {
|
|
147
157
|
await waitForPageReady(session);
|
|
148
158
|
const cfg = getCachedConfig();
|
|
@@ -154,8 +164,8 @@ async function initSession(session, targetId, port, config) {
|
|
|
154
164
|
catch { /* ignore */ }
|
|
155
165
|
}
|
|
156
166
|
});
|
|
157
|
-
// Diagnostic: verify extension content script loaded
|
|
158
|
-
if (activeEngine === "chromium") {
|
|
167
|
+
// Diagnostic: verify extension content script loaded (Standard browser only)
|
|
168
|
+
if (!isElectron && activeEngine === "chromium" && getConfigExtension(config) !== false) {
|
|
159
169
|
try {
|
|
160
170
|
// Check for multiple indicators that extension is loaded
|
|
161
171
|
const { result: extCheck } = await session.Runtime.evaluate({
|
|
@@ -174,29 +184,12 @@ async function initSession(session, targetId, port, config) {
|
|
|
174
184
|
else {
|
|
175
185
|
logDebug(`[Weavetab] [!] Extension content script NOT detected — HUD/overlay may not work`);
|
|
176
186
|
logDebug(`[Weavetab] [!] Try reloading page or check if extension loaded in browser://extensions`);
|
|
177
|
-
// Try to inject a basic error indicator into the page
|
|
178
|
-
try {
|
|
179
|
-
await session.Runtime.evaluate({
|
|
180
|
-
expression: `
|
|
181
|
-
(function() {
|
|
182
|
-
const div = document.createElement('div');
|
|
183
|
-
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;';
|
|
184
|
-
div.textContent = 'WeaveTab Extension Not Loaded';
|
|
185
|
-
document.body.appendChild(div);
|
|
186
|
-
setTimeout(() => div.remove(), 5000);
|
|
187
|
-
})()
|
|
188
|
-
`
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
catch { /* ignore */ }
|
|
192
187
|
}
|
|
193
188
|
}
|
|
194
189
|
catch (checkErr) {
|
|
195
190
|
logDebug(`[Weavetab] [!] Extension health check failed: ${checkErr}`);
|
|
196
191
|
}
|
|
197
|
-
|
|
198
|
-
// Push config + initial idle state to extension right away (Chrome only)
|
|
199
|
-
if (activeEngine === "chromium") {
|
|
192
|
+
// Push config + initial idle state to extension right away (Chrome only)
|
|
200
193
|
const { sendToExtension } = await import("../cdp/helpers.js");
|
|
201
194
|
sendToExtension(session, {
|
|
202
195
|
type: 'Weavetab_CONFIG',
|
|
@@ -207,8 +200,7 @@ async function initSession(session, targetId, port, config) {
|
|
|
207
200
|
state: 'idle',
|
|
208
201
|
}).catch(() => { });
|
|
209
202
|
}
|
|
210
|
-
// Analytics ping every
|
|
211
|
-
// independently cancelled when the tab closes or cleanupAll() is called.
|
|
203
|
+
// Analytics ping every 30s
|
|
212
204
|
const sessionStartTime = Date.now();
|
|
213
205
|
const analyticsInterval = setInterval(async () => {
|
|
214
206
|
try {
|
|
@@ -225,34 +217,138 @@ async function initSession(session, targetId, port, config) {
|
|
|
225
217
|
}).catch(() => { });
|
|
226
218
|
}
|
|
227
219
|
catch { /* ignore */ }
|
|
228
|
-
}, 30_000);
|
|
229
|
-
// Register in the per-tab map so cleanupAll() and the session-close handler can
|
|
230
|
-
// cancel it without relying on a single overwriteable global slot.
|
|
220
|
+
}, 30_000);
|
|
231
221
|
analyticsIntervals.set(targetId, analyticsInterval);
|
|
232
|
-
// unref() so this polling interval does not pin the Node.js event loop.
|
|
233
|
-
// An orphaned MCP process (IDE disconnected, browser closed) would otherwise
|
|
234
|
-
// stay alive indefinitely due to this 30s interval firing forever.
|
|
235
222
|
analyticsInterval.unref();
|
|
236
223
|
}
|
|
237
224
|
// ─── Main connection entry point ──────────────────────────────────────────────
|
|
238
|
-
export async function getOrConnect(config) {
|
|
239
|
-
if (getConfigMode(config) === "attach") {
|
|
240
|
-
|
|
225
|
+
export async function getOrConnect(config, targetApp) {
|
|
226
|
+
if (getConfigMode(config) === "attach" && !targetApp) {
|
|
227
|
+
// track as limitation - use session mode
|
|
228
|
+
const err = new Error("[!] Attach mode is not yet supported. Use session mode (default) instead.");
|
|
229
|
+
err.code = "NOT_IMPLEMENTED";
|
|
230
|
+
throw err;
|
|
241
231
|
}
|
|
242
|
-
|
|
232
|
+
// 1. Check existing active session for this agent & app
|
|
233
|
+
const activeClient = sessionRegistry.getActive(getConfigAgentId(config), targetApp);
|
|
243
234
|
if (activeClient) {
|
|
244
235
|
try {
|
|
245
236
|
await activeClient.session.Browser.getVersion();
|
|
246
237
|
return activeClient;
|
|
247
238
|
}
|
|
248
239
|
catch {
|
|
249
|
-
logDebug(
|
|
240
|
+
logDebug(`[Weavetab CDP] Connection lost for ${targetApp || "browser"}. Reconnecting...`);
|
|
250
241
|
clearProfileVerificationCache(activeClient.targetId);
|
|
251
242
|
sessionRegistry.unregister(activeClient.targetId);
|
|
252
243
|
}
|
|
253
244
|
}
|
|
254
|
-
const releaseMutex = await acquireMutex();
|
|
245
|
+
const releaseMutex = await acquireMutex(targetApp);
|
|
255
246
|
try {
|
|
247
|
+
if (targetApp) {
|
|
248
|
+
// ──────────────── ELECTRON APP CONNECTION FLOW ────────────────
|
|
249
|
+
const appCfg = getElectronAppConfig(targetApp, config);
|
|
250
|
+
if (!appCfg) {
|
|
251
|
+
throw new Error(`[Weavetab] ✗ Electron app "${targetApp}" is not configured in config.json.\n` +
|
|
252
|
+
` Please add an entry under "electron": { "${targetApp}": "C:\\\\path\\\\to\\\\app.exe" } in your config.`);
|
|
253
|
+
}
|
|
254
|
+
let port = null;
|
|
255
|
+
const sessionLock = readLock(targetApp);
|
|
256
|
+
if (sessionLock && sessionLock.mode === "session") {
|
|
257
|
+
if (isLockPending(sessionLock)) {
|
|
258
|
+
logDebug(`[Weavetab] Launch in progress for ${targetApp} by PID ${sessionLock.pid}. Waiting up to 5s...`);
|
|
259
|
+
for (let i = 0; i < 5; i++) {
|
|
260
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
261
|
+
const updatedLock = readLock(targetApp);
|
|
262
|
+
if (!updatedLock || !isLockPending(updatedLock))
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const freshLock = readLock(targetApp);
|
|
267
|
+
if (freshLock && (await isLockValid(freshLock))) {
|
|
268
|
+
port = freshLock.port;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
clearLock(targetApp);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
let targets = [];
|
|
275
|
+
if (port) {
|
|
276
|
+
try {
|
|
277
|
+
targets = await withCDPTimeout(CDP.List({ host: "127.0.0.1", port }), 3000, "CDP.List existing Electron");
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
port = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!port || targets.length === 0) {
|
|
284
|
+
logDebug(`[Weavetab CDP] Launching Electron app ${targetApp}...`);
|
|
285
|
+
const launchResult = await launchElectronApp(targetApp, config);
|
|
286
|
+
port = launchResult.port;
|
|
287
|
+
// Poll for target window availability (Electron may take up to 2-3s to render initial window)
|
|
288
|
+
for (let retry = 0; retry < 10; retry++) {
|
|
289
|
+
try {
|
|
290
|
+
targets = await withCDPTimeout(CDP.List({ host: "127.0.0.1", port }), 2000, "CDP.List Electron retry");
|
|
291
|
+
if (targets.length > 0)
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
catch { /* wait */ }
|
|
295
|
+
await new Promise(r => setTimeout(r, 500));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
let pageTarget = targets.find(t => t.type === "page" || t.type === "webview" || t.type === "app") ?? targets[0];
|
|
299
|
+
if (!pageTarget) {
|
|
300
|
+
throw new Error(`[Weavetab] ✗ No window or webview target found in Electron app "${targetApp}".`);
|
|
301
|
+
}
|
|
302
|
+
const targetId = pageTarget.id || pageTarget.targetId;
|
|
303
|
+
const session = await withCDPTimeout(CDP({ host: "127.0.0.1", port, target: targetId }), 5000, "CDP connect Electron target");
|
|
304
|
+
session.on("close", () => {
|
|
305
|
+
logDebug(`[Weavetab CDP] Electron session WebSocket closed (${targetApp}) — unregistering.`);
|
|
306
|
+
const iv = analyticsIntervals.get(targetId);
|
|
307
|
+
if (iv) {
|
|
308
|
+
clearInterval(iv);
|
|
309
|
+
analyticsIntervals.delete(targetId);
|
|
310
|
+
}
|
|
311
|
+
clearProfileVerificationCache(targetId);
|
|
312
|
+
import("./omni_broker.js").then(m => m.cleanupOmniBroker(targetId)).catch(() => { });
|
|
313
|
+
import("../tools/network_intercept.js").then(m => m.cleanupNetworkIntercept(session)).catch(() => { });
|
|
314
|
+
sessionRegistry.unregister(targetId);
|
|
315
|
+
});
|
|
316
|
+
session.on("error", (err) => {
|
|
317
|
+
logDebug(`[Weavetab CDP] Electron session WebSocket error (${targetApp}):`, err?.message ?? err);
|
|
318
|
+
});
|
|
319
|
+
// Validate connection health
|
|
320
|
+
try {
|
|
321
|
+
await session.Browser.getVersion();
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
throw new Error("Failed health check: CDP session connected to Electron app but getVersion failed.");
|
|
325
|
+
}
|
|
326
|
+
await Promise.all([
|
|
327
|
+
session.Accessibility.enable(),
|
|
328
|
+
session.DOM.enable(),
|
|
329
|
+
session.Page.enable(),
|
|
330
|
+
session.Runtime.enable(),
|
|
331
|
+
session.Network.enable().catch(() => { }),
|
|
332
|
+
session.Performance.enable().catch(() => { })
|
|
333
|
+
]);
|
|
334
|
+
await wireUiConfigBinding(session);
|
|
335
|
+
await initSession(session, targetId, port, config, true);
|
|
336
|
+
await session.Target.setDiscoverTargets({ discover: true }).catch(() => { });
|
|
337
|
+
logDebug(`[Weavetab CDP] [OK] Active Electron target: ${pageTarget.title || pageTarget.url || targetApp}`);
|
|
338
|
+
const client = {
|
|
339
|
+
session,
|
|
340
|
+
targetId,
|
|
341
|
+
url: pageTarget.url || "",
|
|
342
|
+
title: pageTarget.title || targetApp,
|
|
343
|
+
overlayInstalled: false,
|
|
344
|
+
agentId: getConfigAgentId(config),
|
|
345
|
+
appName: targetApp,
|
|
346
|
+
isElectron: true,
|
|
347
|
+
};
|
|
348
|
+
sessionRegistry.register(targetId, client);
|
|
349
|
+
return client;
|
|
350
|
+
}
|
|
351
|
+
// ──────────────── STANDARD WEB BROWSER FLOW ────────────────
|
|
256
352
|
const sessionLock = readLock();
|
|
257
353
|
if (sessionLock && sessionLock.mode === "session") {
|
|
258
354
|
if (isLockPending(sessionLock)) {
|
|
@@ -413,7 +509,7 @@ export async function getOrConnect(config) {
|
|
|
413
509
|
// Continue anyway for now - in production this should be a hard failure
|
|
414
510
|
}
|
|
415
511
|
else {
|
|
416
|
-
logDebug(`[Weavetab Security] Browser fingerprint verified
|
|
512
|
+
logDebug(`[Weavetab Security] Browser fingerprint verified`);
|
|
417
513
|
}
|
|
418
514
|
}
|
|
419
515
|
// NOTE: initSession() already installs the frameNavigated handler (overlay
|
|
@@ -429,7 +525,7 @@ export async function getOrConnect(config) {
|
|
|
429
525
|
// Continue anyway for now - in production this should be a hard failure
|
|
430
526
|
}
|
|
431
527
|
else {
|
|
432
|
-
logDebug(`[Weavetab Security] Profile isolation
|
|
528
|
+
logDebug(`[Weavetab Security] Profile isolation`);
|
|
433
529
|
}
|
|
434
530
|
await session.Target.setDiscoverTargets({ discover: true });
|
|
435
531
|
session.on("Target.targetActivated", (event) => {
|
|
@@ -437,7 +533,7 @@ export async function getOrConnect(config) {
|
|
|
437
533
|
if (activatedId) {
|
|
438
534
|
const client = sessionRegistry.getAll().find(c => c.targetId === activatedId);
|
|
439
535
|
if (client) {
|
|
440
|
-
sessionRegistry.setActive(activatedId, client.agentId);
|
|
536
|
+
sessionRegistry.setActive(activatedId, client.agentId, client.appName);
|
|
441
537
|
}
|
|
442
538
|
}
|
|
443
539
|
});
|
|
@@ -491,28 +587,34 @@ export async function getOrConnect(config) {
|
|
|
491
587
|
await releaseMutex();
|
|
492
588
|
}
|
|
493
589
|
}
|
|
494
|
-
export async function getTabList(agentId) {
|
|
590
|
+
export async function getTabList(agentId, targetApp) {
|
|
495
591
|
const config = getCachedConfig();
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
592
|
+
let port = null;
|
|
593
|
+
if (targetApp) {
|
|
594
|
+
const lock = readLock(targetApp);
|
|
595
|
+
if (lock && (await isLockValid(lock))) {
|
|
596
|
+
port = lock.port;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
port = actualPort;
|
|
601
|
+
if (!port) {
|
|
602
|
+
const lock = readLock();
|
|
603
|
+
if (lock && lock.mode === "session") {
|
|
604
|
+
const valid = await isLockValid(lock);
|
|
605
|
+
if (valid) {
|
|
606
|
+
port = lock.port;
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
logDebug("[Weavetab CDP] getTabList: stale lock detected — clearing.");
|
|
610
|
+
clearLock();
|
|
611
|
+
port = null;
|
|
612
|
+
}
|
|
511
613
|
}
|
|
512
614
|
}
|
|
513
615
|
}
|
|
514
616
|
if (!port)
|
|
515
|
-
throw new Error(
|
|
617
|
+
throw new Error(`[!] No active ${targetApp ? targetApp : "browser"} session. Run browser_go or launch app first.`);
|
|
516
618
|
try {
|
|
517
619
|
const metaSession = await withCDPTimeout(CDP({ host: "127.0.0.1", port }), 5000, "getTabList CDP");
|
|
518
620
|
const { targetInfos } = await withCDPTimeout(metaSession.Target.getTargets(), 5000, "getTabList getTargets");
|
|
@@ -520,21 +622,21 @@ export async function getTabList(agentId) {
|
|
|
520
622
|
const aid = agentId || getConfigAgentId(config);
|
|
521
623
|
const bctxId = sessionRegistry.getBrowserContextIdForAgent(aid);
|
|
522
624
|
return targetInfos
|
|
523
|
-
.filter(t => t.type === "page" && (!bctxId || t.browserContextId === bctxId))
|
|
625
|
+
.filter(t => (t.type === "page" || t.type === "webview" || t.type === "app" || t.type === "other" || t.type === "iframe") && (!bctxId || t.browserContextId === bctxId))
|
|
524
626
|
.map(t => ({
|
|
525
627
|
id: t.targetId,
|
|
526
|
-
title: t.title,
|
|
628
|
+
title: t.title || t.url || t.type,
|
|
527
629
|
url: t.url,
|
|
528
630
|
type: t.type,
|
|
529
631
|
}));
|
|
530
632
|
}
|
|
531
633
|
catch {
|
|
532
|
-
throw new Error(`[!] Weavetab cannot reach browser on port ${port}.`);
|
|
634
|
+
throw new Error(`[!] Weavetab cannot reach ${targetApp ? targetApp : "browser"} on port ${port}.`);
|
|
533
635
|
}
|
|
534
636
|
}
|
|
535
637
|
// Only allow the UI to update cosmetic/dev settings via the binding.
|
|
536
638
|
// Never allow overwriting security-critical fields (role, browserPath, block, allow, profileDir, etc.).
|
|
537
|
-
const UI_ALLOWED_FIELDS = new Set(["devMode", "
|
|
639
|
+
const UI_ALLOWED_FIELDS = new Set(["devMode", "extension"]);
|
|
538
640
|
async function wireUiConfigBinding(session) {
|
|
539
641
|
try {
|
|
540
642
|
await session.Runtime.addBinding({ name: "__wt_save_ui_config" });
|
|
@@ -562,21 +664,38 @@ async function wireUiConfigBinding(session) {
|
|
|
562
664
|
}
|
|
563
665
|
catch { /* already bound */ }
|
|
564
666
|
}
|
|
565
|
-
export async function connectToTab(targetId, port, agentId, browserContextId) {
|
|
667
|
+
export async function connectToTab(targetId, port, agentId, browserContextId, appName) {
|
|
566
668
|
const existing = sessionRegistry.getAll().find(c => c.targetId === targetId);
|
|
567
669
|
if (existing)
|
|
568
670
|
return existing;
|
|
569
|
-
const lock = readLock();
|
|
570
|
-
const p = port || actualPort || lock?.port;
|
|
671
|
+
const lock = readLock(appName);
|
|
672
|
+
const p = port || (appName ? lock?.port : (actualPort || lock?.port));
|
|
571
673
|
if (!p)
|
|
572
|
-
throw new Error(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
674
|
+
throw new Error(`[!] No active ${appName || "browser"} session. Run browser_go first.`);
|
|
675
|
+
let t = null;
|
|
676
|
+
try {
|
|
677
|
+
const targets = await withCDPTimeout(CDP.List({ host: "127.0.0.1", port: p }), 3000, "connectToTab CDP.List");
|
|
678
|
+
t = targets.find((item) => (item.id || item.targetId) === targetId);
|
|
679
|
+
}
|
|
680
|
+
catch { }
|
|
681
|
+
if (!t) {
|
|
682
|
+
try {
|
|
683
|
+
const metaSession = await withCDPTimeout(CDP({ host: "127.0.0.1", port: p }), 3000, "connectToTab meta CDP");
|
|
684
|
+
const { targetInfos } = await withCDPTimeout(metaSession.Target.getTargets(), 3000, "connectToTab meta getTargets");
|
|
685
|
+
await metaSession.close();
|
|
686
|
+
const ti = targetInfos.find(item => item.targetId === targetId);
|
|
687
|
+
if (ti) {
|
|
688
|
+
t = { id: ti.targetId, targetId: ti.targetId, url: ti.url, title: ti.title, type: ti.type };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch { }
|
|
692
|
+
}
|
|
693
|
+
if (!t) {
|
|
694
|
+
t = { id: targetId, targetId, url: "", title: appName || "Tab", type: "page" };
|
|
695
|
+
}
|
|
577
696
|
const session = await withCDPTimeout(CDP({ host: "127.0.0.1", port: p, target: targetId }), 5000, "connectToTab CDP");
|
|
578
697
|
session.on("close", () => {
|
|
579
|
-
logDebug(
|
|
698
|
+
logDebug(`[Weavetab CDP] connectToTab session closed (${appName || "browser"}) — unregistering.`);
|
|
580
699
|
// Cancel this tab's analytics interval so it doesn't outlive the session
|
|
581
700
|
const iv = analyticsIntervals.get(targetId);
|
|
582
701
|
if (iv) {
|
|
@@ -589,7 +708,7 @@ export async function connectToTab(targetId, port, agentId, browserContextId) {
|
|
|
589
708
|
sessionRegistry.unregister(targetId);
|
|
590
709
|
});
|
|
591
710
|
session.on("error", (err) => {
|
|
592
|
-
logDebug(
|
|
711
|
+
logDebug(`[Weavetab CDP] connectToTab session error (${appName || "browser"}):`, err?.message ?? err);
|
|
593
712
|
});
|
|
594
713
|
await Promise.all([
|
|
595
714
|
session.Accessibility.enable(),
|
|
@@ -601,7 +720,7 @@ export async function connectToTab(targetId, port, agentId, browserContextId) {
|
|
|
601
720
|
]);
|
|
602
721
|
await wireUiConfigBinding(session);
|
|
603
722
|
const currentConfig = getCachedConfig();
|
|
604
|
-
await initSession(session, targetId, p, currentConfig);
|
|
723
|
+
await initSession(session, targetId, p, currentConfig, !!appName);
|
|
605
724
|
// NOTE: initSession() already installs a frameNavigated handler that handles
|
|
606
725
|
// clearSnapshot, clearViewportCache, and ensureVisualLayer. Do NOT add a second
|
|
607
726
|
// one here — it would double the CDP work on every navigation.
|
|
@@ -614,6 +733,8 @@ export async function connectToTab(targetId, port, agentId, browserContextId) {
|
|
|
614
733
|
overlayInstalled: false,
|
|
615
734
|
agentId: actualAgentId,
|
|
616
735
|
browserContextId,
|
|
736
|
+
appName,
|
|
737
|
+
isElectron: !!appName,
|
|
617
738
|
};
|
|
618
739
|
sessionRegistry.register(targetId, client);
|
|
619
740
|
return client;
|
package/dist/cdp/helpers.d.ts
CHANGED
|
@@ -9,3 +9,7 @@ export declare function sendToExtension(session: CDP.Client, message: object): P
|
|
|
9
9
|
export declare function getFromExtension<T = any>(session: CDP.Client, message: object, retries?: number): Promise<T | null>;
|
|
10
10
|
export declare function hideWeaveTabOverlay(session: CDP.Client): Promise<void>;
|
|
11
11
|
export declare function restoreWeaveTabOverlay(session: CDP.Client): Promise<void>;
|
|
12
|
+
export declare function applySecretHardening(session: CDP.Client, objectId?: string, selector?: string): Promise<void>;
|
|
13
|
+
export declare function registerAndHardenSecretField(session: CDP.Client, targetId: string, selector: string, secretValue?: string, objectId?: string): Promise<void>;
|
|
14
|
+
export declare function maskSecretFieldsForCapture(session: CDP.Client): Promise<void>;
|
|
15
|
+
export declare function restoreSecretFieldsAfterCapture(session: CDP.Client): Promise<void>;
|
package/dist/cdp/helpers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logDebug } from "../audit/logger.js";
|
|
2
|
+
import { SessionSecretTracker } from "../security/secrets.js";
|
|
2
3
|
const MAX_QUEUE_SIZE = 50;
|
|
3
4
|
const BATCH_INTERVAL_MS = 10; // Reduced from 20ms for faster message delivery
|
|
4
5
|
const messageQueues = new Map();
|
|
@@ -191,3 +192,183 @@ export async function restoreWeaveTabOverlay(session) {
|
|
|
191
192
|
returnByValue: false,
|
|
192
193
|
}).catch(() => { });
|
|
193
194
|
}
|
|
195
|
+
// ─── Secret Hardening & Containment Helpers ──────────────────────────────────
|
|
196
|
+
export async function applySecretHardening(session, objectId, selector) {
|
|
197
|
+
const hardeningScript = `function() {
|
|
198
|
+
try {
|
|
199
|
+
const el = this;
|
|
200
|
+
if (!el) return;
|
|
201
|
+
// Mark as secret
|
|
202
|
+
try { el.setAttribute('data-wt-secret', 'true'); } catch (_) {}
|
|
203
|
+
|
|
204
|
+
// 1. Redact direct .value access via instance-level property override
|
|
205
|
+
const proto = Object.getPrototypeOf(el);
|
|
206
|
+
const origDesc = proto ? Object.getOwnPropertyDescriptor(proto, 'value') : null;
|
|
207
|
+
if (origDesc) {
|
|
208
|
+
Object.defineProperty(el, 'value', {
|
|
209
|
+
get: function() { return '[REDACTED]'; },
|
|
210
|
+
set: origDesc.set,
|
|
211
|
+
configurable: true,
|
|
212
|
+
enumerable: true
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 2. Defeat array spread, iteration, and coercion
|
|
217
|
+
try {
|
|
218
|
+
Object.defineProperty(el, Symbol.iterator, {
|
|
219
|
+
value: function* () { yield '[REDACTED]'; },
|
|
220
|
+
configurable: true
|
|
221
|
+
});
|
|
222
|
+
Object.defineProperty(el, Symbol.toPrimitive, {
|
|
223
|
+
value: function() { return '[REDACTED]'; },
|
|
224
|
+
configurable: true
|
|
225
|
+
});
|
|
226
|
+
// Defeat JSON.stringify / structuredClone leaks
|
|
227
|
+
Object.defineProperty(el, 'toJSON', {
|
|
228
|
+
value: function() { return '[REDACTED]'; },
|
|
229
|
+
configurable: true
|
|
230
|
+
});
|
|
231
|
+
} catch(_) {}
|
|
232
|
+
} catch (e) {
|
|
233
|
+
// fail silently so as not to disrupt the page execution
|
|
234
|
+
}
|
|
235
|
+
}`;
|
|
236
|
+
if (objectId) {
|
|
237
|
+
await session.Runtime.callFunctionOn({
|
|
238
|
+
functionDeclaration: hardeningScript,
|
|
239
|
+
objectId,
|
|
240
|
+
silent: true
|
|
241
|
+
}).catch(() => { });
|
|
242
|
+
}
|
|
243
|
+
else if (selector) {
|
|
244
|
+
await session.Runtime.evaluate({
|
|
245
|
+
expression: `(function(sel) {
|
|
246
|
+
const el = document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
247
|
+
if (el) {
|
|
248
|
+
(${hardeningScript}).call(el);
|
|
249
|
+
}
|
|
250
|
+
})(${JSON.stringify(selector)})`,
|
|
251
|
+
returnByValue: false
|
|
252
|
+
}).catch(() => { });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export async function registerAndHardenSecretField(session, targetId, selector, secretValue, objectId) {
|
|
256
|
+
try {
|
|
257
|
+
if (secretValue) {
|
|
258
|
+
SessionSecretTracker.registerSecretValue(targetId, secretValue);
|
|
259
|
+
}
|
|
260
|
+
let resolvedObjectId = objectId;
|
|
261
|
+
if (!resolvedObjectId && selector) {
|
|
262
|
+
const nodeInfo = await session.Runtime.evaluate({
|
|
263
|
+
expression: `(function(sel) {
|
|
264
|
+
return document.querySelector(sel) || (window.__Weavetab_shadowQuery ? window.__Weavetab_shadowQuery(sel) : null);
|
|
265
|
+
})(${JSON.stringify(selector)})`,
|
|
266
|
+
returnByValue: false
|
|
267
|
+
}).catch(() => null);
|
|
268
|
+
resolvedObjectId = nodeInfo?.result?.objectId;
|
|
269
|
+
}
|
|
270
|
+
if (resolvedObjectId) {
|
|
271
|
+
try {
|
|
272
|
+
const nodeDetails = await session.DOM.requestNode({ objectId: resolvedObjectId });
|
|
273
|
+
if (nodeDetails?.nodeId) {
|
|
274
|
+
const desc = await session.DOM.describeNode({ nodeId: nodeDetails.nodeId });
|
|
275
|
+
if (desc?.node?.backendNodeId) {
|
|
276
|
+
SessionSecretTracker.markNodeAsSecret(targetId, desc.node.backendNodeId);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch { /* DOM lookup best-effort */ }
|
|
281
|
+
}
|
|
282
|
+
await applySecretHardening(session, resolvedObjectId, selector);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
logDebug(`[Weavetab] registerAndHardenSecretField failed: ${err}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
export async function maskSecretFieldsForCapture(session) {
|
|
289
|
+
await session.Runtime.evaluate({
|
|
290
|
+
expression: `(function() {
|
|
291
|
+
var secretRegex = /password|passcode|pin|passphrase|secret|token|auth.?key|access.?code|mot.?de.?passe|contrase[nñ]a|kennwort|senha/i;
|
|
292
|
+
|
|
293
|
+
function queryAllDeep(root, matchFn) {
|
|
294
|
+
var matched = [];
|
|
295
|
+
var elements = root.querySelectorAll('*');
|
|
296
|
+
for (var i = 0; i < elements.length; i++) {
|
|
297
|
+
var el = elements[i];
|
|
298
|
+
if (matchFn(el)) matched.push(el);
|
|
299
|
+
if (el.shadowRoot) {
|
|
300
|
+
matched = matched.concat(queryAllDeep(el.shadowRoot, matchFn));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return matched;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function isSecretNode(el) {
|
|
307
|
+
if (!el || !el.tagName) return false;
|
|
308
|
+
if (el.hasAttribute && el.hasAttribute('data-wt-secret')) return true;
|
|
309
|
+
var tag = el.tagName.toLowerCase();
|
|
310
|
+
if (tag === 'input' || tag === 'textarea') {
|
|
311
|
+
var t = (el.getAttribute('type') || '').toLowerCase();
|
|
312
|
+
var ac = (el.getAttribute('autocomplete') || '').toLowerCase();
|
|
313
|
+
var name = (el.getAttribute('name') || '').toLowerCase();
|
|
314
|
+
var id = (el.getAttribute('id') || '').toLowerCase();
|
|
315
|
+
var aria = (el.getAttribute('aria-label') || '').toLowerCase();
|
|
316
|
+
return t === 'password' || ac.includes('password') || secretRegex.test(name) || secretRegex.test(id) || secretRegex.test(aria);
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
var els = queryAllDeep(document, isSecretNode);
|
|
322
|
+
for (var i = 0; i < els.length; i++) {
|
|
323
|
+
var el = els[i];
|
|
324
|
+
el.__wt_cap_val_backup = el.value;
|
|
325
|
+
try { el.value = ''; } catch(e) {}
|
|
326
|
+
if (el.isContentEditable) {
|
|
327
|
+
el.__wt_cap_txt_backup = el.textContent;
|
|
328
|
+
try { el.textContent = ''; } catch(e) {}
|
|
329
|
+
}
|
|
330
|
+
el.__wt_cap_sec_backup = el.style.webkitTextSecurity;
|
|
331
|
+
try { el.style.webkitTextSecurity = 'disc'; } catch(e) {}
|
|
332
|
+
}
|
|
333
|
+
})()`,
|
|
334
|
+
returnByValue: false,
|
|
335
|
+
}).catch(() => { });
|
|
336
|
+
}
|
|
337
|
+
export async function restoreSecretFieldsAfterCapture(session) {
|
|
338
|
+
await session.Runtime.evaluate({
|
|
339
|
+
expression: `(function() {
|
|
340
|
+
function queryAllDeep(root) {
|
|
341
|
+
var matched = [];
|
|
342
|
+
var elements = root.querySelectorAll('*');
|
|
343
|
+
for (var i = 0; i < elements.length; i++) {
|
|
344
|
+
var el = elements[i];
|
|
345
|
+
if (el.__wt_cap_val_backup !== undefined || el.__wt_cap_txt_backup !== undefined || el.__wt_cap_sec_backup !== undefined) {
|
|
346
|
+
matched.push(el);
|
|
347
|
+
}
|
|
348
|
+
if (el.shadowRoot) {
|
|
349
|
+
matched = matched.concat(queryAllDeep(el.shadowRoot));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return matched;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
var els = queryAllDeep(document);
|
|
356
|
+
for (var i = 0; i < els.length; i++) {
|
|
357
|
+
var el = els[i];
|
|
358
|
+
if (el.__wt_cap_val_backup !== undefined) {
|
|
359
|
+
try { el.value = el.__wt_cap_val_backup; } catch(e) {}
|
|
360
|
+
delete el.__wt_cap_val_backup;
|
|
361
|
+
}
|
|
362
|
+
if (el.__wt_cap_txt_backup !== undefined) {
|
|
363
|
+
try { el.textContent = el.__wt_cap_txt_backup; } catch(e) {}
|
|
364
|
+
delete el.__wt_cap_txt_backup;
|
|
365
|
+
}
|
|
366
|
+
if (el.__wt_cap_sec_backup !== undefined) {
|
|
367
|
+
try { el.style.webkitTextSecurity = el.__wt_cap_sec_backup; } catch(e) {}
|
|
368
|
+
delete el.__wt_cap_sec_backup;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
})()`,
|
|
372
|
+
returnByValue: false,
|
|
373
|
+
}).catch(() => { });
|
|
374
|
+
}
|
package/dist/cdp/lock.d.ts
CHANGED
|
@@ -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
|
|
16
|
-
export declare function
|
|
17
|
-
|
|
18
|
-
export declare function
|
|
19
|
-
export declare function
|
|
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.
|