gm-plugkit 2.0.1503 → 2.0.1505
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 +81 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1505",
|
|
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
|
@@ -563,6 +563,31 @@ function browserStateDir(cwd) {
|
|
|
563
563
|
function browserPortsFile(cwd) { return path.join(browserStateDir(cwd), 'browser-ports.json'); }
|
|
564
564
|
function browserSessionsFile(cwd) { return path.join(browserStateDir(cwd), 'browser-sessions.json'); }
|
|
565
565
|
|
|
566
|
+
function selectIdleBrowserSessions(ports, now, limitMs) {
|
|
567
|
+
const idle = [];
|
|
568
|
+
if (!ports || typeof ports !== 'object') return idle;
|
|
569
|
+
for (const [sid, entry] of Object.entries(ports)) {
|
|
570
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
571
|
+
const lastUse = Number.isFinite(entry.lastUse) ? entry.lastUse : 0;
|
|
572
|
+
const idleMs = now - lastUse;
|
|
573
|
+
if (idleMs >= limitMs) idle.push({ sid, entry, idleMs });
|
|
574
|
+
}
|
|
575
|
+
return idle;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function stampBrowserLastUse(cwd, claudeSessionId) {
|
|
579
|
+
try {
|
|
580
|
+
const portsFile = browserPortsFile(cwd);
|
|
581
|
+
const ports = readJsonFile(portsFile, {});
|
|
582
|
+
const entry = ports[claudeSessionId];
|
|
583
|
+
if (entry && typeof entry === 'object') {
|
|
584
|
+
entry.lastUse = Date.now();
|
|
585
|
+
ports[claudeSessionId] = entry;
|
|
586
|
+
writeJsonFile(portsFile, ports);
|
|
587
|
+
}
|
|
588
|
+
} catch (_) {}
|
|
589
|
+
}
|
|
590
|
+
|
|
566
591
|
function atomicWriteJson(filePath, obj) {
|
|
567
592
|
const tmp = filePath + '.tmp.' + process.pid + '.' + Date.now() + '.' + Math.random().toString(36).slice(2, 8);
|
|
568
593
|
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2));
|
|
@@ -1020,6 +1045,7 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
1020
1045
|
const sid = parseSessionId(r.stdout || '');
|
|
1021
1046
|
if (sid) {
|
|
1022
1047
|
existing.pwSessionId = sid;
|
|
1048
|
+
existing.lastUse = Date.now();
|
|
1023
1049
|
ports[claudeSessionId] = existing;
|
|
1024
1050
|
sessions[claudeSessionId] = [sid];
|
|
1025
1051
|
writeJsonFile(portsFile, ports);
|
|
@@ -1092,7 +1118,7 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
1092
1118
|
logEvent('plugkit', 'browser.launch-failed', { reason: 'session-id-unparseable', stdout: r.stdout });
|
|
1093
1119
|
throw new Error(`could not parse managed browser session id from: ${scrubBrowserRunnerText(r.stdout || '')}`);
|
|
1094
1120
|
}
|
|
1095
|
-
ports[claudeSessionId] = { profileDir, pid: browserPid, port, wsEndpoint, pwSessionId };
|
|
1121
|
+
ports[claudeSessionId] = { profileDir, pid: browserPid, port, wsEndpoint, pwSessionId, lastUse: Date.now() };
|
|
1096
1122
|
sessions[claudeSessionId] = [pwSessionId];
|
|
1097
1123
|
writeJsonFile(portsFile, ports);
|
|
1098
1124
|
writeJsonFile(sessionsFile, sessions);
|
|
@@ -1833,7 +1859,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1833
1859
|
|
|
1834
1860
|
if (trimmed === 'session new' || trimmed === '') {
|
|
1835
1861
|
const pwSessionId = getOrCreateBrowserSession(cwd, sessionId, pw);
|
|
1836
|
-
|
|
1862
|
+
stampBrowserLastUse(cwd, sessionId);
|
|
1837
1863
|
return writeWasmJson(instanceRef.value, {
|
|
1838
1864
|
ok: true,
|
|
1839
1865
|
stdout: `Session ${pwSessionId} attached to locally-profiled chromium at ${path.join(cwd, '.gm', 'browser-profile')}`,
|
|
@@ -1845,12 +1871,26 @@ function makeHostFunctions(instanceRef) {
|
|
|
1845
1871
|
|
|
1846
1872
|
if (trimmed.startsWith('session ')) {
|
|
1847
1873
|
const parts = trimmed.slice(8).trim().split(/\s+/);
|
|
1848
|
-
// playwriter's actual session subcommands are list|new|delete|reset.
|
|
1849
|
-
// Map the legacy close/kill aliases (recognized here historically) to delete,
|
|
1850
|
-
// and pass list/new/delete/reset through verbatim. Anything else is rejected
|
|
1851
|
-
// by playwriter with its own usage text, which is the correct surface.
|
|
1852
1874
|
if (parts[0] === 'close' || parts[0] === 'kill') parts[0] = 'delete';
|
|
1853
1875
|
const r = runBrowserRunner(pw, ['session', ...parts], 30000, cwd, sessionId);
|
|
1876
|
+
if (r.status === 0 && (parts[0] === 'delete' || parts[0] === 'reset')) {
|
|
1877
|
+
try {
|
|
1878
|
+
const portsFile = browserPortsFile(cwd);
|
|
1879
|
+
const sessionsFile = browserSessionsFile(cwd);
|
|
1880
|
+
const ports = readJsonFile(portsFile, {});
|
|
1881
|
+
const sessions = readJsonFile(sessionsFile, {});
|
|
1882
|
+
const entry = ports[sessionId];
|
|
1883
|
+
if (entry && typeof entry === 'object') {
|
|
1884
|
+
if (Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid)) {
|
|
1885
|
+
gracefulCloseBrowser(entry, `session-${parts[0]}`);
|
|
1886
|
+
}
|
|
1887
|
+
delete ports[sessionId];
|
|
1888
|
+
delete sessions[sessionId];
|
|
1889
|
+
writeJsonFile(portsFile, ports);
|
|
1890
|
+
writeJsonFile(sessionsFile, sessions);
|
|
1891
|
+
}
|
|
1892
|
+
} catch (_) {}
|
|
1893
|
+
}
|
|
1854
1894
|
return writeWasmJson(instanceRef.value, {
|
|
1855
1895
|
ok: r.status === 0,
|
|
1856
1896
|
stdout: scrubBrowserRunnerText(r.stdout || ''),
|
|
@@ -1860,7 +1900,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1860
1900
|
}
|
|
1861
1901
|
|
|
1862
1902
|
const pwSessionId = getOrCreateBrowserSession(cwd, sessionId, pw);
|
|
1863
|
-
|
|
1903
|
+
stampBrowserLastUse(cwd, sessionId);
|
|
1864
1904
|
let evalBody = body;
|
|
1865
1905
|
let timeoutMs = 14000;
|
|
1866
1906
|
const timeoutMatch = body.match(/^timeout=(\d+)\s*\n([\s\S]*)$/);
|
|
@@ -2538,27 +2578,49 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2538
2578
|
}
|
|
2539
2579
|
}, 60_000);
|
|
2540
2580
|
|
|
2541
|
-
const BROWSER_IDLE_LIMIT_MS = parseInt(process.env.PLUGKIT_BROWSER_IDLE_LIMIT_MS, 10) ||
|
|
2542
|
-
let lastBrowserActivityMs = Date.now();
|
|
2581
|
+
const BROWSER_IDLE_LIMIT_MS = parseInt(process.env.PLUGKIT_BROWSER_IDLE_LIMIT_MS, 10) || 10 * 60 * 1000;
|
|
2543
2582
|
setInterval(() => {
|
|
2544
2583
|
try {
|
|
2545
|
-
const browserIdleMs = Date.now() - lastBrowserActivityMs;
|
|
2546
|
-
if (browserIdleMs < BROWSER_IDLE_LIMIT_MS) return;
|
|
2547
2584
|
const portsFile = browserPortsFile(process.cwd());
|
|
2548
2585
|
const sessionsFile = browserSessionsFile(process.cwd());
|
|
2549
2586
|
const ports = readJsonFile(portsFile, {});
|
|
2550
|
-
|
|
2587
|
+
const sessions = readJsonFile(sessionsFile, {});
|
|
2588
|
+
const now = Date.now();
|
|
2589
|
+
const idle = selectIdleBrowserSessions(ports, now, BROWSER_IDLE_LIMIT_MS);
|
|
2590
|
+
const idleSids = new Set(idle.map((x) => x.sid));
|
|
2591
|
+
let mutated = false;
|
|
2592
|
+
for (const { sid, entry, idleMs } of idle) {
|
|
2593
|
+
if (Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid)) {
|
|
2594
|
+
try { gracefulCloseBrowser(entry, 'browser-idle'); } catch (_) {}
|
|
2595
|
+
}
|
|
2596
|
+
delete ports[sid];
|
|
2597
|
+
delete sessions[sid];
|
|
2598
|
+
mutated = true;
|
|
2599
|
+
logEvent('plugkit', 'browser.idle-closed', { sid, pid: entry.pid || null, idle_ms: idleMs });
|
|
2600
|
+
}
|
|
2551
2601
|
for (const [sid, entry] of Object.entries(ports)) {
|
|
2552
|
-
if (entry
|
|
2553
|
-
|
|
2602
|
+
if (idleSids.has(sid) || !entry || typeof entry !== 'object') continue;
|
|
2603
|
+
const pidAlive = Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid);
|
|
2604
|
+
if (!pidAlive) {
|
|
2605
|
+
delete ports[sid];
|
|
2606
|
+
delete sessions[sid];
|
|
2607
|
+
mutated = true;
|
|
2608
|
+
logEvent('plugkit', 'browser.stale-reclaimed', { sid, pid: entry.pid || null, reason: 'pid-dead' });
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
const cdpOk = !!fetchJsonSync(`http://127.0.0.1:${entry.port}/json/version`, 1000);
|
|
2612
|
+
if (!cdpOk) {
|
|
2613
|
+
try { gracefulCloseBrowser(entry, 'orphan-cdp-dead'); } catch (_) {}
|
|
2614
|
+
delete ports[sid];
|
|
2615
|
+
delete sessions[sid];
|
|
2616
|
+
mutated = true;
|
|
2617
|
+
logEvent('plugkit', 'browser.stale-reclaimed', { sid, pid: entry.pid || null, reason: 'cdp-dead' });
|
|
2554
2618
|
}
|
|
2555
2619
|
}
|
|
2556
|
-
if (
|
|
2557
|
-
try {
|
|
2558
|
-
try {
|
|
2559
|
-
logEvent('plugkit', 'browser.idle-closed', { count: closed, idle_ms: browserIdleMs });
|
|
2620
|
+
if (mutated) {
|
|
2621
|
+
try { writeJsonFile(portsFile, ports); } catch (_) {}
|
|
2622
|
+
try { writeJsonFile(sessionsFile, sessions); } catch (_) {}
|
|
2560
2623
|
}
|
|
2561
|
-
lastBrowserActivityMs = Date.now();
|
|
2562
2624
|
} catch (e) {
|
|
2563
2625
|
console.error(`[browser-idle] error: ${e.message}`);
|
|
2564
2626
|
}
|