gm-plugkit 2.0.1630 → 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 +1 -1
- package/plugkit-wasm-wrapper.js +61 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
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": {
|
package/plugkit-wasm-wrapper.js
CHANGED
|
@@ -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
|
|
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
|
|
777
|
+
const root = browserRootDir(cwd);
|
|
778
|
+
const gmDir = path.join(root, '.gm');
|
|
760
779
|
try { fs.mkdirSync(gmDir, { recursive: true }); } catch (_) {}
|
|
761
|
-
ensureGitignored(
|
|
762
|
-
ensureGitignored(
|
|
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) {
|