gm-plugkit 2.0.1303 → 2.0.1305

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.1303",
3
+ "version": "2.0.1305",
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": {
@@ -722,105 +722,84 @@ function findInstalledChromiumBinary() {
722
722
  }
723
723
  }
724
724
 
725
+ function fetchJsonSync(url, timeoutMs) {
726
+ const r = spawnSync(process.execPath, ['-e', `
727
+ const http = require('http');
728
+ const req = http.get(${JSON.stringify(url)}, (res) => {
729
+ let buf = '';
730
+ res.on('data', d => buf += d);
731
+ res.on('end', () => {
732
+ if (res.statusCode !== 200) { process.stderr.write('status ' + res.statusCode); process.exit(2); }
733
+ process.stdout.write(buf);
734
+ });
735
+ });
736
+ req.on('error', e => { process.stderr.write(e.message); process.exit(1); });
737
+ req.setTimeout(${timeoutMs || 1500}, () => { req.destroy(new Error('timeout')); });
738
+ `], { encoding: 'utf-8', timeout: (timeoutMs || 1500) + 1500, windowsHide: true });
739
+ if (r.status !== 0) return null;
740
+ try { return JSON.parse(r.stdout); } catch (_) { return null; }
741
+ }
742
+
725
743
  function startManagedBrowser(pw, profileDir) {
726
744
  const headless = process.env.GM_BROWSER_HEADLESS === '1';
727
- const args = [...pw.baseArgs, 'browser', 'start', '--user-data-dir', profileDir];
728
- if (headless) args.push('--headless');
729
- const env = { ...process.env };
730
- if (!env.GM_BROWSER_RUNNER_PATH && !env.PLAYWRITER_BROWSER_PATH) {
731
- const browserBin = findInstalledChromiumBinary();
732
- if (browserBin) {
733
- env.GM_BROWSER_RUNNER_PATH = browserBin;
734
- env.PLAYWRITER_BROWSER_PATH = browserBin;
735
- logEvent('plugkit', 'browser.binary-resolved', { path: browserBin });
736
- } else {
737
- logEvent('plugkit', 'browser.binary-missing', {});
738
- }
745
+ let browserBin = findInstalledChromiumBinary();
746
+ if (!browserBin) {
747
+ logEvent('plugkit', 'browser.chromium-installing', {});
748
+ spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['--yes', 'playwright', 'install', 'chromium'], {
749
+ encoding: 'utf-8',
750
+ timeout: 300000,
751
+ windowsHide: true,
752
+ shell: process.platform === 'win32',
753
+ stdio: 'ignore',
754
+ });
755
+ browserBin = findInstalledChromiumBinary();
739
756
  }
740
- const child = spawn(pw.cmd, args, {
757
+ if (!browserBin) {
758
+ const err = new Error('chromium binary not found after install attempt');
759
+ logEvent('plugkit', 'browser.launch-failed', { reason: 'chromium-missing' });
760
+ throw err;
761
+ }
762
+ const port = findFreePortSync();
763
+ const args = [
764
+ '--user-data-dir=' + profileDir,
765
+ '--remote-debugging-port=' + port,
766
+ '--remote-debugging-address=127.0.0.1',
767
+ '--no-first-run',
768
+ '--no-default-browser-check',
769
+ '--disable-default-apps',
770
+ ];
771
+ if (headless) args.push('--headless=new');
772
+ const chromeLogPath = path.join(profileDir, '.chrome-launch.log');
773
+ let logFd;
774
+ try { logFd = fs.openSync(chromeLogPath, 'a'); } catch (_) { logFd = null; }
775
+ const child = spawn(browserBin, args, {
741
776
  detached: true,
742
- stdio: 'ignore',
743
- shell: pw.shell,
744
- windowsHide: true,
745
- env,
746
- ...(process.platform === 'win32' ? { creationFlags: 0x08000000 | 0x00000008 } : {}),
777
+ stdio: ['ignore', logFd != null ? logFd : 'ignore', logFd != null ? logFd : 'ignore'],
778
+ windowsHide: false,
779
+ env: process.env,
747
780
  });
781
+ try { if (typeof logFd === 'number') fs.closeSync(logFd); } catch (_) {}
748
782
  const pid = child.pid;
749
783
  child.unref();
750
- return pid;
751
- }
752
-
753
- function isColdRunProfile(profileDir) {
754
- try {
755
- if (!fs.existsSync(profileDir)) return true;
756
- const entries = fs.readdirSync(profileDir);
757
- if (entries.length === 0) return true;
758
- if (!entries.some(n => n === 'Default' || n === 'Local State')) return true;
759
- return false;
760
- } catch (_) {
761
- return true;
762
- }
763
- }
764
-
765
- function resolveManagedBrowserKey(pw) {
766
- const explicitKey = process.env.GM_BROWSER_RUNNER_KEY || process.env.PLAYWRITER_BROWSER_KEY;
767
- if (explicitKey) {
768
- if (/^profile:/.test(explicitKey)) {
769
- throw new Error(`GM_BROWSER_RUNNER_KEY=${explicitKey} points at a user OS browser profile; refusing — managed sessions must use install:Chromium:*`);
770
- }
771
- return explicitKey;
772
- }
773
- let text = '';
774
- try {
775
- const r = runBrowserRunner(pw, ['browser', 'list'], 8000);
776
- text = (r && (r.stdout || r.stderr)) || '';
777
- } catch (e) {
778
- throw new Error(`managed browser session list failed: ${e.message}`);
779
- }
780
- const lines = text.split(/\r?\n/);
781
- const installChromium = lines.map(l => (l.match(/\b(install:Chromium:[A-Za-z0-9_-]+)\b/) || [])[1]).find(Boolean);
782
- if (installChromium) return installChromium;
783
- throw new Error(`no managed Chromium install detected; browser runner returned: ${scrubBrowserRunnerText(text).trim()}`);
784
- }
785
-
786
- function waitForExtensionReady(pw, profileDir, opts) {
787
- const cold = (opts && typeof opts.cold === 'boolean') ? opts.cold : isColdRunProfile(profileDir);
788
- const timeoutMs = (opts && opts.timeoutMs) || (cold ? 180000 : 30000);
784
+ logEvent('plugkit', 'browser.chromium-launched', { pid, port, profileDir, headless, binary: browserBin, chromeLogPath });
789
785
  const start = Date.now();
790
- const deadline = start + timeoutMs;
791
- const backoff = [2000, 4000, 8000];
792
- let attempt = 0;
793
- let lastErr = '';
794
- let lastProgressAt = start;
795
- const browserKey = resolveManagedBrowserKey(pw);
786
+ const deadline = start + 30000;
787
+ let wsEndpoint = null;
788
+ let lastErr = null;
796
789
  while (Date.now() < deadline) {
797
- const remaining = deadline - Date.now();
798
- const innerTimeout = Math.max(28000, Math.min(remaining, 30000));
799
- const r = runBrowserRunner(pw, ['session', 'new', '--browser', browserKey], innerTimeout);
800
- if (r && r.status === 0) return r;
801
- lastErr = scrubBrowserRunnerText((r && (r.stderr || r.stdout)) || '');
802
- const now = Date.now();
803
- if (now - lastProgressAt >= 10000) {
804
- logEvent('plugkit', 'browser.extension-wait', {
805
- elapsed_ms: now - start,
806
- cold_run: cold,
807
- profileDir,
808
- attempt,
809
- });
810
- lastProgressAt = now;
790
+ const info = fetchJsonSync(`http://127.0.0.1:${port}/json/version`, 1500);
791
+ if (info && info.webSocketDebuggerUrl) {
792
+ wsEndpoint = info.webSocketDebuggerUrl;
793
+ break;
811
794
  }
812
- const sleepMs = backoff[Math.min(attempt, backoff.length - 1)];
813
- attempt++;
814
- if (Date.now() + sleepMs >= deadline) break;
815
- sleepSync(sleepMs);
795
+ sleepSync(500);
796
+ }
797
+ if (!wsEndpoint) {
798
+ logEvent('plugkit', 'browser.launch-failed', { reason: 'cdp-not-ready', pid, port, elapsed_ms: Date.now() - start });
799
+ throw new Error(`chromium launched (pid=${pid}) but CDP at 127.0.0.1:${port} did not become ready within 30s${lastErr ? ' :: ' + lastErr : ''}`);
816
800
  }
817
- const flavor = cold
818
- ? `cold-run timeout after ${Math.round((Date.now() - start) / 1000)}s waiting for managed browser extension to connect (first run downloads chromium ~150MB and installs the extension; if this persists the extension never registered with the relay server)`
819
- : `warm-run timeout after ${Math.round((Date.now() - start) / 1000)}s waiting for managed browser extension to reconnect (profile exists but extension is not registering; relay server may be wedged)`;
820
- const err = new Error(`managed browser session start failed: ${flavor}${lastErr ? ` :: ${lastErr}` : ''}`);
821
- err._lastErr = lastErr;
822
- err._coldRun = cold;
823
- throw err;
801
+ logEvent('plugkit', 'browser.cdp-ready', { pid, port, ms: Date.now() - start, wsEndpoint });
802
+ return { pid, port, wsEndpoint };
824
803
  }
825
804
 
826
805
  function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
@@ -830,15 +809,29 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
830
809
  const ports = readJsonFile(portsFile, {});
831
810
  const sessions = readJsonFile(sessionsFile, {});
832
811
  const existing = ports[claudeSessionId];
833
- if (existing && existing.pid) {
812
+ if (existing && existing.pid && existing.wsEndpoint) {
834
813
  const wantProfile = path.join(cwd, '.gm', 'browser-profile');
835
814
  const pidOk = isProcessAliveSync(existing.pid);
836
815
  const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(path.join(cwd, '.gm', 'browser-profile'));
837
- if (pidOk && profileOk) {
816
+ const cdpOk = pidOk && !!fetchJsonSync(`http://127.0.0.1:${existing.port}/json/version`, 1000);
817
+ if (pidOk && profileOk && cdpOk) {
838
818
  const pwIds = sessions[claudeSessionId] || [];
839
- if (pwIds.length > 0) return pwIds[0];
819
+ if (pwIds.length > 0 && existing.pwSessionId) return existing.pwSessionId;
820
+ const r = runBrowserRunner(pw, ['session', 'new', '--direct', existing.wsEndpoint], 30000);
821
+ if (r && r.status === 0) {
822
+ const sid = parseSessionId(r.stdout || '');
823
+ if (sid) {
824
+ existing.pwSessionId = sid;
825
+ ports[claudeSessionId] = existing;
826
+ sessions[claudeSessionId] = [sid];
827
+ writeJsonFile(portsFile, ports);
828
+ writeJsonFile(sessionsFile, sessions);
829
+ logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true });
830
+ return sid;
831
+ }
832
+ }
840
833
  } else {
841
- const reason = !pidOk ? 'pid-dead' : 'profile-drift';
834
+ const reason = !pidOk ? 'pid-dead' : (!cdpOk ? 'cdp-dead' : 'profile-drift');
842
835
  logEvent('hook', 'deviation.browser-profile-collision', {
843
836
  sid: claudeSessionId,
844
837
  stale_pid: existing.pid || null,
@@ -853,32 +846,39 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
853
846
  }
854
847
  }
855
848
  cleanDeadProfileFragments(cwd);
856
- const probedProfile = path.join(cwd, '.gm', 'browser-profile');
857
- const coldRun = isColdRunProfile(probedProfile);
858
849
  const profileDir = acquireProfileDir(cwd);
859
- logEvent('plugkit', 'browser.start', { profileDir, cold_run: coldRun });
860
- const browserPid = startManagedBrowser(pw, profileDir);
861
- const newR = waitForExtensionReady(pw, profileDir, { cold: coldRun });
862
- const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
863
- const out = stripAnsi(newR.stdout || '').trim();
864
- let pwSessionId = null;
865
- const created = out.match(/Session\s+(\S+)\s+created/i);
866
- if (created) pwSessionId = created[1];
867
- if (!pwSessionId) {
868
- const hex = out.match(/\b([a-f0-9-]{8,})\b/i);
869
- if (hex) pwSessionId = hex[1];
850
+ logEvent('plugkit', 'browser.start', { profileDir });
851
+ const { pid: browserPid, port, wsEndpoint } = startManagedBrowser(pw, profileDir);
852
+ const r = runBrowserRunner(pw, ['session', 'new', '--direct', wsEndpoint], 30000);
853
+ if (!r || r.status !== 0) {
854
+ const errTxt = scrubBrowserRunnerText((r && (r.stderr || r.stdout)) || 'unknown');
855
+ logEvent('plugkit', 'browser.launch-failed', { reason: 'session-attach-failed', pid: browserPid, port, error: errTxt });
856
+ throw new Error(`playwriter session new --direct failed: ${errTxt}`);
870
857
  }
858
+ const pwSessionId = parseSessionId(r.stdout || '');
871
859
  if (!pwSessionId) {
872
- try { const j = JSON.parse(out); pwSessionId = j.id || j.session_id || j.session; } catch (_) {}
860
+ logEvent('plugkit', 'browser.launch-failed', { reason: 'session-id-unparseable', stdout: r.stdout });
861
+ throw new Error(`could not parse managed browser session id from: ${scrubBrowserRunnerText(r.stdout || '')}`);
873
862
  }
874
- if (!pwSessionId) throw new Error(`could not parse managed browser session id from: ${scrubBrowserRunnerText(out)}`);
875
- ports[claudeSessionId] = { profileDir, pid: browserPid };
863
+ ports[claudeSessionId] = { profileDir, pid: browserPid, port, wsEndpoint, pwSessionId };
876
864
  sessions[claudeSessionId] = [pwSessionId];
877
865
  writeJsonFile(portsFile, ports);
878
866
  writeJsonFile(sessionsFile, sessions);
867
+ logEvent('plugkit', 'browser.attached', { pwSessionId, pid: browserPid, port });
879
868
  return pwSessionId;
880
869
  }
881
870
 
871
+ function parseSessionId(rawOut) {
872
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
873
+ const out = stripAnsi(rawOut || '').trim();
874
+ const created = out.match(/Session\s+(\S+)\s+created/i);
875
+ if (created) return created[1];
876
+ const hex = out.match(/\b([a-f0-9-]{8,})\b/i);
877
+ if (hex) return hex[1];
878
+ try { const j = JSON.parse(out); return j.id || j.session_id || j.session || null; } catch (_) {}
879
+ return null;
880
+ }
881
+
882
882
  const VEC_K_DEFAULT = 10;
883
883
  const EMBED_MODEL_DEFAULT = process.env.EMBED_MODEL || 'mistral/mistral-embed';
884
884
  const INFERENCE_MODEL_DEFAULT = process.env.INFERENCE_MODEL || 'groq/llama-3.3-70b-versatile';
package/plugkit.sha256 CHANGED
@@ -1 +1 @@
1
- {"plugkit.wasm":"8b45c841f3d43e28baed5f3388b3e21238e9c368cef5b99a0e5ca8b682a5d6eb"}
1
+ {"plugkit.wasm":"7f24800faa40a43e615a98669dedfc7be0a2650dcb37e63dafb68e1f39c500a3"}
package/plugkit.version CHANGED
@@ -1 +1 @@
1
- 0.1.484
1
+ 0.1.485