gm-plugkit 2.0.1687 → 2.0.1689

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1687",
3
+ "version": "2.0.1689",
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": {
@@ -1168,11 +1168,14 @@ function closeExtraBlankTabs(port, keepWsEndpoint) {
1168
1168
  try {
1169
1169
  const targets = fetchJsonSync(`http://127.0.0.1:${port}/json/list`, 1500);
1170
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));
1171
1176
  let closed = 0;
1172
- for (const t of targets) {
1173
- if (!t || t.type !== 'page') continue;
1177
+ for (const t of toClose) {
1174
1178
  if (t.webSocketDebuggerUrl === keepWsEndpoint) continue;
1175
- if (t.url !== 'about:blank' && t.url !== '') continue;
1176
1179
  const r = spawnSync(process.execPath, ['-e', `
1177
1180
  const http = require('http');
1178
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)); });
@@ -1187,26 +1190,15 @@ function closeExtraBlankTabs(port, keepWsEndpoint) {
1187
1190
  }
1188
1191
  }
1189
1192
 
1190
- function startManagedBrowser(pw, profileDir) {
1191
- const headless = process.env.GM_BROWSER_HEADLESS === '1';
1192
- let browserBin = findInstalledChromiumBinary();
1193
- if (!browserBin) {
1194
- logEvent('plugkit', 'browser.chromium-installing', {});
1195
- spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['--yes', 'playwright', 'install', 'chromium'], {
1196
- encoding: 'utf-8',
1197
- timeout: 300000,
1198
- windowsHide: true,
1199
- shell: process.platform === 'win32',
1200
- stdio: 'ignore',
1201
- });
1202
- browserBin = findInstalledChromiumBinary();
1203
- }
1204
- if (!browserBin) {
1205
- const err = new Error('chromium binary not found after install attempt');
1206
- logEvent('plugkit', 'browser.launch-failed', { reason: 'chromium-missing' });
1207
- throw err;
1193
+ function chromeLogHasSandboxDenied(chromeLogPath) {
1194
+ try {
1195
+ return /Sandbox cannot access executable/.test(fs.readFileSync(chromeLogPath, 'utf-8'));
1196
+ } catch (_) {
1197
+ return false;
1208
1198
  }
1209
- const port = findFreePortSync();
1199
+ }
1200
+
1201
+ function spawnChromiumOnce(browserBin, profileDir, port, headless, noSandbox) {
1210
1202
  const args = [
1211
1203
  '--user-data-dir=' + profileDir,
1212
1204
  '--remote-debugging-port=' + port,
@@ -1216,7 +1208,7 @@ function startManagedBrowser(pw, profileDir) {
1216
1208
  '--disable-default-apps',
1217
1209
  '--disable-gpu-process-crash-limit',
1218
1210
  ];
1219
- if (process.env.GM_BROWSER_NO_SANDBOX === '1') {
1211
+ if (noSandbox) {
1220
1212
  args.push('--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage');
1221
1213
  }
1222
1214
  if (headless) {
@@ -1234,27 +1226,70 @@ function startManagedBrowser(pw, profileDir) {
1234
1226
  env: process.env,
1235
1227
  });
1236
1228
  try { if (typeof logFd === 'number') fs.closeSync(logFd); } catch (_) {}
1237
- const pid = child.pid;
1238
1229
  child.unref();
1239
- logEvent('plugkit', 'browser.chromium-launched', { pid, port, profileDir, headless, binary: browserBin, chromeLogPath });
1230
+ return { pid: child.pid, chromeLogPath };
1231
+ }
1232
+
1233
+ function waitForCdpReady(port, deadlineMs) {
1240
1234
  const start = Date.now();
1241
- const deadline = start + 30000;
1242
- let wsEndpoint = null;
1243
- let lastErr = null;
1235
+ const deadline = start + deadlineMs;
1244
1236
  while (Date.now() < deadline) {
1245
1237
  const info = fetchJsonSync(`http://127.0.0.1:${port}/json/version`, 1500);
1246
- if (info && info.webSocketDebuggerUrl) {
1247
- wsEndpoint = info.webSocketDebuggerUrl;
1248
- break;
1249
- }
1238
+ if (info && info.webSocketDebuggerUrl) return { wsEndpoint: info.webSocketDebuggerUrl, ms: Date.now() - start };
1250
1239
  sleepSync(500);
1251
1240
  }
1252
- if (!wsEndpoint) {
1253
- logEvent('plugkit', 'browser.launch-failed', { reason: 'cdp-not-ready', pid, port, elapsed_ms: Date.now() - start });
1254
- throw new Error(`chromium launched (pid=${pid}) but CDP at 127.0.0.1:${port} did not become ready within 30s${lastErr ? ' :: ' + lastErr : ''}`);
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 };
1255
1290
  }
1256
- logEvent('plugkit', 'browser.cdp-ready', { pid, port, ms: Date.now() - start, wsEndpoint });
1257
- 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 };
1258
1293
  }
