gm-plugkit 2.0.1291 → 2.0.1292

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.1291",
3
+ "version": "2.0.1292",
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": {
@@ -461,15 +461,17 @@ function writeJsonFile(fp, value) {
461
461
  try { fs.writeFileSync(fp, JSON.stringify(value, null, 2)); } catch (_) {}
462
462
  }
463
463
 
464
- function findPlaywriter() {
464
+ const BROWSER_RUNNER_BIN = process.env.GM_BROWSER_RUNNER_BIN || 'playwriter';
465
+
466
+ function findBrowserRunner() {
465
467
  const npmR = spawnSync('npm', ['root', '-g'], { encoding: 'utf-8', shell: true });
466
468
  if (npmR.status === 0 && npmR.stdout.trim()) {
467
469
  const root = npmR.stdout.trim().split(/\r?\n/).pop();
468
- const binJs = path.join(root, 'playwriter', 'bin.js');
470
+ const binJs = path.join(root, BROWSER_RUNNER_BIN, 'bin.js');
469
471
  if (fs.existsSync(binJs)) return { cmd: process.execPath, baseArgs: [binJs], shell: false };
470
472
  }
471
473
  const whichCmd = process.platform === 'win32' ? 'where' : 'which';
472
- const r = spawnSync(whichCmd, ['playwriter'], { encoding: 'utf-8', shell: true });
474
+ const r = spawnSync(whichCmd, [BROWSER_RUNNER_BIN], { encoding: 'utf-8', shell: true });
473
475
  if (r.status === 0 && r.stdout.trim()) {
474
476
  const candidates = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
475
477
  const cmd = candidates.find(c => c.toLowerCase().endsWith('.cmd')) || candidates.find(c => !c.toLowerCase().endsWith('.ps1')) || candidates[0];
@@ -477,11 +479,11 @@ function findPlaywriter() {
477
479
  }
478
480
  const bunR = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', shell: true });
479
481
  if (bunR.status === 0 && bunR.stdout.trim()) {
480
- return { cmd: 'bun', baseArgs: ['x', 'playwriter@latest'], shell: true };
482
+ return { cmd: 'bun', baseArgs: ['x', `${BROWSER_RUNNER_BIN}@latest`], shell: true };
481
483
  }
482
484
  const npxR = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', shell: true });
483
485
  if (npxR.status === 0 && npxR.stdout.trim()) {
484
- return { cmd: 'npx', baseArgs: ['-y', 'playwriter'], shell: true };
486
+ return { cmd: 'npx', baseArgs: ['-y', BROWSER_RUNNER_BIN], shell: true };
485
487
  }
486
488
  return null;
487
489
  }
@@ -637,7 +639,7 @@ function sleepSync(ms) {
637
639
  spawnSync(process.execPath, ['-e', `setTimeout(()=>{}, ${ms})`], { timeout: ms + 2000 });
638
640
  }
639
641
 
640
- function runPlaywriter(pw, args, timeoutMs) {
642
+ function runBrowserRunner(pw, args, timeoutMs) {
641
643
  const allArgs = [...pw.baseArgs, ...args];
642
644
  const useShell = !!pw.shell;
643
645
  const spawnCmd = useShell && /\s/.test(pw.cmd) ? `"${pw.cmd}"` : pw.cmd;
@@ -654,7 +656,7 @@ function runPlaywriter(pw, args, timeoutMs) {
654
656
  function scrubBrowserRunnerText(s) {
655
657
  if (!s || typeof s !== 'string') return s;
656
658
  let t = s;
657
- t = t.replace(/(^|[^A-Za-z0-9_\\/.-])playwriter(?![A-Za-z0-9_\\/.-])/gi, (m, pre) => `${pre}managed browser session`);
659
+ t = t.replace(/(^|[^A-Za-z0-9_\\/.-])(playwriter|playwright|puppeteer)(?![A-Za-z0-9_\\/.-])/gi, (m, pre) => `${pre}managed browser session`);
658
660
  t = t.replace(/Click the[^.\n]*?extension[^.\n]*?icon[^.\n]*?\.?/gi, '');
659
661
  t = t.replace(/(connected\s+)?browser\s+extension(\s+is)?\s+not\s+connected\b[^.\n]*\.?/gi, '');
660
662
  t = t.replace(/no\s+connected\s+browsers?\b[^.\n]*\.?/gi, '');
@@ -664,18 +666,20 @@ function scrubBrowserRunnerText(s) {
664
666
 
665
667
  function findInstalledChromiumBinary() {
666
668
  try {
667
- if (process.env.PLAYWRITER_BROWSER_PATH && fs.existsSync(process.env.PLAYWRITER_BROWSER_PATH)) {
668
- return process.env.PLAYWRITER_BROWSER_PATH;
669
+ const explicit = process.env.GM_BROWSER_RUNNER_PATH || process.env.PLAYWRITER_BROWSER_PATH;
670
+ if (explicit && fs.existsSync(explicit)) {
671
+ return explicit;
669
672
  }
670
673
  const roots = [];
674
+ const cacheDir = process.env.GM_BROWSER_RUNNER_CACHE_DIR || 'ms-playwright';
671
675
  if (process.platform === 'win32') {
672
676
  const lad = process.env.LOCALAPPDATA;
673
- if (lad) roots.push(path.join(lad, 'ms-playwright'));
677
+ if (lad) roots.push(path.join(lad, cacheDir));
674
678
  } else {
675
679
  const home = process.env.HOME || '';
676
680
  if (home) {
677
- roots.push(path.join(home, '.cache', 'ms-playwright'));
678
- roots.push(path.join(home, 'Library', 'Caches', 'ms-playwright'));
681
+ roots.push(path.join(home, '.cache', cacheDir));
682
+ roots.push(path.join(home, 'Library', 'Caches', cacheDir));
679
683
  }
680
684
  }
681
685
  const exeName = process.platform === 'win32' ? 'chrome.exe' : (process.platform === 'darwin' ? 'Chromium.app/Contents/MacOS/Chromium' : 'chrome');
@@ -707,9 +711,10 @@ function findInstalledChromiumBinary() {
707
711
  function startManagedBrowser(pw, profileDir) {
708
712
  const args = [...pw.baseArgs, 'browser', 'start', '--user-data-dir', profileDir, '--headless'];
709
713
  const env = { ...process.env };
710
- if (!env.PLAYWRITER_BROWSER_PATH) {
714
+ if (!env.GM_BROWSER_RUNNER_PATH && !env.PLAYWRITER_BROWSER_PATH) {
711
715
  const browserBin = findInstalledChromiumBinary();
712
716
  if (browserBin) {
717
+ env.GM_BROWSER_RUNNER_PATH = browserBin;
713
718
  env.PLAYWRITER_BROWSER_PATH = browserBin;
714
719
  logEvent('plugkit', 'browser.binary-resolved', { path: browserBin });
715
720
  } else {
@@ -742,24 +747,24 @@ function isColdRunProfile(profileDir) {
742
747
  }
743
748
 
744
749
  function resolveManagedBrowserKey(pw) {
745
- if (process.env.PLAYWRITER_BROWSER_KEY) {
746
- const k = process.env.PLAYWRITER_BROWSER_KEY;
747
- if (/^profile:/.test(k)) {
748
- throw new Error(`PLAYWRITER_BROWSER_KEY=${k} points at a user OS browser profile; refusing — managed sessions must use install:Chromium:*`);
750
+ const explicitKey = process.env.GM_BROWSER_RUNNER_KEY || process.env.PLAYWRITER_BROWSER_KEY;
751
+ if (explicitKey) {
752
+ if (/^profile:/.test(explicitKey)) {
753
+ throw new Error(`GM_BROWSER_RUNNER_KEY=${explicitKey} points at a user OS browser profile; refusing — managed sessions must use install:Chromium:*`);
749
754
  }
750
- return k;
755
+ return explicitKey;
751
756
  }
752
757
  let text = '';
753
758
  try {
754
- const r = runPlaywriter(pw, ['browser', 'list'], 8000);
759
+ const r = runBrowserRunner(pw, ['browser', 'list'], 8000);
755
760
  text = (r && (r.stdout || r.stderr)) || '';
756
761
  } catch (e) {
757
- throw new Error(`playwriter browser list failed: ${e.message}`);
762
+ throw new Error(`managed browser session list failed: ${e.message}`);
758
763
  }
759
764
  const lines = text.split(/\r?\n/);
760
765
  const installChromium = lines.map(l => (l.match(/\b(install:Chromium:[A-Za-z0-9_-]+)\b/) || [])[1]).find(Boolean);
761
766
  if (installChromium) return installChromium;
762
- throw new Error(`no managed Chromium install detected; playwriter browser list returned: ${scrubBrowserRunnerText(text).trim()}`);
767
+ throw new Error(`no managed Chromium install detected; browser runner returned: ${scrubBrowserRunnerText(text).trim()}`);
763
768
  }
764
769
 
765
770
  function waitForExtensionReady(pw, profileDir, opts) {
@@ -775,7 +780,7 @@ function waitForExtensionReady(pw, profileDir, opts) {
775
780
  while (Date.now() < deadline) {
776
781
  const remaining = deadline - Date.now();
777
782
  const innerTimeout = Math.max(28000, Math.min(remaining, 30000));
778
- const r = runPlaywriter(pw, ['session', 'new', '--browser', browserKey], innerTimeout);
783
+ const r = runBrowserRunner(pw, ['session', 'new', '--browser', browserKey], innerTimeout);
779
784
  if (r && r.status === 0) return r;
780
785
  lastErr = scrubBrowserRunnerText((r && (r.stderr || r.stdout)) || '');
781
786
  const now = Date.now();
@@ -1461,11 +1466,11 @@ function makeHostFunctions(instanceRef) {
1461
1466
  const body = readWasmStr(instanceRef.value, bodyPtr, bodyLen);
1462
1467
  const cwd = readWasmStr(instanceRef.value, cwdPtr, cwdLen) || process.cwd();
1463
1468
  const sessionId = readWasmStr(instanceRef.value, sidPtr, sidLen) || 'default';
1464
- const pw = findPlaywriter();
1469
+ const pw = findBrowserRunner();
1465
1470
  if (!pw) return writeWasmJson(instanceRef.value, { ok: false, error: 'managed browser session runner not available' });
1466
1471
  if (body.startsWith('session ')) {
1467
1472
  const parts = body.slice(8).trim().split(/\s+/);
1468
- const r = runPlaywriter(pw, ['session', ...parts], 30000);
1473
+ const r = runBrowserRunner(pw, ['session', ...parts], 30000);
1469
1474
  return writeWasmJson(instanceRef.value, {
1470
1475
  ok: r.status === 0,
1471
1476
  stdout: scrubBrowserRunnerText(r.stdout || ''),
@@ -1474,7 +1479,7 @@ function makeHostFunctions(instanceRef) {
1474
1479
  });
1475
1480
  }
1476
1481
  const pwSessionId = getOrCreateBrowserSession(cwd, sessionId, pw);
1477
- const r = runPlaywriter(pw, ['-s', pwSessionId, '--timeout', '14000', '-e', body], 60000);
1482
+ const r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', '14000', '-e', body], 60000);
1478
1483
  return writeWasmJson(instanceRef.value, {
1479
1484
  ok: r.status === 0,
1480
1485
  stdout: scrubBrowserRunnerText(r.stdout || ''),
@@ -1659,7 +1664,13 @@ async function runSpoolWatcher(instance, spoolDir) {
1659
1664
  const ageMs = Date.now() - priorBoot.ts;
1660
1665
  const shutdownIsNewer = priorShutdownForAbort && Number.isFinite(priorShutdownForAbort.ts) && priorShutdownForAbort.ts >= priorBoot.ts;
1661
1666
  if (ageMs > 30_000 && !shutdownIsNewer) {
1662
- logEvent('plugkit', 'watcher.silent-abort', {
1667
+ let priorVerbSnap = null;
1668
+ let priorStatusSnap = null;
1669
+ let priorPidAlive = null;
1670
+ try { priorVerbSnap = JSON.parse(fs.readFileSync(VERB_ACTIVE_PATH, 'utf-8')); } catch (_) {}
1671
+ try { priorStatusSnap = JSON.parse(fs.readFileSync(path.join(path.dirname(BOOT_ACTIVE_PATH), '.status.json'), 'utf-8')); } catch (_) {}
1672
+ try { process.kill(priorBoot.pid, 0); priorPidAlive = true; } catch (e) { priorPidAlive = e.code === 'EPERM'; }
1673
+ const forensics = {
1663
1674
  prior_pid: priorBoot.pid,
1664
1675
  prior_ts: priorBoot.ts,
1665
1676
  prior_sha: priorBoot.wrapper_sha || null,
@@ -1668,8 +1679,15 @@ async function runSpoolWatcher(instance, spoolDir) {
1668
1679
  age_ms: ageMs,
1669
1680
  shutdown_reason_present: !!priorShutdownForAbort,
1670
1681
  shutdown_reason_ts: priorShutdownForAbort ? priorShutdownForAbort.ts : null,
1671
- });
1672
- try { console.error(`[plugkit-wasm] SILENT ABORT detected: prior watcher pid=${priorBoot.pid} sha=${priorBoot.wrapper_sha} died without writing .shutdown-reason.json (age=${ageMs}ms)`); } catch (_) {}
1682
+ prior_verb_active: priorVerbSnap,
1683
+ prior_status: priorStatusSnap,
1684
+ prior_pid_alive: priorPidAlive,
1685
+ };
1686
+ logEvent('plugkit', 'watcher.silent-abort', forensics);
1687
+ try {
1688
+ fs.writeFileSync(path.join(path.dirname(BOOT_ACTIVE_PATH), '.silent-abort-forensics.json'), JSON.stringify(forensics, null, 2));
1689
+ } catch (_) {}
1690
+ try { console.error(`[plugkit-wasm] SILENT ABORT detected: prior watcher pid=${priorBoot.pid} sha=${priorBoot.wrapper_sha} died without writing .shutdown-reason.json (age=${ageMs}ms, prior_pid_alive=${priorPidAlive})`); } catch (_) {}
1673
1691
  }
1674
1692
  }
1675
1693
  } catch (_) {}