gm-plugkit 2.0.1680 → 2.0.1682

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.1680",
3
+ "version": "2.0.1682",
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": {
@@ -605,12 +605,16 @@ function stampBrowserLastUse(cwd, claudeSessionId) {
605
605
  } catch (_) {}
606
606
  }
607
607
 
608
- function atomicWriteJson(filePath, obj) {
608
+ function atomicWriteRaw(filePath, data) {
609
609
  const tmp = filePath + '.tmp.' + process.pid + '.' + Date.now() + '.' + Math.random().toString(36).slice(2, 8);
610
- fs.writeFileSync(tmp, JSON.stringify(obj, null, 2));
610
+ fs.writeFileSync(tmp, data);
611
611
  fs.renameSync(tmp, filePath);
612
612
  }
613
613
 
614
+ function atomicWriteJson(filePath, obj) {
615
+ atomicWriteRaw(filePath, JSON.stringify(obj, null, 2));
616
+ }
617
+
614
618
  function migrateLegacyBrowserState(cwd) {
615
619
  const dst1 = browserPortsFile(cwd);
616
620
  const dst2 = browserSessionsFile(cwd);
@@ -1279,65 +1283,66 @@ function gracefulCloseBrowser(entry, reason) {
1279
1283
  try { logEvent('plugkit', 'browser.closed', { reason: reason || 'closed', pid, port, profileDir }); } catch (_) {}
1280
1284
  }
1281
1285
 
1282
- function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1283
- migrateLegacyBrowserState(cwd);
1284
- const portsFile = browserPortsFile(cwd);
1285
- const sessionsFile = browserSessionsFile(cwd);
1286
- const ports = readJsonFile(portsFile, {});
1287
- const sessions = readJsonFile(sessionsFile, {});
1286
+ function resolveExistingBrowserEntry(cwd, claudeSessionId, pw, portsFile, sessionsFile, ports, sessions) {
1288
1287
  const existing = ports[claudeSessionId];
1289
- if (existing && existing.pid && existing.wsEndpoint) {
1290
- const wantProfile = sessionProfileDir(cwd, claudeSessionId);
1291
- const pidOk = isProcessAliveSync(existing.pid);
1292
- const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(wantProfile);
1293
- const cdpOk = pidOk && !!fetchJsonSync(`http://127.0.0.1:${existing.port}/json/version`, 1000);
1294
- if (pidOk && profileOk && cdpOk) {
1295
- const pwIds = sessions[claudeSessionId] || [];
1296
- if (pwIds.length > 0 && existing.pwSessionId) return existing.pwSessionId;
1297
- const r = runBrowserRunner(pw, ['session', 'new', '--direct', existing.wsEndpoint], 30000, cwd, claudeSessionId);
1298
- if (r && r.status === 0) {
1299
- const sid = parseSessionId(r.stdout || '');
1300
- if (sid) {
1301
- existing.pwSessionId = sid;
1302
- existing.lastUse = Date.now();
1303
- ports[claudeSessionId] = existing;
1304
- sessions[claudeSessionId] = [sid];
1305
- writeJsonFile(portsFile, ports);
1306
- writeJsonFile(sessionsFile, sessions);
1307
- logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true });
1308
- return sid;
1309
- }
1310
- }
1311
- } else {
1312
- const reason = !pidOk ? 'pid-dead' : (!cdpOk ? 'cdp-dead' : 'profile-drift');
1313
- if (reason === 'pid-dead') {
1314
- logEvent('plugkit', 'browser.stale-reclaimed', {
1315
- sid: claudeSessionId,
1316
- stale_pid: existing.pid || null,
1317
- stale_profile: existing.profileDir || null,
1318
- want_profile: wantProfile,
1319
- });
1320
- } else {
1321
- logEvent('hook', 'deviation.browser-profile-collision', {
1322
- sid: claudeSessionId,
1323
- stale_pid: existing.pid || null,
1324
- stale_profile: existing.profileDir || null,
1325
- want_profile: wantProfile,
1326
- reason,
1327
- });
1328
- }
1329
- if (typeof gracefulCloseBrowser === 'function') {
1330
- try { gracefulCloseBrowser(existing, `collision:${reason}`); } catch (_) {}
1331
- } else if (pidOk && Number.isFinite(existing.pid)) {
1332
- try { killPidQuiet(existing.pid); } catch (_) {}
1288
+ if (!(existing && existing.pid && existing.wsEndpoint)) return null;
1289
+ const wantProfile = sessionProfileDir(cwd, claudeSessionId);
1290
+ const pidOk = isProcessAliveSync(existing.pid);
1291
+ const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(wantProfile);
1292
+ const cdpOk = pidOk && !!fetchJsonSync(`http://127.0.0.1:${existing.port}/json/version`, 1000);
1293
+ if (pidOk && profileOk && cdpOk) {
1294
+ const pwIds = sessions[claudeSessionId] || [];
1295
+ if (pwIds.length > 0 && existing.pwSessionId) return existing.pwSessionId;
1296
+ const r = runBrowserRunner(pw, ['session', 'new', '--direct', existing.wsEndpoint], 30000, cwd, claudeSessionId);
1297
+ if (r && r.status === 0) {
1298
+ const sid = parseSessionId(r.stdout || '');
1299
+ if (sid) {
1300
+ existing.pwSessionId = sid;
1301
+ existing.lastUse = Date.now();
1302
+ ports[claudeSessionId] = existing;
1303
+ sessions[claudeSessionId] = [sid];
1304
+ writeJsonFile(portsFile, ports);
1305
+ writeJsonFile(sessionsFile, sessions);
1306
+ logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true });
1307
+ return sid;
1333
1308
  }
1334
- purgeProfileLockFiles(existing.profileDir);
1335
- delete ports[claudeSessionId];
1336
- delete sessions[claudeSessionId];
1337
- try { writeJsonFile(portsFile, ports); } catch (_) {}
1338
- try { writeJsonFile(sessionsFile, sessions); } catch (_) {}
1339
1309
  }
1310
+ return null;
1311
+ }
1312
+ const reason = !pidOk ? 'pid-dead' : (!cdpOk ? 'cdp-dead' : 'profile-drift');
1313
+ if (reason === 'pid-dead') {
1314
+ logEvent('plugkit', 'browser.stale-reclaimed', {
1315
+ sid: claudeSessionId,
1316
+ stale_pid: existing.pid || null,
1317
+ stale_profile: existing.profileDir || null,
1318
+ want_profile: wantProfile,
1319
+ });
1320
+ } else {
1321
+ logEvent('hook', 'deviation.browser-profile-collision', {
1322
+ sid: claudeSessionId,
1323
+ stale_pid: existing.pid || null,
1324
+ stale_profile: existing.profileDir || null,
1325
+ want_profile: wantProfile,
1326
+ reason,
1327
+ });
1340
1328
  }
1329
+ if (typeof gracefulCloseBrowser === 'function') {
1330
+ try { gracefulCloseBrowser(existing, `collision:${reason}`); } catch (_) {}
1331
+ } else if (pidOk && Number.isFinite(existing.pid)) {
1332
+ try { killPidQuiet(existing.pid); } catch (_) {}
1333
+ }
1334
+ purgeProfileLockFiles(existing.profileDir);
1335
+ delete ports[claudeSessionId];
1336
+ delete sessions[claudeSessionId];
1337
+ try { writeJsonFile(portsFile, ports); } catch (_) {}
1338
+ try { writeJsonFile(sessionsFile, sessions); } catch (_) {}
1339
+ return null;
1340
+ }
1341
+
1342
+ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1343
+ migrateLegacyBrowserState(cwd);
1344
+ const portsFile = browserPortsFile(cwd);
1345
+ const sessionsFile = browserSessionsFile(cwd);
1341
1346
  const spawnLock = path.join(browserStateDir(cwd), `.browser-spawn-${sessionProfileSlug(claudeSessionId)}.lock`);
1342
1347
  let lockFd = null;
1343
1348
  const spawnDeadline = Date.now() + 35000;
@@ -1366,19 +1371,16 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1366
1371
  try { if (lockFd !== null) { fs.writeSync(lockFd, `${process.pid}|${Date.now()}`); fs.closeSync(lockFd); } } catch (_) {}
1367
1372
  const releaseSpawnLock = () => { try { const o = parseInt(String(fs.readFileSync(spawnLock, 'utf-8')).split('|')[0], 10); if (o === process.pid) fs.unlinkSync(spawnLock); } catch (_) {} };
1368
1373
  try {
1369
- const winner2 = readJsonFile(portsFile, {})[claudeSessionId];
1370
- if (winner2 && winner2.pid && winner2.wsEndpoint && isProcessAliveSync(winner2.pid)
1371
- && fetchJsonSync(`http://127.0.0.1:${winner2.port}/json/version`, 1000)) {
1372
- const a = runBrowserRunner(pw, ['session', 'new', '--direct', winner2.wsEndpoint], 30000, cwd, claudeSessionId);
1373
- const sid = a && a.status === 0 ? parseSessionId(a.stdout || '') : null;
1374
- if (sid) { logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true, via: 'spawn-lock-recheck' }); return sid; }
1375
- }
1374
+ const portsLocked = readJsonFile(portsFile, {});
1375
+ const sessionsLocked = readJsonFile(sessionsFile, {});
1376
+ const reused = resolveExistingBrowserEntry(cwd, claudeSessionId, pw, portsFile, sessionsFile, portsLocked, sessionsLocked);
1377
+ if (reused) { logEvent('plugkit', 'browser.attached', { pwSessionId: reused, reused: true, via: 'spawn-lock-recheck' }); return reused; }
1376
1378
  cleanDeadProfileFragments(cwd);
