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 +1 -1
- package/plugkit-wasm-wrapper.js +74 -72
- package/plugkit.version +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
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": {
|
package/plugkit-wasm-wrapper.js
CHANGED
|
@@ -605,12 +605,16 @@ function stampBrowserLastUse(cwd, claudeSessionId) {
|
|
|
605
605
|
} catch (_) {}
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
function
|
|
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,
|
|
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
|
|
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
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
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(
|
|
1381
|
-
const ent =
|
|
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
|
-
|
|
1413
|
-
|
|
1414
|
-
writeJsonFile(portsFile,
|
|
1415
|
-
writeJsonFile(sessionsFile,
|
|
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
|
-
|
|
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
|
-
|
|
2829
|
+
atomicWriteJson(PEER_REGISTRY_PATH, reg);
|
|
2828
2830
|
} catch (_) {}
|
|
2829
2831
|
}
|
|
2830
2832
|
registerSelfAsPeer();
|
package/plugkit.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.733
|