gm-plugkit 2.0.1304 → 2.0.1306
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 +139 -109
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1306",
|
|
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
|
@@ -381,7 +381,18 @@ function emitOrchestratorEvents(verb, taskBase, resultStr) {
|
|
|
381
381
|
if (verb === 'prd-resolve' && errData && errData.deviation_kind === 'prd-resolve-unknown-id') {
|
|
382
382
|
logEvent('hook', 'deviation.prd-resolve-unknown-id', { task: taskBase, prd_id: errData.prd_id, reason: errData.error });
|
|
383
383
|
}
|
|
384
|
-
|
|
384
|
+
const reason = (parsed && (parsed.reason || parsed.error)) ||
|
|
385
|
+
(parsed && parsed.data && (parsed.data.reason || parsed.data.error)) ||
|
|
386
|
+
(errData && (errData.reason || errData.error)) ||
|
|
387
|
+
(parsed && parsed.stderr) ||
|
|
388
|
+
'unknown';
|
|
389
|
+
logEvent('plugkit', 'orchestrator.error', {
|
|
390
|
+
verb,
|
|
391
|
+
task: taskBase,
|
|
392
|
+
error: String(reason).slice(0, 500),
|
|
393
|
+
gate_denied: !!(parsed && parsed.gate_denied),
|
|
394
|
+
next_dispatch: parsed && parsed.next_dispatch || null,
|
|
395
|
+
});
|
|
385
396
|
return;
|
|
386
397
|
}
|
|
387
398
|
const data = parsed.data || {};
|
|
@@ -722,105 +733,84 @@ function findInstalledChromiumBinary() {
|
|
|
722
733
|
}
|
|
723
734
|
}
|
|
724
735
|
|
|
736
|
+
function fetchJsonSync(url, timeoutMs) {
|
|
737
|
+
const r = spawnSync(process.execPath, ['-e', `
|
|
738
|
+
const http = require('http');
|
|
739
|
+
const req = http.get(${JSON.stringify(url)}, (res) => {
|
|
740
|
+
let buf = '';
|
|
741
|
+
res.on('data', d => buf += d);
|
|
742
|
+
res.on('end', () => {
|
|
743
|
+
if (res.statusCode !== 200) { process.stderr.write('status ' + res.statusCode); process.exit(2); }
|
|
744
|
+
process.stdout.write(buf);
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
req.on('error', e => { process.stderr.write(e.message); process.exit(1); });
|
|
748
|
+
req.setTimeout(${timeoutMs || 1500}, () => { req.destroy(new Error('timeout')); });
|
|
749
|
+
`], { encoding: 'utf-8', timeout: (timeoutMs || 1500) + 1500, windowsHide: true });
|
|
750
|
+
if (r.status !== 0) return null;
|
|
751
|
+
try { return JSON.parse(r.stdout); } catch (_) { return null; }
|
|
752
|
+
}
|
|
753
|
+
|
|
725
754
|
function startManagedBrowser(pw, profileDir) {
|
|
726
755
|
const headless = process.env.GM_BROWSER_HEADLESS === '1';
|
|
727
|
-
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
|
|
756
|
+
let browserBin = findInstalledChromiumBinary();
|
|
757
|
+
if (!browserBin) {
|
|
758
|
+
logEvent('plugkit', 'browser.chromium-installing', {});
|
|
759
|
+
spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['--yes', 'playwright', 'install', 'chromium'], {
|
|
760
|
+
encoding: 'utf-8',
|
|
761
|
+
timeout: 300000,
|
|
762
|
+
windowsHide: true,
|
|
763
|
+
shell: process.platform === 'win32',
|
|
764
|
+
stdio: 'ignore',
|
|
765
|
+
});
|
|
766
|
+
browserBin = findInstalledChromiumBinary();
|
|
767
|
+
}
|
|
768
|
+
if (!browserBin) {
|
|
769
|
+
const err = new Error('chromium binary not found after install attempt');
|
|
770
|
+
logEvent('plugkit', 'browser.launch-failed', { reason: 'chromium-missing' });
|
|
771
|
+
throw err;
|
|
739
772
|
}
|
|
740
|
-
const
|
|
773
|
+
const port = findFreePortSync();
|
|
774
|
+
const args = [
|
|
775
|
+
'--user-data-dir=' + profileDir,
|
|
776
|
+
'--remote-debugging-port=' + port,
|
|
777
|
+
'--remote-debugging-address=127.0.0.1',
|
|
778
|
+
'--no-first-run',
|
|
779
|
+
'--no-default-browser-check',
|
|
780
|
+
'--disable-default-apps',
|
|
781
|
+
];
|
|
782
|
+
if (headless) args.push('--headless=new');
|
|
783
|
+
const chromeLogPath = path.join(profileDir, '.chrome-launch.log');
|
|
784
|
+
let logFd;
|
|
785
|
+
try { logFd = fs.openSync(chromeLogPath, 'a'); } catch (_) { logFd = null; }
|
|
786
|
+
const child = spawn(browserBin, args, {
|
|
741
787
|
detached: true,
|
|
742
|
-
stdio: 'ignore',
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
env,
|
|
746
|
-
...(process.platform === 'win32' ? { creationFlags: 0x08000000 | 0x00000008 } : {}),
|
|
788
|
+
stdio: ['ignore', logFd != null ? logFd : 'ignore', logFd != null ? logFd : 'ignore'],
|
|
789
|
+
windowsHide: false,
|
|
790
|
+
env: process.env,
|
|
747
791
|
});
|
|
792
|
+
try { if (typeof logFd === 'number') fs.closeSync(logFd); } catch (_) {}
|
|
748
793
|
const pid = child.pid;
|
|
749
794
|
child.unref();
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
function isColdRunProfile(profileDir) {
|
|
754
|
-
try {
|
|
755
|
-
if (!fs.existsSync(profileDir)) return true;
|
|
756
|
-
const entries = fs.readdirSync(profileDir);
|
|
757
|
-
if (entries.length === 0) return true;
|
|
758
|
-
if (!entries.some(n => n === 'Default' || n === 'Local State')) return true;
|
|
759
|
-
return false;
|
|
760
|
-
} catch (_) {
|
|
761
|
-
return true;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function resolveManagedBrowserKey(pw) {
|
|
766
|
-
const explicitKey = process.env.GM_BROWSER_RUNNER_KEY || process.env.PLAYWRITER_BROWSER_KEY;
|
|
767
|
-
if (explicitKey) {
|
|
768
|
-
if (/^profile:/.test(explicitKey)) {
|
|
769
|
-
throw new Error(`GM_BROWSER_RUNNER_KEY=${explicitKey} points at a user OS browser profile; refusing — managed sessions must use install:Chromium:*`);
|
|
770
|
-
}
|
|
771
|
-
return explicitKey;
|
|
772
|
-
}
|
|
773
|
-
let text = '';
|
|
774
|
-
try {
|
|
775
|
-
const r = runBrowserRunner(pw, ['browser', 'list'], 8000);
|
|
776
|
-
text = (r && (r.stdout || r.stderr)) || '';
|
|
777
|
-
} catch (e) {
|
|
778
|
-
throw new Error(`managed browser session list failed: ${e.message}`);
|
|
779
|
-
}
|
|
780
|
-
const lines = text.split(/\r?\n/);
|
|
781
|
-
const installChromium = lines.map(l => (l.match(/\b(install:Chromium:[A-Za-z0-9_-]+)\b/) || [])[1]).find(Boolean);
|
|
782
|
-
if (installChromium) return installChromium;
|
|
783
|
-
throw new Error(`no managed Chromium install detected; browser runner returned: ${scrubBrowserRunnerText(text).trim()}`);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
function waitForExtensionReady(pw, profileDir, opts) {
|
|
787
|
-
const cold = (opts && typeof opts.cold === 'boolean') ? opts.cold : isColdRunProfile(profileDir);
|
|
788
|
-
const timeoutMs = (opts && opts.timeoutMs) || (cold ? 180000 : 30000);
|
|
795
|
+
logEvent('plugkit', 'browser.chromium-launched', { pid, port, profileDir, headless, binary: browserBin, chromeLogPath });
|
|
789
796
|
const start = Date.now();
|
|
790
|
-
const deadline = start +
|
|
791
|
-
|
|
792
|
-
let
|
|
793
|
-
let lastErr = '';
|
|
794
|
-
let lastProgressAt = start;
|
|
795
|
-
const browserKey = resolveManagedBrowserKey(pw);
|
|
797
|
+
const deadline = start + 30000;
|
|
798
|
+
let wsEndpoint = null;
|
|
799
|
+
let lastErr = null;
|
|
796
800
|
while (Date.now() < deadline) {
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
lastErr = scrubBrowserRunnerText((r && (r.stderr || r.stdout)) || '');
|
|
802
|
-
const now = Date.now();
|
|
803
|
-
if (now - lastProgressAt >= 10000) {
|
|
804
|
-
logEvent('plugkit', 'browser.extension-wait', {
|
|
805
|
-
elapsed_ms: now - start,
|
|
806
|
-
cold_run: cold,
|
|
807
|
-
profileDir,
|
|
808
|
-
attempt,
|
|
809
|
-
});
|
|
810
|
-
lastProgressAt = now;
|
|
801
|
+
const info = fetchJsonSync(`http://127.0.0.1:${port}/json/version`, 1500);
|
|
802
|
+
if (info && info.webSocketDebuggerUrl) {
|
|
803
|
+
wsEndpoint = info.webSocketDebuggerUrl;
|
|
804
|
+
break;
|
|
811
805
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
806
|
+
sleepSync(500);
|
|
807
|
+
}
|
|
808
|
+
if (!wsEndpoint) {
|
|
809
|
+
logEvent('plugkit', 'browser.launch-failed', { reason: 'cdp-not-ready', pid, port, elapsed_ms: Date.now() - start });
|
|
810
|
+
throw new Error(`chromium launched (pid=${pid}) but CDP at 127.0.0.1:${port} did not become ready within 30s${lastErr ? ' :: ' + lastErr : ''}`);
|
|
816
811
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
: `warm-run timeout after ${Math.round((Date.now() - start) / 1000)}s waiting for managed browser extension to reconnect (profile exists but extension is not registering; relay server may be wedged)`;
|
|
820
|
-
const err = new Error(`managed browser session start failed: ${flavor}${lastErr ? ` :: ${lastErr}` : ''}`);
|
|
821
|
-
err._lastErr = lastErr;
|
|
822
|
-
err._coldRun = cold;
|
|
823
|
-
throw err;
|
|
812
|
+
logEvent('plugkit', 'browser.cdp-ready', { pid, port, ms: Date.now() - start, wsEndpoint });
|
|
813
|
+
return { pid, port, wsEndpoint };
|
|
824
814
|
}
|
|
825
815
|
|
|
826
816
|
function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
@@ -830,15 +820,29 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
830
820
|
const ports = readJsonFile(portsFile, {});
|
|
831
821
|
const sessions = readJsonFile(sessionsFile, {});
|
|
832
822
|
const existing = ports[claudeSessionId];
|
|
833
|
-
if (existing && existing.pid) {
|
|
823
|
+
if (existing && existing.pid && existing.wsEndpoint) {
|
|
834
824
|
const wantProfile = path.join(cwd, '.gm', 'browser-profile');
|
|
835
825
|
const pidOk = isProcessAliveSync(existing.pid);
|
|
836
826
|
const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(path.join(cwd, '.gm', 'browser-profile'));
|
|
837
|
-
|
|
827
|
+
const cdpOk = pidOk && !!fetchJsonSync(`http://127.0.0.1:${existing.port}/json/version`, 1000);
|
|
828
|
+
if (pidOk && profileOk && cdpOk) {
|
|
838
829
|
const pwIds = sessions[claudeSessionId] || [];
|
|
839
|
-
if (pwIds.length > 0) return
|
|
830
|
+
if (pwIds.length > 0 && existing.pwSessionId) return existing.pwSessionId;
|
|
831
|
+
const r = runBrowserRunner(pw, ['session', 'new', '--direct', existing.wsEndpoint], 30000);
|
|
832
|
+
if (r && r.status === 0) {
|
|
833
|
+
const sid = parseSessionId(r.stdout || '');
|
|
834
|
+
if (sid) {
|
|
835
|
+
existing.pwSessionId = sid;
|
|
836
|
+
ports[claudeSessionId] = existing;
|
|
837
|
+
sessions[claudeSessionId] = [sid];
|
|
838
|
+
writeJsonFile(portsFile, ports);
|
|
839
|
+
writeJsonFile(sessionsFile, sessions);
|
|
840
|
+
logEvent('plugkit', 'browser.attached', { pwSessionId: sid, reused: true });
|
|
841
|
+
return sid;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
840
844
|
} else {
|
|
841
|
-
const reason = !pidOk ? 'pid-dead' : 'profile-drift';
|
|
845
|
+
const reason = !pidOk ? 'pid-dead' : (!cdpOk ? 'cdp-dead' : 'profile-drift');
|
|
842
846
|
logEvent('hook', 'deviation.browser-profile-collision', {
|
|
843
847
|
sid: claudeSessionId,
|
|
844
848
|
stale_pid: existing.pid || null,
|
|
@@ -853,32 +857,58 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
853
857
|
}
|
|
854
858
|
}
|
|
855
859
|
cleanDeadProfileFragments(cwd);
|
|
856
|
-
const probedProfile = path.join(cwd, '.gm', 'browser-profile');
|
|
857
|
-
const coldRun = isColdRunProfile(probedProfile);
|
|
858
860
|
const profileDir = acquireProfileDir(cwd);
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
861
|
+
const aliveCdpForProfile = (() => {
|
|
862
|
+
for (const key of Object.keys(ports)) {
|
|
863
|
+
const ent = ports[key];
|
|
864
|
+
if (!ent || !ent.pid || !ent.port || !ent.wsEndpoint) continue;
|
|
865
|
+
if (ent.profileDir !== profileDir && !(ent.profileDir || '').startsWith(profileDir)) continue;
|
|
866
|
+
if (!isProcessAliveSync(ent.pid)) continue;
|
|
867
|
+
const info = fetchJsonSync(`http://127.0.0.1:${ent.port}/json/version`, 1000);
|
|
868
|
+
if (info && info.webSocketDebuggerUrl) {
|
|
869
|
+
return { pid: ent.pid, port: ent.port, wsEndpoint: ent.wsEndpoint };
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return null;
|
|
873
|
+
})();
|
|
874
|
+
let browserPid, port, wsEndpoint;
|
|
875
|
+
if (aliveCdpForProfile) {
|
|
876
|
+
({ pid: browserPid, port, wsEndpoint } = aliveCdpForProfile);
|
|
877
|
+
logEvent('plugkit', 'browser.reused-existing-chromium', { pid: browserPid, port, profileDir });
|
|
878
|
+
} else {
|
|
879
|
+
logEvent('plugkit', 'browser.start', { profileDir });
|
|
880
|
+
({ pid: browserPid, port, wsEndpoint } = startManagedBrowser(pw, profileDir));
|
|
870
881
|
}
|
|
882
|
+
const r = runBrowserRunner(pw, ['session', 'new', '--direct', wsEndpoint], 30000);
|
|
883
|
+
if (!r || r.status !== 0) {
|
|
884
|
+
const errTxt = scrubBrowserRunnerText((r && (r.stderr || r.stdout)) || 'unknown');
|
|
885
|
+
logEvent('plugkit', 'browser.launch-failed', { reason: 'session-attach-failed', pid: browserPid, port, error: errTxt });
|
|
886
|
+
throw new Error(`playwriter session new --direct failed: ${errTxt}`);
|
|
887
|
+
}
|
|
888
|
+
const pwSessionId = parseSessionId(r.stdout || '');
|
|
871
889
|
if (!pwSessionId) {
|
|
872
|
-
|
|
890
|
+
logEvent('plugkit', 'browser.launch-failed', { reason: 'session-id-unparseable', stdout: r.stdout });
|
|
891
|
+
throw new Error(`could not parse managed browser session id from: ${scrubBrowserRunnerText(r.stdout || '')}`);
|
|
873
892
|
}
|
|
874
|
-
|
|
875
|
-
ports[claudeSessionId] = { profileDir, pid: browserPid };
|
|
893
|
+
ports[claudeSessionId] = { profileDir, pid: browserPid, port, wsEndpoint, pwSessionId };
|
|
876
894
|
sessions[claudeSessionId] = [pwSessionId];
|
|
877
895
|
writeJsonFile(portsFile, ports);
|
|
878
896
|
writeJsonFile(sessionsFile, sessions);
|
|
897
|
+
logEvent('plugkit', 'browser.attached', { pwSessionId, pid: browserPid, port });
|
|
879
898
|
return pwSessionId;
|
|
880
899
|
}
|
|
881
900
|
|
|
901
|
+
function parseSessionId(rawOut) {
|
|
902
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
903
|
+
const out = stripAnsi(rawOut || '').trim();
|
|
904
|
+
const created = out.match(/Session\s+(\S+)\s+created/i);
|
|
905
|
+
if (created) return created[1];
|
|
906
|
+
const hex = out.match(/\b([a-f0-9-]{8,})\b/i);
|
|
907
|
+
if (hex) return hex[1];
|
|
908
|
+
try { const j = JSON.parse(out); return j.id || j.session_id || j.session || null; } catch (_) {}
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
|
|
882
912
|
const VEC_K_DEFAULT = 10;
|
|
883
913
|
const EMBED_MODEL_DEFAULT = process.env.EMBED_MODEL || 'mistral/mistral-embed';
|
|
884
914
|
const INFERENCE_MODEL_DEFAULT = process.env.INFERENCE_MODEL || 'groq/llama-3.3-70b-versatile';
|