gm-plugkit 2.0.1308 → 2.0.1310
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 +140 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1310",
|
|
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
|
@@ -813,6 +813,55 @@ function startManagedBrowser(pw, profileDir) {
|
|
|
813
813
|
return { pid, port, wsEndpoint };
|
|
814
814
|
}
|
|
815
815
|
|
|
816
|
+
function killPidQuiet(pid) {
|
|
817
|
+
if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return false;
|
|
818
|
+
try { process.kill(pid, 'SIGTERM'); } catch (_) {}
|
|
819
|
+
if (process.platform === 'win32') {
|
|
820
|
+
try { spawnSync('taskkill', ['/F', '/T', '/PID', String(pid)], { stdio: 'ignore', timeout: 3000 }); } catch (_) {}
|
|
821
|
+
}
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function purgeProfileLockFiles(profileDir) {
|
|
826
|
+
if (!profileDir) return;
|
|
827
|
+
for (const name of ['SingletonLock', 'SingletonCookie', 'SingletonSocket']) {
|
|
828
|
+
try { fs.unlinkSync(path.join(profileDir, name)); } catch (_) {}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function sleepSyncMs(ms) {
|
|
833
|
+
if (ms <= 0) return;
|
|
834
|
+
spawnSync(process.execPath, ['-e', `setTimeout(()=>process.exit(0),${ms})`], { timeout: ms + 500, windowsHide: true });
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function gracefulCloseBrowser(entry, reason) {
|
|
838
|
+
if (!entry) return;
|
|
839
|
+
const { pid, port, profileDir } = entry;
|
|
840
|
+
if (Number.isFinite(port) && port > 0) {
|
|
841
|
+
try {
|
|
842
|
+
const info = fetchJsonSync(`http://127.0.0.1:${port}/json/version`, 600);
|
|
843
|
+
if (info && info.webSocketDebuggerUrl) {
|
|
844
|
+
spawnSync(process.execPath, ['-e', `
|
|
845
|
+
const http = require('http');
|
|
846
|
+
const req = http.request({host:'127.0.0.1',port:${port},path:'/json/close/browser',method:'GET',timeout:1500},
|
|
847
|
+
res => { res.resume(); res.on('end', () => process.exit(0)); });
|
|
848
|
+
req.on('error', () => process.exit(1));
|
|
849
|
+
req.on('timeout', () => { req.destroy(); process.exit(1); });
|
|
850
|
+
req.end();
|
|
851
|
+
`], { timeout: 3000, windowsHide: true });
|
|
852
|
+
}
|
|
853
|
+
} catch (_) {}
|
|
854
|
+
}
|
|
855
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
856
|
+
const deadline = Date.now() + 1500;
|
|
857
|
+
try { process.kill(pid, 'SIGTERM'); } catch (_) {}
|
|
858
|
+
while (Date.now() < deadline && isProcessAliveSync(pid)) sleepSyncMs(Math.min(150, deadline - Date.now()));
|
|
859
|
+
if (isProcessAliveSync(pid)) killPidQuiet(pid);
|
|
860
|
+
}
|
|
861
|
+
purgeProfileLockFiles(profileDir);
|
|
862
|
+
try { logEvent('plugkit', 'browser.closed', { reason: reason || 'closed', pid, port, profileDir }); } catch (_) {}
|
|
863
|
+
}
|
|
864
|
+
|
|
816
865
|
function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
817
866
|
migrateLegacyBrowserState(cwd);
|
|
818
867
|
const portsFile = browserPortsFile(cwd);
|
|
@@ -850,6 +899,12 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
850
899
|
want_profile: wantProfile,
|
|
851
900
|
reason,
|
|
852
901
|
});
|
|
902
|
+
if (typeof gracefulCloseBrowser === 'function') {
|
|
903
|
+
try { gracefulCloseBrowser(existing, `collision:${reason}`); } catch (_) {}
|
|
904
|
+
} else if (pidOk && Number.isFinite(existing.pid)) {
|
|
905
|
+
try { killPidQuiet(existing.pid); } catch (_) {}
|
|
906
|
+
}
|
|
907
|
+
purgeProfileLockFiles(existing.profileDir);
|
|
853
908
|
delete ports[claudeSessionId];
|
|
854
909
|
delete sessions[claudeSessionId];
|
|
855
910
|
try { writeJsonFile(portsFile, ports); } catch (_) {}
|
|
@@ -1560,6 +1615,15 @@ function makeHostFunctions(instanceRef) {
|
|
|
1560
1615
|
const prefix = level >= 3 ? '[plugkit-wasm:err]' : level >= 2 ? '[plugkit-wasm:warn]' : '[plugkit-wasm]';
|
|
1561
1616
|
if (level >= 2) console.error(`${prefix} ${msg}`);
|
|
1562
1617
|
else console.log(`${prefix} ${msg}`);
|
|
1618
|
+
const evtMatch = msg.match(/^evt:\s*(\{.*\})\s*$/);
|
|
1619
|
+
if (evtMatch) {
|
|
1620
|
+
try {
|
|
1621
|
+
const ev = JSON.parse(evtMatch[1]);
|
|
1622
|
+
const eventName = ev.event || 'wasm.event';
|
|
1623
|
+
const { event: _e, ts: _ts, sess: _s, sub: _sub, ...fields } = ev;
|
|
1624
|
+
logEvent(ev.sub || 'plugkit', eventName, fields);
|
|
1625
|
+
} catch (_) {}
|
|
1626
|
+
}
|
|
1563
1627
|
return 0;
|
|
1564
1628
|
} catch (e) {
|
|
1565
1629
|
return 0;
|
|
@@ -1596,6 +1660,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1596
1660
|
});
|
|
1597
1661
|
}
|
|
1598
1662
|
const pwSessionId = getOrCreateBrowserSession(cwd, sessionId, pw);
|
|
1663
|
+
try { lastBrowserActivityMs = Date.now(); } catch (_) {}
|
|
1599
1664
|
const r = runBrowserRunner(pw, ['-s', pwSessionId, '--timeout', '14000', '-e', body], 60000);
|
|
1600
1665
|
return writeWasmJson(instanceRef.value, {
|
|
1601
1666
|
ok: r.status === 0,
|
|
@@ -1899,14 +1964,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1899
1964
|
lastActivityMs = Date.now();
|
|
1900
1965
|
}
|
|
1901
1966
|
|
|
1902
|
-
|
|
1903
|
-
if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return false;
|
|
1904
|
-
try { process.kill(pid, 'SIGTERM'); } catch (_) {}
|
|
1905
|
-
if (process.platform === 'win32') {
|
|
1906
|
-
try { spawnSync('taskkill', ['/F', '/T', '/PID', String(pid)], { stdio: 'ignore', timeout: 3000 }); } catch (_) {}
|
|
1907
|
-
}
|
|
1908
|
-
return true;
|
|
1909
|
-
}
|
|
1967
|
+
/* killPidQuiet, purgeProfileLockFiles, gracefulCloseBrowser are module-scope (defined above spool()). */
|
|
1910
1968
|
|
|
1911
1969
|
function teardownAll(reason) {
|
|
1912
1970
|
try {
|
|
@@ -1921,7 +1979,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1921
1979
|
const sessionsFile = browserSessionsFile(process.cwd());
|
|
1922
1980
|
const ports = readJsonFile(portsFile, {});
|
|
1923
1981
|
for (const [sid, entry] of Object.entries(ports)) {
|
|
1924
|
-
|
|
1982
|
+
gracefulCloseBrowser(entry, `teardown:${reason}`);
|
|
1925
1983
|
}
|
|
1926
1984
|
try { fs.unlinkSync(portsFile); } catch (_) {}
|
|
1927
1985
|
try { fs.unlinkSync(sessionsFile); } catch (_) {}
|
|
@@ -1934,6 +1992,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1934
1992
|
pid: process.pid,
|
|
1935
1993
|
idle_ms: Date.now() - lastActivityMs,
|
|
1936
1994
|
}));
|
|
1995
|
+
__shutdownReasonWritten = true;
|
|
1937
1996
|
} catch (_) {}
|
|
1938
1997
|
|
|
1939
1998
|
try { fs.unlinkSync(STATUS_PATH_FOR_TEARDOWN); } catch (_) {}
|
|
@@ -2013,6 +2072,32 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2013
2072
|
}
|
|
2014
2073
|
}, 60_000);
|
|
2015
2074
|
|
|
2075
|
+
const BROWSER_IDLE_LIMIT_MS = parseInt(process.env.PLUGKIT_BROWSER_IDLE_LIMIT_MS, 10) || 10 * 60 * 1000;
|
|
2076
|
+
let lastBrowserActivityMs = Date.now();
|
|
2077
|
+
setInterval(() => {
|
|
2078
|
+
try {
|
|
2079
|
+
const browserIdleMs = Date.now() - lastBrowserActivityMs;
|
|
2080
|
+
if (browserIdleMs < BROWSER_IDLE_LIMIT_MS) return;
|
|
2081
|
+
const portsFile = browserPortsFile(process.cwd());
|
|
2082
|
+
const sessionsFile = browserSessionsFile(process.cwd());
|
|
2083
|
+
const ports = readJsonFile(portsFile, {});
|
|
2084
|
+
let closed = 0;
|
|
2085
|
+
for (const [sid, entry] of Object.entries(ports)) {
|
|
2086
|
+
if (entry && Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid)) {
|
|
2087
|
+
try { gracefulCloseBrowser(entry, 'browser-idle'); closed++; } catch (_) {}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (closed > 0) {
|
|
2091
|
+
try { fs.unlinkSync(portsFile); } catch (_) {}
|
|
2092
|
+
try { fs.unlinkSync(sessionsFile); } catch (_) {}
|
|
2093
|
+
logEvent('plugkit', 'browser.idle-closed', { count: closed, idle_ms: browserIdleMs });
|
|
2094
|
+
}
|
|
2095
|
+
lastBrowserActivityMs = Date.now();
|
|
2096
|
+
} catch (e) {
|
|
2097
|
+
console.error(`[browser-idle] error: ${e.message}`);
|
|
2098
|
+
}
|
|
2099
|
+
}, 60_000);
|
|
2100
|
+
|
|
2016
2101
|
setInterval(() => {
|
|
2017
2102
|
try {
|
|
2018
2103
|
const idleMs = Date.now() - lastActivityMs;
|
|
@@ -2038,20 +2123,48 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2038
2123
|
}
|
|
2039
2124
|
}, IDLE_CHECK_MS);
|
|
2040
2125
|
|
|
2041
|
-
|
|
2126
|
+
const SHUTDOWN_REQUEST_PATH = path.join(spoolDir, '.shutdown-requested');
|
|
2127
|
+
setInterval(() => {
|
|
2042
2128
|
try {
|
|
2043
|
-
fs.
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2129
|
+
if (!fs.existsSync(SHUTDOWN_REQUEST_PATH)) return;
|
|
2130
|
+
let reqReason = 'shutdown-requested';
|
|
2131
|
+
try {
|
|
2132
|
+
const raw = fs.readFileSync(SHUTDOWN_REQUEST_PATH, 'utf-8').trim();
|
|
2133
|
+
if (raw) {
|
|
2134
|
+
try { const j = JSON.parse(raw); if (j && j.reason) reqReason = String(j.reason); }
|
|
2135
|
+
catch (_) { reqReason = raw.slice(0, 64); }
|
|
2136
|
+
}
|
|
2137
|
+
} catch (_) {}
|
|
2138
|
+
try { fs.unlinkSync(SHUTDOWN_REQUEST_PATH); } catch (_) {}
|
|
2139
|
+
handleSignalShutdown(reqReason.toUpperCase());
|
|
2140
|
+
} catch (e) {
|
|
2141
|
+
console.error(`[shutdown-request] error: ${e.message}`);
|
|
2142
|
+
}
|
|
2143
|
+
}, 2000);
|
|
2144
|
+
|
|
2145
|
+
let _signalShutdownInFlight = false;
|
|
2146
|
+
function handleSignalShutdown(sig) {
|
|
2147
|
+
if (_signalShutdownInFlight) return;
|
|
2148
|
+
_signalShutdownInFlight = true;
|
|
2149
|
+
try { teardownAll(sig.toLowerCase()); } catch (_) {
|
|
2150
|
+
try {
|
|
2151
|
+
fs.writeFileSync(SHUTDOWN_REASON_PATH, JSON.stringify({
|
|
2152
|
+
reason: sig.toLowerCase(),
|
|
2153
|
+
ts: Date.now(),
|
|
2154
|
+
pid: process.pid,
|
|
2155
|
+
teardown_failed: true,
|
|
2156
|
+
}));
|
|
2157
|
+
__shutdownReasonWritten = true;
|
|
2158
|
+
} catch (__) {}
|
|
2159
|
+
try { clearBootActive(); } catch (__) {}
|
|
2160
|
+
try { releaseLock(); } catch (__) {}
|
|
2161
|
+
process.exit(0);
|
|
2162
|
+
}
|
|
2052
2163
|
}
|
|
2053
2164
|
process.on('SIGINT', () => handleSignalShutdown('SIGINT'));
|
|
2054
2165
|
process.on('SIGTERM', () => handleSignalShutdown('SIGTERM'));
|
|
2166
|
+
process.on('SIGBREAK', () => handleSignalShutdown('SIGBREAK'));
|
|
2167
|
+
process.on('SIGHUP', () => handleSignalShutdown('SIGHUP'));
|
|
2055
2168
|
process.on('exit', () => { try { clearBootActive(); } catch (_) {} releaseLock(); });
|
|
2056
2169
|
|
|
2057
2170
|
try {
|
|
@@ -2089,8 +2202,9 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2089
2202
|
prior_status: _priorStatus,
|
|
2090
2203
|
prior_status_age_ms: _priorStatus && Number.isFinite(_priorStatus.ts) ? Date.now() - _priorStatus.ts : null,
|
|
2091
2204
|
};
|
|
2092
|
-
const
|
|
2093
|
-
const
|
|
2205
|
+
const _UNPLANNED_REASONS = new Set(['uncaughtexception', 'unhandledrejection', 'wasm-abort', 'wasm-abort-graceful']);
|
|
2206
|
+
const _normalizedShutdownReason = _priorShutdown && _priorShutdown.reason ? String(_priorShutdown.reason).toLowerCase() : null;
|
|
2207
|
+
const _isPlannedBoot = !!_normalizedShutdownReason && !_UNPLANNED_REASONS.has(_normalizedShutdownReason);
|
|
2094
2208
|
const _isFirstBoot = !_priorShutdown && !_priorStatus;
|
|
2095
2209
|
const UNPLANNED_RESTART_MARKER = path.join(spoolDir, '.unplanned-restart.json');
|
|
2096
2210
|
const HEARTBEAT_RECENT_MS = 60_000;
|
|
@@ -2131,7 +2245,11 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2131
2245
|
history,
|
|
2132
2246
|
}, null, 2));
|
|
2133
2247
|
} catch (_) {}
|
|
2134
|
-
|
|
2248
|
+
if (_isPlannedBoot) {
|
|
2249
|
+
console.log(`[plugkit-wasm] planned restart: prior reason="${_priorShutdown.reason}" boot_reason=${_bootReason}`);
|
|
2250
|
+
} else {
|
|
2251
|
+
console.error(`[plugkit-wasm] UNPLANNED RESTART detected — prior watcher died without writing .shutdown-reason.json. prior_status_age_ms=${restartContext.prior_status_age_ms} boot_reason=${_bootReason}`);
|
|
2252
|
+
}
|
|
2135
2253
|
}
|
|
2136
2254
|
try { fs.unlinkSync(SHUTDOWN_REASON_PATH); } catch (_) {}
|
|
2137
2255
|
logEvent('plugkit', 'watcher.boot', { version: _bootVersion, in_dir: inDir, out_dir: outDir, spool_dir: spoolDir, ...restartContext });
|