gm-plugkit 2.0.1633 → 2.0.1634
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/instructions/browser.md +3 -1
- package/package.json +1 -1
- package/plugkit-wasm-wrapper.js +67 -1
package/instructions/browser.md
CHANGED
|
@@ -44,9 +44,11 @@ dom=<css-selector>\n
|
|
|
44
44
|
|
|
45
45
|
**`dom=<css-selector>\n` is the zero-boilerplate element probe.** Returns `{selector, match_count, elements:[{tag, text, attrs, visible, rect}]}` for up to 20 matches -- the fastest answer to "is this element there and what does it say." An invalid selector returns `result.error` (no crash). Composes with `url=`.
|
|
46
46
|
|
|
47
|
+
**One session per run -- reuse it, then close it.** A browser session is keyed by its spool `sessionId`; every dispatch carrying the SAME sessionId reuses the SAME chromium. A DIFFERENT sessionId opens its OWN chromium -- so a run that invents `probe`/`w2`/`w3`/... names leaks one browser per name. Pick one sessionId, use it for every dispatch, and end with `session close` so nothing is left open; the eval envelope carries a `multi_session_warning` the moment a second distinct session opens. The idle reaper (closes sessions unused past the idle window) and the OS-orphan reaper (kills managed chromiums no live session owns, sparing in-use ones and your own Chrome) are backstops for crashes, not a license to leave sessions open -- close yours.
|
|
48
|
+
|
|
47
49
|
## Envelope
|
|
48
50
|
|
|
49
|
-
`{ok, stdout, stderr, exit_code, session_id?, navigation_requested, landed_on_blank?, hint?}`. `stdout` = stringified eval result; `stderr` = page errors + launch diagnostics; `exit_code` non-zero = the dispatch did not land -- read `stderr` and re-dispatch, never blind. `navigation_requested` reflects whether the dispatch carried a `url=`/bare-URL navigation; `landed_on_blank: true` with a `hint` means the expression ran against `about:blank` -- prefix `url=<target>` and re-dispatch.
|
|
51
|
+
`{ok, stdout, stderr, exit_code, session_id?, navigation_requested, landed_on_blank?, hint?, multi_session_warning?}`. `stdout` = stringified eval result; `stderr` = page errors + launch diagnostics; `exit_code` non-zero = the dispatch did not land -- read `stderr` and re-dispatch, never blind. `navigation_requested` reflects whether the dispatch carried a `url=`/bare-URL navigation; `landed_on_blank: true` with a `hint` means the expression ran against `about:blank` -- prefix `url=<target>` and re-dispatch.
|
|
50
52
|
|
|
51
53
|
## Headed by default
|
|
52
54
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1634",
|
|
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
|
@@ -873,6 +873,62 @@ function reapOrphanBrowserSessions(pw, cwd, claudeSessionId, reason) {
|
|
|
873
873
|
}
|
|
874
874
|
}
|
|
875
875
|
|
|
876
|
+
const __openedSessionIds = new Set();
|
|
877
|
+
function enumerateManagedChromiums(profileRootMarker) {
|
|
878
|
+
const marker = String(profileRootMarker || '').toLowerCase().replace(/\\/g, '/');
|
|
879
|
+
const out = [];
|
|
880
|
+
try {
|
|
881
|
+
if (process.platform === 'win32') {
|
|
882
|
+
const ps = `Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" | Where-Object { $_.CommandLine -like '*--remote-debugging-port*' -and $_.CommandLine -like '*browser-profile*' -and $_.CommandLine -notlike '*--type=*' } | ForEach-Object { $_.ProcessId.ToString() + '|' + $_.CommandLine }`;
|
|
883
|
+
const r = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { encoding: 'utf-8', windowsHide: true, timeout: 10000 });
|
|
884
|
+
if (r.status === 0 && r.stdout) {
|
|
885
|
+
for (const line of r.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
886
|
+
const bar = line.indexOf('|');
|
|
887
|
+
if (bar < 0) continue;
|
|
888
|
+
const pid = parseInt(line.slice(0, bar), 10);
|
|
889
|
+
const cmd = line.slice(bar + 1);
|
|
890
|
+
if (/--type=/.test(cmd)) continue;
|
|
891
|
+
if (Number.isFinite(pid) && cmd.toLowerCase().replace(/\\/g, '/').includes(marker)) out.push({ pid, cmd });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
} else {
|
|
895
|
+
const r = spawnSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 10000 });
|
|
896
|
+
if (r.status === 0 && r.stdout) {
|
|
897
|
+
for (const line of r.stdout.split('\n').slice(1)) {
|
|
898
|
+
if (!/--remote-debugging-port/.test(line) || !/browser-profile/.test(line)) continue;
|
|
899
|
+
if (/--type=/.test(line)) continue;
|
|
900
|
+
const m = line.match(/^\s*(\d+)\s+(.+)$/);
|
|
901
|
+
if (!m) continue;
|
|
902
|
+
const pid = parseInt(m[1], 10);
|
|
903
|
+
if (Number.isFinite(pid) && m[2].toLowerCase().replace(/\\/g, '/').includes(marker)) out.push({ pid, cmd: m[2] });
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
} catch (_) {}
|
|
908
|
+
return out;
|
|
909
|
+
}
|
|
910
|
+
function reapOrphanChromiums(cwd, reason) {
|
|
911
|
+
try {
|
|
912
|
+
const root = browserRootDir(cwd);
|
|
913
|
+
const marker = path.join(root, '.gm', 'browser-profile').toLowerCase().replace(/\\/g, '/');
|
|
914
|
+
const procs = enumerateManagedChromiums(marker);
|
|
915
|
+
if (procs.length === 0) return { reaped: 0 };
|
|
916
|
+
const ports = readJsonFile(browserPortsFile(cwd), {});
|
|
917
|
+
const livePids = new Set();
|
|
918
|
+
for (const ent of Object.values(ports)) {
|
|
919
|
+
if (ent && Number.isFinite(ent.pid) && isProcessAliveSync(ent.pid)) livePids.add(ent.pid);
|
|
920
|
+
}
|
|
921
|
+
let reaped = 0;
|
|
922
|
+
for (const { pid } of procs) {
|
|
923
|
+
if (livePids.has(pid)) continue;
|
|
924
|
+
try { killPidQuiet(pid); reaped++; logEvent('plugkit', 'browser.os-orphan-reaped', { pid, reason: reason || 'sweep' }); } catch (_) {}
|
|
925
|
+
}
|
|
926
|
+
return { reaped };
|
|
927
|
+
} catch (_) {
|
|
928
|
+
return { reaped: 0 };
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
876
932
|
function resolveWindowsExeLocal(cmd) {
|
|
877
933
|
if (process.platform !== 'win32') return cmd;
|
|
878
934
|
try {
|
|
@@ -1307,6 +1363,10 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
1307
1363
|
sessions[claudeSessionId] = [pwSessionId];
|
|
1308
1364
|
writeJsonFile(portsFile, ports);
|
|
1309
1365
|
writeJsonFile(sessionsFile, sessions);
|
|
1366
|
+
if (!__openedSessionIds.has(claudeSessionId) && __openedSessionIds.size >= 1) {
|
|
1367
|
+
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' });
|
|
1368
|
+
}
|
|
1369
|
+
__openedSessionIds.add(claudeSessionId);
|
|
1310
1370
|
logEvent('plugkit', 'browser.attached', { pwSessionId, pid: browserPid, port });
|
|
1311
1371
|
return pwSessionId;
|
|
1312
1372
|
} finally { releaseSpawnLock(); }
|
|
@@ -2187,6 +2247,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
2187
2247
|
stderr: '',
|
|
2188
2248
|
exit_code: 0,
|
|
2189
2249
|
session_id: pwSessionId,
|
|
2250
|
+
hint: 'Reuse this same session for every browser dispatch this run (the spool sessionId selects it); a different sessionId opens its OWN chromium. Close it with `session close` when done -- the idle/orphan reaper is only a backstop.',
|
|
2190
2251
|
});
|
|
2191
2252
|
}
|
|
2192
2253
|
|
|
@@ -2376,6 +2437,9 @@ function makeHostFunctions(instanceRef) {
|
|
|
2376
2437
|
};
|
|
2377
2438
|
if (resultParsed) envelope.result = parsedResult;
|
|
2378
2439
|
envelope.navigation_requested = !!startUrl;
|
|
2440
|
+
if (__openedSessionIds.size > 1) {
|
|
2441
|
+
envelope.multi_session_warning = `${__openedSessionIds.size} distinct browser sessions opened this run, each its own chromium -- reuse ONE sessionId per run and 'session close' it when done to avoid leaking browsers.`;
|
|
2442
|
+
}
|
|
2379
2443
|
if (landedOnBlank) {
|
|
2380
2444
|
envelope.landed_on_blank = true;
|
|
2381
2445
|
envelope.hint = "page is about:blank: this dispatch did not navigate, so the expression evaluated against an empty page. Prefix the body with 'url=<target>' (or send a bare 'https://...' URL) to open the page you want before evaluating.";
|
|
@@ -2488,6 +2552,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2488
2552
|
} catch (_) {}
|
|
2489
2553
|
|
|
2490
2554
|
try { reapOrphanBrowserSessions(findBrowserRunner(), process.cwd(), process.env.CLAUDE_SESSION_ID || 'claude-loop-iter', 'watcher-boot'); } catch (_) {}
|
|
2555
|
+
try { reapOrphanChromiums(process.cwd(), 'watcher-boot'); } catch (_) {}
|
|
2491
2556
|
|
|
2492
2557
|
|
|
2493
2558
|
const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
|
|
@@ -3124,7 +3189,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
3124
3189
|
}
|
|
3125
3190
|
}, 60_000);
|
|
3126
3191
|
|
|
3127
|
-
const BROWSER_IDLE_LIMIT_MS = parseInt(process.env.PLUGKIT_BROWSER_IDLE_LIMIT_MS, 10) ||
|
|
3192
|
+
const BROWSER_IDLE_LIMIT_MS = parseInt(process.env.PLUGKIT_BROWSER_IDLE_LIMIT_MS, 10) || 3 * 60 * 1000;
|
|
3128
3193
|
setInterval(() => {
|
|
3129
3194
|
try {
|
|
3130
3195
|
const portsFile = browserPortsFile(process.cwd());
|
|
@@ -3168,6 +3233,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
3168
3233
|
try { writeJsonFile(sessionsFile, sessions); } catch (_) {}
|
|
3169
3234
|
}
|
|
3170
3235
|
try { reapOrphanBrowserSessions(findBrowserRunner(), process.cwd(), process.env.CLAUDE_SESSION_ID || 'claude-loop-iter', 'idle-sweep'); } catch (_) {}
|
|
3236
|
+
try { reapOrphanChromiums(process.cwd(), 'idle-sweep'); } catch (_) {}
|
|
3171
3237
|
} catch (e) {
|
|
3172
3238
|
console.error(`[browser-idle] error: ${e.message}`);
|
|
3173
3239
|
}
|