gm-plugkit 2.0.1629 → 2.0.1631

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.1629",
3
+ "version": "2.0.1631",
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": {
@@ -549,8 +549,26 @@ const TMP_DIR = os.tmpdir();
549
549
  const LEGACY_BROWSER_PORTS_FILE = path.join(TMP_DIR, 'plugkit-browser-ports.json');
550
550
  const LEGACY_BROWSER_SESSIONS_FILE = path.join(TMP_DIR, 'plugkit-browser-sessions.json');
551
551
 
552
+ const __browserRootCache = new Map();
553
+ function browserRootDir(cwd) {
554
+ const start = path.resolve(cwd || process.cwd());
555
+ if (__browserRootCache.has(start)) return __browserRootCache.get(start);
556
+ let root = start;
557
+ try {
558
+ const r = spawnSync('git', ['rev-parse', '--git-common-dir'], { cwd: start, encoding: 'utf-8', windowsHide: true, timeout: 1500 });
559
+ if (r.status === 0 && r.stdout && r.stdout.trim()) {
560
+ let commonDir = r.stdout.trim();
561
+ if (!path.isAbsolute(commonDir)) commonDir = path.resolve(start, commonDir);
562
+ if (/(^|[\\/])\.git$/.test(commonDir)) root = path.dirname(commonDir);
563
+ }
564
+ } catch (_) {}
565
+ root = path.resolve(root);
566
+ __browserRootCache.set(start, root);
567
+ return root;
568
+ }
569
+
552
570
  function browserStateDir(cwd) {
553
- const dir = path.join(cwd || process.cwd(), '.gm', 'exec-spool');
571
+ const dir = path.join(browserRootDir(cwd), '.gm', 'exec-spool');
554
572
  try { fs.mkdirSync(dir, { recursive: true }); } catch (_) {}
555
573
  return dir;
556
574
  }
@@ -752,14 +770,15 @@ function sessionProfileSlug(claudeSessionId) {
752
770
  }
753
771
 
754
772
  function sessionProfileDir(cwd, claudeSessionId) {
755
- return path.join(cwd, '.gm', `browser-profile-${sessionProfileSlug(claudeSessionId)}`);
773
+ return path.join(browserRootDir(cwd), '.gm', `browser-profile-${sessionProfileSlug(claudeSessionId)}`);
756
774
  }
757
775
 
758
776
  function acquireProfileDir(cwd, claudeSessionId) {
759
- const gmDir = path.join(cwd, '.gm');
777
+ const root = browserRootDir(cwd);
778
+ const gmDir = path.join(root, '.gm');
760
779
  try { fs.mkdirSync(gmDir, { recursive: true }); } catch (_) {}
761
- ensureGitignored(cwd, '.gm/browser-profile/');
762
- ensureGitignored(cwd, '.gm/browser-profile-*/');
780
+ ensureGitignored(root, '.gm/browser-profile/');
781
+ ensureGitignored(root, '.gm/browser-profile-*/');
763
782
  const primary = sessionProfileDir(cwd, claudeSessionId);
764
783
  try { fs.mkdirSync(primary, { recursive: true }); } catch (_) {}
765
784
  if (!isProfileLocked(primary)) return primary;
@@ -770,7 +789,7 @@ function acquireProfileDir(cwd, claudeSessionId) {
770
789
 
771
790
  function cleanDeadProfileFragments(cwd) {
772
791
  try {
773
- const gmDir = path.join(cwd, '.gm');
792
+ const gmDir = path.join(browserRootDir(cwd), '.gm');
774
793
  if (!fs.existsSync(gmDir)) return { cleaned: 0 };
775
794
  let cleaned = 0;
776
795
  for (const name of fs.readdirSync(gmDir)) {
@@ -1202,6 +1221,41 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1202
1221
  try { writeJsonFile(sessionsFile, sessions); } catch (_) {}
1203
1222
  }
1204
1223
  }
1224
+ const spawnLock = path.join(browserStateDir(cwd), `.browser-spawn-${sessionProfileSlug(claudeSessionId)}.lock`);
1225
+ let lockFd = null;
1226
+ const spawnDeadline = Date.now() + 35000;
1227
+ for (;;) {
1228
+ try { lockFd = fs.openSync(spawnLock, 'wx'); break; }
1229
+ catch (e) {
1230
+ if (e.code !== 'EEXIST') break;
1231
+ let stale = false;
1232
+ try {
1233
+ const owner = parseInt(String(fs.readFileSync(spawnLock, 'utf-8')).split('|')[0], 10);
1234
+ const ageOk = (Date.now() - fs.statSync(spawnLock).mtimeMs) < 40000;
1235
+ if (!ageOk || !(Number.isFinite(owner) && isProcessAliveSync(owner))) stale = true;
1236
+ } catch (_) { stale = true; }
1237
+ if (stale) { try { fs.unlinkSync(spawnLock); } catch (_) {} continue; }
1238
+ const winner = readJsonFile(portsFile, {})[claudeSessionId];
1239
+ if (winner && winner.pid && winner.wsEndpoint && isProcessAliveSync(winner.pid)
1240
+ && fetchJsonSync(`http://127.0.0.1:${winner.port}/json/version`, 1000)) {
1241
+ const a = runBrowserRunner(pw, ['session', 'new', '--direct', winner.wsEndpoint], 30000, cwd, claudeSessionId);
1242
+ const sid = a && a.status === 0 ? parseSessionId(a.stdout || '') : null;
1243
+ if (sid) { logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true, via: 'spawn-lock-wait' }); return sid; }
1244
+ }
1245
+ if (Date.now() > spawnDeadline) break;
1246
+ sleepSyncMs(300);
1247
+ }
1248
+ }
1249
+ try { if (lockFd !== null) { fs.writeSync(lockFd, `${process.pid}|${Date.now()}`); fs.closeSync(lockFd); } } catch (_) {}
1250
+ const releaseSpawnLock = () => { try { const o = parseInt(String(fs.readFileSync(spawnLock, 'utf-8')).split('|')[0], 10); if (o === process.pid) fs.unlinkSync(spawnLock); } catch (_) {} };
1251
+ try {
1252
+ const winner2 = readJsonFile(portsFile, {})[claudeSessionId];
1253
+ if (winner2 && winner2.pid && winner2.wsEndpoint && isProcessAliveSync(winner2.pid)
1254
+ && fetchJsonSync(`http://127.0.0.1:${winner2.port}/json/version`, 1000)) {
1255
+ const a = runBrowserRunner(pw, ['session', 'new', '--direct', winner2.wsEndpoint], 30000, cwd, claudeSessionId);
1256
+ const sid = a && a.status === 0 ? parseSessionId(a.stdout || '') : null;
1257
+ if (sid) { logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true, via: 'spawn-lock-recheck' }); return sid; }
1258
+ }
1205
1259
  cleanDeadProfileFragments(cwd);
1206
1260
  reapOrphanBrowserSessions(pw, cwd, claudeSessionId, 'pre-spawn');
1207
1261
  const profileDir = acquireProfileDir(cwd, claudeSessionId);
@@ -1243,6 +1297,7 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1243
1297
  writeJsonFile(sessionsFile, sessions);
1244
1298
  logEvent('plugkit', 'browser.attached', { pwSessionId, pid: browserPid, port });
1245
1299
  return pwSessionId;
1300
+ } finally { releaseSpawnLock(); }
1246
1301
  }
1247
1302
 
1248
1303
  function parseSessionId(rawOut) {