1259
1294
 
1260
1295
  function killPidQuiet(pid) {
@@ -1306,6 +1341,18 @@ function gracefulCloseBrowser(entry, reason) {
1306
1341
  try { logEvent('plugkit', 'browser.closed', { reason: reason || 'closed', pid, port, profileDir }); } catch (_) {}
1307
1342
  }
1308
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
+
1309
1356
  function resolveExistingBrowserEntry(cwd, claudeSessionId, pw, portsFile, sessionsFile, ports, sessions) {
1310
1357
  const existing = ports[claudeSessionId];
1311
1358
  if (!(existing && existing.pid && existing.wsEndpoint)) return null;
@@ -1315,7 +1362,10 @@ function resolveExistingBrowserEntry(cwd, claudeSessionId, pw, portsFile, sessio
1315
1362
  const cdpOk = pidOk && !!fetchJsonSync(`http://127.0.0.1:${existing.port}/json/version`, 1000);
1316
1363
  if (pidOk && profileOk && cdpOk) {
1317
1364
  const pwIds = sessions[claudeSessionId] || [];
1318
- if (pwIds.length > 0 && existing.pwSessionId) return existing.pwSessionId;
1365
+ if (pwIds.length > 0 && existing.pwSessionId) {
1366
+ checkSessionNavigatedAway(existing.port, claudeSessionId);
1367
+ return existing.pwSessionId;
1368
+ }
1319
1369
  const r = runBrowserRunner(pw, ['session', 'new', '--direct', existing.wsEndpoint], 30000, cwd, claudeSessionId);
1320
1370
  if (r && r.status === 0) {
1321
1371
  const sid = parseSessionId(r.stdout || '');
@@ -1385,7 +1435,11 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1385
1435
  && fetchJsonSync(`http://127.0.0.1:${winner.port}/json/version`, 1000)) {
1386
1436
  const a = runBrowserRunner(pw, ['session', 'new', '--direct', winner.wsEndpoint], 30000, cwd, claudeSessionId);
1387
1437
  const sid = a && a.status === 0 ? parseSessionId(a.stdout || '') : null;
1388
- if (sid) { logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true, via: 'spawn-lock-wait' }); return 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
+ }
1389
1443
  }
1390
1444
  if (Date.now() > spawnDeadline) break;
1391
1445
  sleepSyncMs(300);
@@ -2328,7 +2382,7 @@ function makeHostFunctions(instanceRef) {
2328
2382
  stampBrowserLastUse(cwd, sessionId);
2329
2383
  return writeWasmJson(instanceRef.value, {
2330
2384
  ok: true,
2331
- stdout: `Session ${pwSessionId} attached to locally-profiled chromium at ${path.join(cwd, '.gm', 'browser-profile')}`,
2385
+ stdout: `Session ${pwSessionId} attached to locally-profiled chromium at ${sessionProfileDir(cwd, sessionId)}`,
2332
2386
  stderr: '',
2333
2387
  exit_code: 0,
2334
2388
  session_id: pwSessionId,
package/plugkit.version CHANGED
@@ -1 +1 @@
1
- 0.1.735
1
+ 0.1.736