gm-plugkit 2.0.1518 → 2.0.1520
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/bootstrap.js +16 -1
- package/package.json +1 -1
- package/plugkit-wasm-wrapper.js +103 -8
package/bootstrap.js
CHANGED
|
@@ -665,6 +665,20 @@ function ensureWrapperFresh() {
|
|
|
665
665
|
} catch (_) { return false; }
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
+
function ensureGmPlugkitVersionFresh() {
|
|
669
|
+
try {
|
|
670
|
+
const ownPkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
671
|
+
if (!ownPkg || !ownPkg.version) return false;
|
|
672
|
+
const dst = path.join(gmToolsDir(), 'gm-plugkit.version');
|
|
673
|
+
let cur = null;
|
|
674
|
+
try { cur = fs.readFileSync(dst, 'utf-8').trim(); } catch (_) {}
|
|
675
|
+
if (cur === ownPkg.version) return false;
|
|
676
|
+
fs.mkdirSync(gmToolsDir(), { recursive: true });
|
|
677
|
+
fs.writeFileSync(dst, ownPkg.version);
|
|
678
|
+
return true;
|
|
679
|
+
} catch (_) { return false; }
|
|
680
|
+
}
|
|
681
|
+
|
|
668
682
|
function ensureSkillMdFresh() {
|
|
669
683
|
try {
|
|
670
684
|
const candidates = [
|
|
@@ -820,8 +834,9 @@ async function ensureReady(opts) {
|
|
|
820
834
|
if (isReady() && !versionDrift) {
|
|
821
835
|
const wasmPath = getWasmPath();
|
|
822
836
|
const wrapperUpdated = ensureWrapperFresh();
|
|
837
|
+
const versionMarkerUpdated = ensureGmPlugkitVersionFresh();
|
|
823
838
|
ensureSkillMdFresh();
|
|
824
|
-
return { ok: true, wasmPath, binaryPath: wasmPath, status: wrapperUpdated ? 'wrapper-refreshed' : 'already-ready', version: installed };
|
|
839
|
+
return { ok: true, wasmPath, binaryPath: wasmPath, status: (wrapperUpdated || versionMarkerUpdated) ? 'wrapper-refreshed' : 'already-ready', version: installed };
|
|
825
840
|
}
|
|
826
841
|
if (versionDrift) {
|
|
827
842
|
try { killRunningDaemons(`version_drift:${installed}->${targetVersion}`); } catch (_) {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1520",
|
|
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
|
@@ -807,6 +807,49 @@ function cleanDeadProfileFragments(cwd) {
|
|
|
807
807
|
}
|
|
808
808
|
}
|
|
809
809
|
|
|
810
|
+
function parsePlaywriterSessionList(stdout) {
|
|
811
|
+
const rows = [];
|
|
812
|
+
if (!stdout) return rows;
|
|
813
|
+
const lines = stdout.split(/\r?\n/);
|
|
814
|
+
for (const line of lines) {
|
|
815
|
+
const m = line.match(/^\s*(\d+)\s+\S+\s+\S+\s+\S+\s+(\S+)/);
|
|
816
|
+
if (!m) continue;
|
|
817
|
+
const id = m[1];
|
|
818
|
+
let cwd = m[2];
|
|
819
|
+
if (cwd === '-') cwd = '';
|
|
820
|
+
rows.push({ id, cwd });
|
|
821
|
+
}
|
|
822
|
+
return rows;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function reapOrphanBrowserSessions(pw, cwd, claudeSessionId, reason) {
|
|
826
|
+
try {
|
|
827
|
+
const ports = readJsonFile(browserPortsFile(cwd), {});
|
|
828
|
+
const activeIds = new Set();
|
|
829
|
+
for (const ent of Object.values(ports)) {
|
|
830
|
+
if (ent && ent.pwSessionId) activeIds.add(String(ent.pwSessionId));
|
|
831
|
+
}
|
|
832
|
+
const r = runBrowserRunner(pw, ['session', 'list'], 15000, cwd, claudeSessionId);
|
|
833
|
+
if (!r || r.status !== 0) return { reaped: 0 };
|
|
834
|
+
const rows = parsePlaywriterSessionList(r.stdout || '');
|
|
835
|
+
const norm = (p) => String(p || '').replace(/[\\/]+$/, '').toLowerCase();
|
|
836
|
+
const wantCwd = norm(cwd);
|
|
837
|
+
let reaped = 0;
|
|
838
|
+
for (const { id, cwd: rowCwd } of rows) {
|
|
839
|
+
if (rowCwd && norm(rowCwd) !== wantCwd) continue;
|
|
840
|
+
if (activeIds.has(String(id))) continue;
|
|
841
|
+
const d = runBrowserRunner(pw, ['session', 'delete', id], 15000, cwd, claudeSessionId);
|
|
842
|
+
if (d && d.status === 0) {
|
|
843
|
+
reaped++;
|
|
844
|
+
try { logEvent('plugkit', 'browser.orphan-session-reaped', { session_id: id, reason: reason || 'boot', cwd }); } catch (_) {}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return { reaped };
|
|
848
|
+
} catch (_) {
|
|
849
|
+
return { reaped: 0 };
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
810
853
|
function resolveWindowsExeLocal(cmd) {
|
|
811
854
|
if (process.platform !== 'win32') return cmd;
|
|
812
855
|
try {
|
|
@@ -828,7 +871,7 @@ function resolveWindowsExeLocal(cmd) {
|
|
|
828
871
|
|
|
829
872
|
function isPortReachableSync(host, port, timeoutMs) {
|
|
830
873
|
const r = spawnSync(process.execPath, ['-e', `
|
|
831
|
-
const net =
|
|
874
|
+
const net = require('net');
|
|
832
875
|
const s = net.connect({ port: ${port}, host: ${JSON.stringify(host)} });
|
|
833
876
|
let done = false;
|
|
834
877
|
s.on('connect', () => { done = true; s.destroy(); process.exit(0); });
|
|
@@ -840,7 +883,7 @@ function isPortReachableSync(host, port, timeoutMs) {
|
|
|
840
883
|
|
|
841
884
|
function findFreePortSync() {
|
|
842
885
|
const r = spawnSync(process.execPath, ['-e', `
|
|
843
|
-
const net =
|
|
886
|
+
const net = require('net');
|
|
844
887
|
const srv = net.createServer();
|
|
845
888
|
srv.listen(0, '127.0.0.1', () => { const p = srv.address().port; srv.close(() => { process.stdout.write(String(p)); }); });
|
|
846
889
|
srv.on('error', e => { process.stderr.write(e.message); process.exit(1); });
|
|
@@ -851,7 +894,7 @@ function findFreePortSync() {
|
|
|
851
894
|
|
|
852
895
|
function isPortAliveSync(port) {
|
|
853
896
|
const r = spawnSync(process.execPath, ['-e', `
|
|
854
|
-
const net =
|
|
897
|
+
const net = require('net');
|
|
855
898
|
const s = net.connect({ port: ${port}, host: '127.0.0.1' });
|
|
856
899
|
s.on('connect', () => { s.destroy(); process.exit(0); });
|
|
857
900
|
s.on('error', () => process.exit(1));
|
|
@@ -949,7 +992,7 @@ function findInstalledChromiumBinary() {
|
|
|
949
992
|
|
|
950
993
|
function fetchJsonSync(url, timeoutMs) {
|
|
951
994
|
const r = spawnSync(process.execPath, ['-e', `
|
|
952
|
-
const http =
|
|
995
|
+
const http = require('http');
|
|
953
996
|
const req = http.get(${JSON.stringify(url)}, (res) => {
|
|
954
997
|
let buf = '';
|
|
955
998
|
res.on('data', d => buf += d);
|
|
@@ -1057,7 +1100,7 @@ function gracefulCloseBrowser(entry, reason) {
|
|
|
1057
1100
|
const info = fetchJsonSync(`http://127.0.0.1:${port}/json/version`, 600);
|
|
1058
1101
|
if (info && info.webSocketDebuggerUrl) {
|
|
1059
1102
|
spawnSync(process.execPath, ['-e', `
|
|
1060
|
-
const http =
|
|
1103
|
+
const http = require('http');
|
|
1061
1104
|
const req = http.request({host:'127.0.0.1',port:${port},path:'/json/close/browser',method:'GET',timeout:1500},
|
|
1062
1105
|
res => { res.resume(); res.on('end', () => process.exit(0)); });
|
|
1063
1106
|
req.on('error', () => process.exit(1));
|
|
@@ -1137,6 +1180,7 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
1137
1180
|
}
|
|
1138
1181
|
}
|
|
1139
1182
|
cleanDeadProfileFragments(cwd);
|
|
1183
|
+
reapOrphanBrowserSessions(pw, cwd, claudeSessionId, 'pre-spawn');
|
|
1140
1184
|
const profileDir = acquireProfileDir(cwd, claudeSessionId);
|
|
1141
1185
|
const aliveCdpForProfile = (() => {
|
|
1142
1186
|
for (const key of Object.keys(ports)) {
|
|
@@ -2085,6 +2129,8 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2085
2129
|
fs.writeFileSync(path.join(gmDir, 'long-gap-retry-state'), '');
|
|
2086
2130
|
} catch (_) {}
|
|
2087
2131
|
|
|
2132
|
+
try { reapOrphanBrowserSessions(findBrowserRunner(), process.cwd(), process.env.CLAUDE_SESSION_ID || 'claude-loop-iter', 'watcher-boot'); } catch (_) {}
|
|
2133
|
+
|
|
2088
2134
|
|
|
2089
2135
|
const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
|
|
2090
2136
|
try {
|
|
@@ -2369,8 +2415,33 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2369
2415
|
}
|
|
2370
2416
|
const latest = JSON.parse(body).version;
|
|
2371
2417
|
const stalePath = path.join(spoolDir, '.gm-plugkit-stale.json');
|
|
2418
|
+
const respawnGuardPath = path.join(spoolDir, '.gm-plugkit-respawn-guard.json');
|
|
2372
2419
|
if (!latest || latest === own) {
|
|
2373
2420
|
if (fs.existsSync(stalePath)) { try { fs.unlinkSync(stalePath); } catch (_) {} }
|
|
2421
|
+
if (fs.existsSync(respawnGuardPath)) { try { fs.unlinkSync(respawnGuardPath); } catch (_) {} }
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
let respawnGuard = { attempts: 0, last_own: null, last_latest: null, first_ts: Date.now() };
|
|
2425
|
+
try {
|
|
2426
|
+
if (fs.existsSync(respawnGuardPath)) respawnGuard = JSON.parse(fs.readFileSync(respawnGuardPath, 'utf8'));
|
|
2427
|
+
} catch (_) {}
|
|
2428
|
+
const sameStaleAsBefore = respawnGuard.last_own === own && respawnGuard.last_latest === latest;
|
|
2429
|
+
const cameFromSelfRespawn = process.env.PLUGKIT_BOOT_REASON === 'self-respawn-from-self-stale';
|
|
2430
|
+
if (sameStaleAsBefore && respawnGuard.attempts >= 3) {
|
|
2431
|
+
try { fs.writeFileSync(stalePath, JSON.stringify({
|
|
2432
|
+
ts: new Date().toISOString(),
|
|
2433
|
+
reason: 'gm-plugkit-self-stale-respawn-exhausted',
|
|
2434
|
+
running_version: own,
|
|
2435
|
+
latest_version: latest,
|
|
2436
|
+
respawn_attempts: respawnGuard.attempts,
|
|
2437
|
+
instruction: `gm-plugkit ${own} cannot self-upgrade to ${latest}: ${respawnGuard.attempts} respawns all came up ${own} (bun/npx cache is serving the stale tarball). Respawn loop halted to keep this watcher alive and serving verbs. Fix manually: bun pm cache rm; npm cache clean --force; rm -rf ~/AppData/Local/npm-cache/_npx ~/.bun/install/cache; then bun x gm-plugkit@latest --kill-stale-watchers; bun x gm-plugkit@latest spool`,
|
|
2438
|
+
detected_by: 'watcher-periodic-probe',
|
|
2439
|
+
}, null, 2)); } catch (_) {}
|
|
2440
|
+
if (!_selfStaleLoggedOnce) {
|
|
2441
|
+
_selfStaleLoggedOnce = true;
|
|
2442
|
+
try { logEvent('plugkit', 'gm-plugkit.self-stale-respawn-exhausted', { running_version: own, latest_version: latest, attempts: respawnGuard.attempts }); } catch (_) {}
|
|
2443
|
+
console.error(`[plugkit-wasm] gm-plugkit self-stale respawn EXHAUSTED after ${respawnGuard.attempts} attempts (cache serving stale ${own} for latest ${latest}); halting respawn loop and staying alive to serve verbs`);
|
|
2444
|
+
}
|
|
2374
2445
|
return;
|
|
2375
2446
|
}
|
|
2376
2447
|
const marker = {
|
|
@@ -2389,6 +2460,25 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2389
2460
|
try {
|
|
2390
2461
|
const cp = _childProcess;
|
|
2391
2462
|
const bunPath = process.env.GM_BUN_PATH || 'bun';
|
|
2463
|
+
const bustCache = sameStaleAsBefore || cameFromSelfRespawn;
|
|
2464
|
+
if (bustCache) {
|
|
2465
|
+
try { cp.execFileSync(bunPath, ['pm', 'cache', 'rm'], { stdio: 'ignore', timeout: 30000, windowsHide: true }); } catch (_) {}
|
|
2466
|
+
try {
|
|
2467
|
+
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
2468
|
+
for (const rel of ['AppData/Local/npm-cache/_npx', '.npm/_npx', '.bun/install/cache']) {
|
|
2469
|
+
try { fs.rmSync(path.join(home, rel), { recursive: true, force: true }); } catch (_) {}
|
|
2470
|
+
}
|
|
2471
|
+
} catch (_) {}
|
|
2472
|
+
try { logEvent('plugkit', 'gm-plugkit.self-stale-cache-busted', { running_version: own, latest_version: latest, attempt: (respawnGuard.attempts || 0) + 1 }); } catch (_) {}
|
|
2473
|
+
}
|
|
2474
|
+
try { fs.writeFileSync(respawnGuardPath, JSON.stringify({
|
|
2475
|
+
attempts: (sameStaleAsBefore ? (respawnGuard.attempts || 0) : 0) + 1,
|
|
2476
|
+
last_own: own,
|
|
2477
|
+
last_latest: latest,
|
|
2478
|
+
first_ts: respawnGuard.first_ts || Date.now(),
|
|
2479
|
+
last_ts: Date.now(),
|
|
2480
|
+
cache_busted: bustCache,
|
|
2481
|
+
}, null, 2)); } catch (_) {}
|
|
2392
2482
|
const child = cp.spawn(bunPath, ['x', `gm-plugkit@${latest}`, 'spool'], {
|
|
2393
2483
|
cwd: process.cwd(),
|
|
2394
2484
|
detached: true,
|
|
@@ -2397,7 +2487,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2397
2487
|
env: { ...process.env, PLUGKIT_BOOT_REASON: 'self-respawn-from-self-stale' },
|
|
2398
2488
|
});
|
|
2399
2489
|
child.unref();
|
|
2400
|
-
try { logEvent('plugkit', 'gm-plugkit.self-stale-respawn', { running_version: own, latest_version: latest }); } catch (_) {}
|
|
2490
|
+
try { logEvent('plugkit', 'gm-plugkit.self-stale-respawn', { running_version: own, latest_version: latest, cache_busted: bustCache, attempt: (respawnGuard.attempts || 0) + 1 }); } catch (_) {}
|
|
2401
2491
|
try { fs.writeFileSync(path.join(spoolDir, '.shutdown-reason.json'), JSON.stringify({ reason: 'gm-plugkit-self-stale', ts: Date.now(), pid: process.pid, running_version: own, latest_version: latest })); } catch (_) {}
|
|
2402
2492
|
// Wait for the replacement's fresh heartbeat before exiting (mirror the
|
|
2403
2493
|
// version-drift path) instead of a blind 2s exit: the gm-plugkit download can
|
|
@@ -2406,13 +2496,17 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2406
2496
|
const myPid = process.pid;
|
|
2407
2497
|
const respawnDeadline = Date.now() + 90000;
|
|
2408
2498
|
const exitSelfStale = () => { try { process.exit(0); } catch (_) {} };
|
|
2499
|
+
const ownVersionFile = path.join(GM_TOOLS_ROOT, 'gm-plugkit.version');
|
|
2409
2500
|
const pollSelfStaleReplacement = () => {
|
|
2410
2501
|
try {
|
|
2411
2502
|
const st = JSON.parse(fs.readFileSync(STATUS_PATH_FOR_TEARDOWN, 'utf8'));
|
|
2412
2503
|
const freshHeartbeat = st && st.ts && (Date.now() - st.ts) < 15000;
|
|
2413
2504
|
const differentProc = st && st.pid && st.pid !== myPid;
|
|
2414
|
-
|
|
2415
|
-
|
|
2505
|
+
let replacementOnLatest = false;
|
|
2506
|
+
try { replacementOnLatest = fs.readFileSync(ownVersionFile, 'utf-8').trim() === latest; } catch (_) {}
|
|
2507
|
+
if (freshHeartbeat && differentProc && replacementOnLatest) {
|
|
2508
|
+
try { fs.unlinkSync(respawnGuardPath); } catch (_) {}
|
|
2509
|
+
try { logEvent('plugkit', 'gm-plugkit.self-stale-respawn-confirmed', { old_pid: myPid, new_pid: st.pid, new_version: st.version, latest_version: latest, replacement_gm_plugkit: latest }); } catch (_) {}
|
|
2416
2510
|
return exitSelfStale();
|
|
2417
2511
|
}
|
|
2418
2512
|
} catch (_) {}
|
|
@@ -2674,6 +2768,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2674
2768
|
try { writeJsonFile(portsFile, ports); } catch (_) {}
|
|
2675
2769
|
try { writeJsonFile(sessionsFile, sessions); } catch (_) {}
|
|
2676
2770
|
}
|
|
2771
|
+
try { reapOrphanBrowserSessions(findBrowserRunner(), process.cwd(), process.env.CLAUDE_SESSION_ID || 'claude-loop-iter', 'idle-sweep'); } catch (_) {}
|
|
2677
2772
|
} catch (e) {
|
|
2678
2773
|
console.error(`[browser-idle] error: ${e.message}`);
|
|
2679
2774
|
}
|