1377
1379
  reapOrphanBrowserSessions(pw, cwd, claudeSessionId, 'pre-spawn');
1378
1380
  const profileDir = acquireProfileDir(cwd, claudeSessionId);
1379
1381
  const aliveCdpForProfile = (() => {
1380
- for (const key of Object.keys(ports)) {
1381
- const ent = ports[key];
1382
+ for (const key of Object.keys(portsLocked)) {
1383
+ const ent = portsLocked[key];
1382
1384
  if (!ent || !ent.pid || !ent.port || !ent.wsEndpoint) continue;
1383
1385
  if (ent.profileDir !== profileDir && !(ent.profileDir || '').startsWith(profileDir)) continue;
1384
1386
  if (!isProcessAliveSync(ent.pid)) continue;
@@ -1409,10 +1411,10 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
1409
1411
  logEvent('plugkit', 'browser.launch-failed', { reason: 'session-id-unparseable', stdout: r.stdout });
1410
1412
  throw new Error(`could not parse managed browser session id from: ${scrubBrowserRunnerText(r.stdout || '')}`);
1411
1413
  }
1412
- ports[claudeSessionId] = { profileDir, pid: browserPid, port, wsEndpoint, pwSessionId, lastUse: Date.now() };
1413
- sessions[claudeSessionId] = [pwSessionId];
1414
- writeJsonFile(portsFile, ports);
1415
- writeJsonFile(sessionsFile, sessions);
1414
+ portsLocked[claudeSessionId] = { profileDir, pid: browserPid, port, wsEndpoint, pwSessionId, lastUse: Date.now() };
1415
+ sessionsLocked[claudeSessionId] = [pwSessionId];
1416
+ writeJsonFile(portsFile, portsLocked);
1417
+ writeJsonFile(sessionsFile, sessionsLocked);
1416
1418
  clearLaunching(browserPid);
1417
1419
  if (!__openedSessionIds.has(claudeSessionId) && __openedSessionIds.size >= 1) {
1418
1420
  logEvent('hook', 'deviation.browser-multi-session', { sid: claudeSessionId, already_open: Array.from(__openedSessionIds), reason: 'a 2nd distinct browser sessionId launched its own chromium this run -- reuse one session per run and close it when done' });
@@ -1945,7 +1947,7 @@ function makeHostFunctions(instanceRef) {
1945
1947
  const key = readWasmStr(instanceRef.value, keyPtr, keyLen);
1946
1948
  const val = readWasmStr(instanceRef.value, valPtr, valLen);
1947
1949
  if (!ns || !key) return 0;
1948
- fs.writeFileSync(kvFilePath(ns, key, true), val);
1950
+ atomicWriteRaw(kvFilePath(ns, key, true), val);
1949
1951
  return 1;
1950
1952
  } catch (e) {
1951
1953
  return 0;
@@ -2824,7 +2826,7 @@ async function runSpoolWatcher(instance, spoolDir) {
2824
2826
  let reg = {};
2825
2827
  try { reg = JSON.parse(fs.readFileSync(PEER_REGISTRY_PATH, 'utf-8')); } catch (_) {}
2826
2828
  reg[process.cwd()] = { pid: process.pid, ts: Date.now(), sha: _ownWrapperSha12 };
2827
- fs.writeFileSync(PEER_REGISTRY_PATH, JSON.stringify(reg, null, 2));
2829
+ atomicWriteJson(PEER_REGISTRY_PATH, reg);
2828
2830
  } catch (_) {}
2829
2831
  }
2830
2832
  registerSelfAsPeer();
package/plugkit.version CHANGED
@@ -1 +1 @@
1
- 0.1.732
1
+ 0.1.733