gm-plugkit 2.0.1686 → 2.0.1688
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/package.json +1 -1
- package/plugkit-wasm-wrapper.js +120 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1688",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/plugkit-wasm-wrapper.js
CHANGED
|
@@ -1164,26 +1164,41 @@ function fetchJsonSync(url, timeoutMs) {
|
|
|
1164
1164
|
try { return JSON.parse(r.stdout); } catch (_) { return null; }
|
|
1165
1165
|
}
|
|
1166
1166
|
|
|
1167
|
-
function
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1167
|
+
function closeExtraBlankTabs(port, keepWsEndpoint) {
|
|
1168
|
+
try {
|
|
1169
|
+
const targets = fetchJsonSync(`http://127.0.0.1:${port}/json/list`, 1500);
|
|
1170
|
+
if (!Array.isArray(targets)) return { closed: 0 };
|
|
1171
|
+
const pages = targets.filter(t => t && t.type === 'page');
|
|
1172
|
+
const blank = pages.filter(t => t.url === 'about:blank' || t.url === '');
|
|
1173
|
+
const nonBlankCount = pages.length - blank.length;
|
|
1174
|
+
const keepCount = nonBlankCount > 0 ? 0 : 1;
|
|
1175
|
+
const toClose = blank.slice(0, Math.max(0, blank.length - keepCount));
|
|
1176
|
+
let closed = 0;
|
|
1177
|
+
for (const t of toClose) {
|
|
1178
|
+
if (t.webSocketDebuggerUrl === keepWsEndpoint) continue;
|
|
1179
|
+
const r = spawnSync(process.execPath, ['-e', `
|
|
1180
|
+
const http = require('http');
|
|
1181
|
+
const req = http.get(${JSON.stringify(`http://127.0.0.1:${port}/json/close/${t.id}`)}, res => { res.resume(); res.on('end', () => process.exit(0)); });
|
|
1182
|
+
req.on('error', () => process.exit(1));
|
|
1183
|
+
req.setTimeout(1500, () => { req.destroy(); process.exit(1); });
|
|
1184
|
+
`], { timeout: 3000, windowsHide: true });
|
|
1185
|
+
if (r.status === 0) closed++;
|
|
1186
|
+
}
|
|
1187
|
+
return { closed };
|
|
1188
|
+
} catch (_) {
|
|
1189
|
+
return { closed: 0 };
|
|
1180
1190
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function chromeLogHasSandboxDenied(chromeLogPath) {
|
|
1194
|
+
try {
|
|
1195
|
+
return /Sandbox cannot access executable/.test(fs.readFileSync(chromeLogPath, 'utf-8'));
|
|
1196
|
+
} catch (_) {
|
|
1197
|
+
return false;
|
|
1185
1198
|
}
|
|
1186
|
-
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function spawnChromiumOnce(browserBin, profileDir, port, headless, noSandbox) {
|
|
1187
1202
|
const args = [
|
|
1188
1203
|
'--user-data-dir=' + profileDir,
|
|
1189
1204
|
'--remote-debugging-port=' + port,
|
|
@@ -1193,7 +1208,7 @@ function startManagedBrowser(pw, profileDir) {
|
|
|
1193
1208
|
'--disable-default-apps',
|
|
1194
1209
|
'--disable-gpu-process-crash-limit',
|
|
1195
1210
|
];
|
|
1196
|
-
if (
|
|
1211
|
+
if (noSandbox) {
|
|
1197
1212
|
args.push('--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage');
|
|
1198
1213
|
}
|
|
1199
1214
|
if (headless) {
|
|
@@ -1211,27 +1226,70 @@ function startManagedBrowser(pw, profileDir) {
|
|
|
1211
1226
|
env: process.env,
|
|
1212
1227
|
});
|
|
1213
1228
|
try { if (typeof logFd === 'number') fs.closeSync(logFd); } catch (_) {}
|
|
1214
|
-
const pid = child.pid;
|
|
1215
1229
|
child.unref();
|
|
1216
|
-
|
|
1230
|
+
return { pid: child.pid, chromeLogPath };
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function waitForCdpReady(port, deadlineMs) {
|
|
1217
1234
|
const start = Date.now();
|
|
1218
|
-
const deadline = start +
|
|
1219
|
-
let wsEndpoint = null;
|
|
1220
|
-
let lastErr = null;
|
|
1235
|
+
const deadline = start + deadlineMs;
|
|
1221
1236
|
while (Date.now() < deadline) {
|
|
1222
1237
|
const info = fetchJsonSync(`http://127.0.0.1:${port}/json/version`, 1500);
|
|
1223
|
-
if (info && info.webSocketDebuggerUrl) {
|
|
1224
|
-
wsEndpoint = info.webSocketDebuggerUrl;
|
|
1225
|
-
break;
|
|
1226
|
-
}
|
|
1238
|
+
if (info && info.webSocketDebuggerUrl) return { wsEndpoint: info.webSocketDebuggerUrl, ms: Date.now() - start };
|
|
1227
1239
|
sleepSync(500);
|
|
1228
1240
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function startManagedBrowser(pw, profileDir) {
|
|
1245
|
+
const headless = process.env.GM_BROWSER_HEADLESS === '1';
|
|
1246
|
+
logEvent('plugkit', 'browser.headless-mode-resolved', { headless, source: headless ? 'GM_BROWSER_HEADLESS=1' : 'default-headful' });
|
|
1247
|
+
let browserBin = findInstalledChromiumBinary();
|
|
1248
|
+
if (!browserBin) {
|
|
1249
|
+
logEvent('plugkit', 'browser.chromium-installing', {});
|
|
1250
|
+
spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['--yes', 'playwright', 'install', 'chromium'], {
|
|
1251
|
+
encoding: 'utf-8',
|
|
1252
|
+
timeout: 300000,
|
|
1253
|
+
windowsHide: true,
|
|
1254
|
+
shell: process.platform === 'win32',
|
|
1255
|
+
stdio: 'ignore',
|
|
1256
|
+
});
|
|
1257
|
+
browserBin = findInstalledChromiumBinary();
|
|
1258
|
+
}
|
|
1259
|
+
if (!browserBin) {
|
|
1260
|
+
const err = new Error('chromium binary not found after install attempt');
|
|
1261
|
+
logEvent('plugkit', 'browser.launch-failed', { reason: 'chromium-missing' });
|
|
1262
|
+
throw err;
|
|
1263
|
+
}
|
|
1264
|
+
const port = findFreePortSync();
|
|
1265
|
+
let noSandbox = process.env.GM_BROWSER_NO_SANDBOX === '1';
|
|
1266
|
+
let { pid, chromeLogPath } = spawnChromiumOnce(browserBin, profileDir, port, headless, noSandbox);
|
|
1267
|
+
logEvent('plugkit', 'browser.chromium-launched', { pid, port, profileDir, headless, noSandbox, binary: browserBin, chromeLogPath });
|
|
1268
|
+
let ready = waitForCdpReady(port, 30000);
|
|
1269
|
+
if (!ready) {
|
|
1270
|
+
logEvent('plugkit', 'browser.launch-failed', { reason: 'cdp-not-ready', pid, port });
|
|
1271
|
+
throw new Error(`chromium launched (pid=${pid}) but CDP at 127.0.0.1:${port} did not become ready within 30s`);
|
|
1272
|
+
}
|
|
1273
|
+
if (!noSandbox && chromeLogHasSandboxDenied(chromeLogPath)) {
|
|
1274
|
+
logEvent('plugkit', 'browser.sandbox-fallback-engaged', { pid, port, profileDir, reason: 'sandbox-access-denied-detected-in-chrome-launch-log' });
|
|
1275
|
+
try { process.kill(pid, 'SIGTERM'); } catch (_) {}
|
|
1276
|
+
sleepSyncMs(500);
|
|
1277
|
+
try { killPidQuiet(pid); } catch (_) {}
|
|
1278
|
+
purgeProfileLockFiles(profileDir);
|
|
1279
|
+
noSandbox = true;
|
|
1280
|
+
const port2 = findFreePortSync();
|
|
1281
|
+
({ pid, chromeLogPath } = spawnChromiumOnce(browserBin, profileDir, port2, headless, noSandbox));
|
|
1282
|
+
logEvent('plugkit', 'browser.chromium-launched', { pid, port: port2, profileDir, headless, noSandbox, binary: browserBin, chromeLogPath, retry: true });
|
|
1283
|
+
ready = waitForCdpReady(port2, 30000);
|
|
1284
|
+
if (!ready) {
|
|
1285
|
+
logEvent('plugkit', 'browser.launch-failed', { reason: 'cdp-not-ready-after-sandbox-fallback', pid, port: port2 });
|
|
1286
|
+
throw new Error(`chromium sandbox-fallback relaunch (pid=${pid}) but CDP at 127.0.0.1:${port2} did not become ready within 30s`);
|
|
1287
|
+
}
|
|
1288
|
+
logEvent('plugkit', 'browser.cdp-ready', { pid, port: port2, ms: ready.ms, wsEndpoint: ready.wsEndpoint, noSandbox: true });
|
|
1289
|
+
return { pid, port: port2, wsEndpoint: ready.wsEndpoint };
|
|
1232
1290
|
}
|
|
1233
|
-
logEvent('plugkit', 'browser.cdp-ready', { pid, port, ms:
|
|
1234
|
-
return { pid, port, wsEndpoint };
|
|
1291
|
+
logEvent('plugkit', 'browser.cdp-ready', { pid, port, ms: ready.ms, wsEndpoint: ready.wsEndpoint });
|
|
1292
|
+
return { pid, port, wsEndpoint: ready.wsEndpoint };
|
|
1235
1293
|
}
|
|
1236
1294
|
|
|
1237
1295
|
function killPidQuiet(pid) {
|
|
@@ -1283,6 +1341,18 @@ function gracefulCloseBrowser(entry, reason) {
|
|
|
1283
1341
|
try { logEvent('plugkit', 'browser.closed', { reason: reason || 'closed', pid, port, profileDir }); } catch (_) {}
|
|
1284
1342
|
}
|
|
1285
1343
|
|
|
1344
|
+
function checkSessionNavigatedAway(port, claudeSessionId) {
|
|
1345
|
+
try {
|
|
1346
|
+
const list = fetchJsonSync(`http://127.0.0.1:${port}/json/list`, 1000);
|
|
1347
|
+
if (!Array.isArray(list)) return;
|
|
1348
|
+
const pages = list.filter(t => t && t.type === 'page');
|
|
1349
|
+
const stray = pages.filter(t => /^(chrome:\/\/new-tab-page|about:blank|chrome:\/\/newtab)/i.test(String(t.url || '')));
|
|
1350
|
+
if (pages.length > 0 && stray.length === pages.length) {
|
|
1351
|
+
logEvent('plugkit', 'browser.session-navigated-away', { sid: claudeSessionId, port, urls: pages.map(p => p.url) });
|
|
1352
|
+
}
|
|
1353
|
+
} catch (_) {}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1286
1356
|
function resolveExistingBrowserEntry(cwd, claudeSessionId, pw, portsFile, sessionsFile, ports, sessions) {
|
|
1287
1357
|
const existing = ports[claudeSessionId];
|
|
1288
1358
|
if (!(existing && existing.pid && existing.wsEndpoint)) return null;
|
|
@@ -1292,7 +1362,10 @@ function resolveExistingBrowserEntry(cwd, claudeSessionId, pw, portsFile, sessio
|
|
|
1292
1362
|
const cdpOk = pidOk && !!fetchJsonSync(`http://127.0.0.1:${existing.port}/json/version`, 1000);
|
|
1293
1363
|
if (pidOk && profileOk && cdpOk) {
|
|
1294
1364
|
const pwIds = sessions[claudeSessionId] || [];
|
|
1295
|
-
if (pwIds.length > 0 && existing.pwSessionId)
|
|
1365
|
+
if (pwIds.length > 0 && existing.pwSessionId) {
|
|
1366
|
+
checkSessionNavigatedAway(existing.port, claudeSessionId);
|
|
1367
|
+
return existing.pwSessionId;
|
|
1368
|
+
}
|
|
1296
1369
|
const r = runBrowserRunner(pw, ['session', 'new', '--direct', existing.wsEndpoint], 30000, cwd, claudeSessionId);
|
|
1297
1370
|
if (r && r.status === 0) {
|
|
1298
1371
|
const sid = parseSessionId(r.stdout || '');
|
|
@@ -1362,7 +1435,11 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
1362
1435
|
&& fetchJsonSync(`http://127.0.0.1:${winner.port}/json/version`, 1000)) {
|
|
1363
1436
|
const a = runBrowserRunner(pw, ['session', 'new', '--direct', winner.wsEndpoint], 30000, cwd, claudeSessionId);
|
|
1364
1437
|
const sid = a && a.status === 0 ? parseSessionId(a.stdout || '') : null;
|
|
1365
|
-
if (sid) {
|
|
1438
|
+
if (sid) {
|
|
1439
|
+
checkSessionNavigatedAway(winner.port, claudeSessionId);
|
|
1440
|
+
logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true, via: 'spawn-lock-wait' });
|
|
1441
|
+
return sid;
|
|
1442
|
+
}
|
|
1366
1443
|
}
|
|
1367
1444
|
if (Date.now() > spawnDeadline) break;
|
|
1368
1445
|
sleepSyncMs(300);
|
|
@@ -1391,13 +1468,15 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
1391
1468
|
}
|
|
1392
1469
|
return null;
|
|
1393
1470
|
})();
|
|
1394
|
-
let browserPid, port, wsEndpoint;
|
|
1471
|
+
let browserPid, port, wsEndpoint, freshLaunch;
|
|
1395
1472
|
if (aliveCdpForProfile) {
|
|
1396
1473
|
({ pid: browserPid, port, wsEndpoint } = aliveCdpForProfile);
|
|
1474
|
+
freshLaunch = false;
|
|
1397
1475
|
logEvent('plugkit', 'browser.reused-existing-chromium', { pid: browserPid, port, profileDir });
|
|
1398
1476
|
} else {
|
|
1399
1477
|
logEvent('plugkit', 'browser.start', { profileDir });
|
|
1400
1478
|
({ pid: browserPid, port, wsEndpoint } = startManagedBrowser(pw, profileDir));
|
|
1479
|
+
freshLaunch = true;
|
|
1401
1480
|
}
|
|
1402
1481
|
markLaunching(browserPid);
|
|
1403
1482
|
const r = runBrowserRunner(pw, ['session', 'new', '--direct', wsEndpoint], 30000, cwd, claudeSessionId);
|
|
@@ -1406,6 +1485,10 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
1406
1485
|
logEvent('plugkit', 'browser.launch-failed', { reason: 'session-attach-failed', pid: browserPid, port, error: errTxt });
|
|
1407
1486
|
throw new Error(`playwriter session new --direct failed: ${errTxt}`);
|
|
1408
1487
|
}
|
|
1488
|
+
if (freshLaunch) {
|
|
1489
|
+
const { closed } = closeExtraBlankTabs(port, wsEndpoint);
|
|
1490
|
+
if (closed > 0) logEvent('plugkit', 'browser.extra-blank-tabs-closed', { pid: browserPid, port, closed });
|
|
1491
|
+
}
|
|
1409
1492
|
const pwSessionId = parseSessionId(r.stdout || '');
|
|
1410
1493
|
if (!pwSessionId) {
|
|
1411
1494
|
logEvent('plugkit', 'browser.launch-failed', { reason: 'session-id-unparseable', stdout: r.stdout });
|
|
@@ -2299,7 +2382,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
2299
2382
|
stampBrowserLastUse(cwd, sessionId);
|
|
2300
2383
|
return writeWasmJson(instanceRef.value, {
|
|
2301
2384
|
ok: true,
|
|
2302
|
-
stdout: `Session ${pwSessionId} attached to locally-profiled chromium at ${
|
|
2385
|
+
stdout: `Session ${pwSessionId} attached to locally-profiled chromium at ${sessionProfileDir(cwd, sessionId)}`,
|
|
2303
2386
|
stderr: '',
|
|
2304
2387
|
exit_code: 0,
|
|
2305
2388
|
session_id: pwSessionId,
|