gm-skill 2.0.1268 → 2.0.1270
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/README.md +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +40 -157
- package/gm.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ An earlier generation fanned out fifteen per-platform downstream repos (gm-cc, g
|
|
|
35
35
|
|
|
36
36
|
## Version
|
|
37
37
|
|
|
38
|
-
`2.0.
|
|
38
|
+
`2.0.1270` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
|
|
39
39
|
|
|
40
40
|
## Source of truth
|
|
41
41
|
|
|
@@ -420,105 +420,6 @@ function writeJsonFile(fp, value) {
|
|
|
420
420
|
try { fs.writeFileSync(fp, JSON.stringify(value, null, 2)); } catch (_) {}
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
function msPlaywrightCacheRoots() {
|
|
424
|
-
const roots = [];
|
|
425
|
-
if (process.env.PLAYWRIGHT_BROWSERS_PATH) roots.push(process.env.PLAYWRIGHT_BROWSERS_PATH);
|
|
426
|
-
if (process.platform === 'win32') {
|
|
427
|
-
if (process.env.LOCALAPPDATA) roots.push(path.join(process.env.LOCALAPPDATA, 'ms-playwright'));
|
|
428
|
-
} else if (process.platform === 'darwin') {
|
|
429
|
-
if (process.env.HOME) roots.push(path.join(process.env.HOME, 'Library', 'Caches', 'ms-playwright'));
|
|
430
|
-
} else {
|
|
431
|
-
if (process.env.HOME) roots.push(path.join(process.env.HOME, '.cache', 'ms-playwright'));
|
|
432
|
-
}
|
|
433
|
-
return roots.filter(r => r && fs.existsSync(r));
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function chromiumExeFromCacheRoot(root) {
|
|
437
|
-
try {
|
|
438
|
-
const entries = fs.readdirSync(root)
|
|
439
|
-
.filter(n => /^chromium-\d+$/.test(n))
|
|
440
|
-
.sort((a, b) => parseInt(b.split('-')[1], 10) - parseInt(a.split('-')[1], 10));
|
|
441
|
-
const subdirs = process.platform === 'win32'
|
|
442
|
-
? ['chrome-win64', 'chrome-win']
|
|
443
|
-
: process.platform === 'darwin'
|
|
444
|
-
? ['chrome-mac/Chromium.app/Contents/MacOS/Chromium']
|
|
445
|
-
: ['chrome-linux'];
|
|
446
|
-
const exeName = process.platform === 'win32' ? 'chrome.exe'
|
|
447
|
-
: process.platform === 'darwin' ? null
|
|
448
|
-
: 'chrome';
|
|
449
|
-
for (const e of entries) {
|
|
450
|
-
for (const sub of subdirs) {
|
|
451
|
-
const p = exeName ? path.join(root, e, sub, exeName) : path.join(root, e, sub);
|
|
452
|
-
if (fs.existsSync(p)) return p;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
} catch (_) {}
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function findBundledChromium() {
|
|
460
|
-
for (const root of msPlaywrightCacheRoots()) {
|
|
461
|
-
const exe = chromiumExeFromCacheRoot(root);
|
|
462
|
-
if (exe) return exe;
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
const npmR = spawnSync('npm', ['root', '-g'], { encoding: 'utf-8', shell: true, windowsHide: true, timeout: 5000 });
|
|
466
|
-
if (npmR.status === 0 && npmR.stdout.trim()) {
|
|
467
|
-
const root = npmR.stdout.trim().split(/\r?\n/).pop();
|
|
468
|
-
const pwBrowsers = path.join(root, 'playwriter', 'node_modules', '@xmorse', 'playwright-core', '.local-browsers');
|
|
469
|
-
if (fs.existsSync(pwBrowsers)) {
|
|
470
|
-
const exe = chromiumExeFromCacheRoot(pwBrowsers);
|
|
471
|
-
if (exe) return exe;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
} catch (_) {}
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function ensureBundledChromium(pw) {
|
|
479
|
-
const existing = findBundledChromium();
|
|
480
|
-
if (existing) return { exe: existing, installed: false };
|
|
481
|
-
const installer = pw && pw.cmd
|
|
482
|
-
? { cmd: pw.cmd, args: [...pw.baseArgs, 'install', 'chromium'], shell: pw.shell }
|
|
483
|
-
: { cmd: 'npx', args: ['-y', 'playwright', 'install', 'chromium'], shell: true };
|
|
484
|
-
logEvent('bootstrap', 'browser.chromium-install.start', { via: installer.cmd });
|
|
485
|
-
const r = spawnSync(installer.cmd, installer.args, {
|
|
486
|
-
encoding: 'utf-8',
|
|
487
|
-
timeout: 600000,
|
|
488
|
-
shell: installer.shell,
|
|
489
|
-
windowsHide: true,
|
|
490
|
-
env: { ...process.env, PLAYWRIGHT_BROWSERS_PATH: process.env.PLAYWRIGHT_BROWSERS_PATH || '' },
|
|
491
|
-
});
|
|
492
|
-
logEvent('bootstrap', 'browser.chromium-install.done', { status: r.status });
|
|
493
|
-
const after = findBundledChromium();
|
|
494
|
-
if (after) return { exe: after, installed: true };
|
|
495
|
-
return { exe: null, installed: false, error: r.stderr || r.stdout || 'install failed' };
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function findChrome() {
|
|
499
|
-
const bundled = findBundledChromium();
|
|
500
|
-
if (bundled) return bundled;
|
|
501
|
-
if (process.platform === 'win32') {
|
|
502
|
-
const candidates = [
|
|
503
|
-
path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
504
|
-
path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
505
|
-
path.join(process.env.LOCALAPPDATA || '', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
506
|
-
];
|
|
507
|
-
for (const c of candidates) { if (c && fs.existsSync(c)) return c; }
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
if (process.platform === 'darwin') {
|
|
511
|
-
const mac = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
512
|
-
if (fs.existsSync(mac)) return mac;
|
|
513
|
-
return null;
|
|
514
|
-
}
|
|
515
|
-
for (const bin of ['google-chrome', 'chromium', 'chromium-browser']) {
|
|
516
|
-
const r = spawnSync('which', [bin], { encoding: 'utf-8' });
|
|
517
|
-
if (r.status === 0 && r.stdout.trim()) return r.stdout.trim();
|
|
518
|
-
}
|
|
519
|
-
return null;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
423
|
function findPlaywriter() {
|
|
523
424
|
const npmR = spawnSync('npm', ['root', '-g'], { encoding: 'utf-8', shell: true });
|
|
524
425
|
if (npmR.status === 0 && npmR.stdout.trim()) {
|
|
@@ -715,6 +616,35 @@ function scrubBrowserRunnerText(s) {
|
|
|
715
616
|
return t;
|
|
716
617
|
}
|
|
717
618
|
|
|
619
|
+
function startManagedBrowser(pw, profileDir) {
|
|
620
|
+
const args = [...pw.baseArgs, 'browser', 'start', '--user-data-dir', profileDir, '--headless'];
|
|
621
|
+
const child = spawn(pw.cmd, args, {
|
|
622
|
+
detached: true,
|
|
623
|
+
stdio: 'ignore',
|
|
624
|
+
shell: pw.shell,
|
|
625
|
+
windowsHide: true,
|
|
626
|
+
env: process.env,
|
|
627
|
+
...(process.platform === 'win32' ? { creationFlags: 0x08000000 | 0x00000008 } : {}),
|
|
628
|
+
});
|
|
629
|
+
const pid = child.pid;
|
|
630
|
+
child.unref();
|
|
631
|
+
return pid;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function waitForExtensionReady(pw, timeoutMs) {
|
|
635
|
+
const deadline = Date.now() + (timeoutMs || 30000);
|
|
636
|
+
let lastErr = '';
|
|
637
|
+
while (Date.now() < deadline) {
|
|
638
|
+
const r = runPlaywriter(pw, ['session', 'new'], 15000);
|
|
639
|
+
if (r.status === 0) return r;
|
|
640
|
+
lastErr = scrubBrowserRunnerText(r.stderr || r.stdout || '');
|
|
641
|
+
sleepSync(500);
|
|
642
|
+
}
|
|
643
|
+
const err = new Error(`managed browser session start failed: ${lastErr || 'timeout waiting for extension'}`);
|
|
644
|
+
err._lastErr = lastErr;
|
|
645
|
+
throw err;
|
|
646
|
+
}
|
|
647
|
+
|
|
718
648
|
function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
719
649
|
migrateLegacyBrowserState(cwd);
|
|
720
650
|
const portsFile = browserPortsFile(cwd);
|
|
@@ -722,20 +652,18 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
722
652
|
const ports = readJsonFile(portsFile, {});
|
|
723
653
|
const sessions = readJsonFile(sessionsFile, {});
|
|
724
654
|
const existing = ports[claudeSessionId];
|
|
725
|
-
if (existing && existing.
|
|
655
|
+
if (existing && existing.pid) {
|
|
726
656
|
const wantProfile = path.join(cwd, '.gm', 'browser-profile');
|
|
727
|
-
const pidOk =
|
|
657
|
+
const pidOk = isProcessAliveSync(existing.pid);
|
|
728
658
|
const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(path.join(cwd, '.gm', 'browser-profile'));
|
|
729
|
-
|
|
730
|
-
if (pidOk && profileOk && portOk) {
|
|
659
|
+
if (pidOk && profileOk) {
|
|
731
660
|
const pwIds = sessions[claudeSessionId] || [];
|
|
732
661
|
if (pwIds.length > 0) return pwIds[0];
|
|
733
662
|
} else {
|
|
734
|
-
const reason = !pidOk ? 'pid-dead' :
|
|
663
|
+
const reason = !pidOk ? 'pid-dead' : 'profile-drift';
|
|
735
664
|
logEvent('hook', 'deviation.browser-profile-collision', {
|
|
736
665
|
sid: claudeSessionId,
|
|
737
666
|
stale_pid: existing.pid || null,
|
|
738
|
-
stale_port: existing.port || null,
|
|
739
667
|
stale_profile: existing.profileDir || null,
|
|
740
668
|
want_profile: wantProfile,
|
|
741
669
|
reason,
|
|
@@ -747,44 +675,9 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
747
675
|
}
|
|
748
676
|
}
|
|
749
677
|
cleanDeadProfileFragments(cwd);
|
|
750
|
-
let chrome = findBundledChromium();
|
|
751
|
-
if (!chrome) {
|
|
752
|
-
const ensured = ensureBundledChromium(pw);
|
|
753
|
-
if (ensured.exe) chrome = ensured.exe;
|
|
754
|
-
}
|
|
755
|
-
if (!chrome) chrome = findChrome();
|
|
756
|
-
if (!chrome) throw new Error('No chromium binary available. Run: npx playwright install chromium');
|
|
757
678
|
const profileDir = acquireProfileDir(cwd);
|
|
758
|
-
const
|
|
759
|
-
const
|
|
760
|
-
`--remote-debugging-port=${port}`,
|
|
761
|
-
`--user-data-dir=${profileDir}`,
|
|
762
|
-
'--no-first-run',
|
|
763
|
-
'--no-default-browser-check',
|
|
764
|
-
'--disable-features=Translate',
|
|
765
|
-
];
|
|
766
|
-
// Chrome itself is GUI but its remote-debugging port creates conhost
|
|
767
|
-
// when launched from a console-attached parent. CREATE_NO_WINDOW
|
|
768
|
-
// (0x08000000) | DETACHED_PROCESS (0x00000008) prevents the helper
|
|
769
|
-
// processes (sandbox, GPU, network service) from allocating consoles.
|
|
770
|
-
// Inherited by the entire chrome subprocess tree on Windows.
|
|
771
|
-
const child = spawn(chrome, chromeArgs, {
|
|
772
|
-
detached: true,
|
|
773
|
-
stdio: 'ignore',
|
|
774
|
-
windowsHide: true,
|
|
775
|
-
...(process.platform === 'win32' ? { creationFlags: 0x08000000 | 0x00000008 } : {}),
|
|
776
|
-
});
|
|
777
|
-
const chromePid = child.pid;
|
|
778
|
-
child.unref();
|
|
779
|
-
const deadline = Date.now() + 10000;
|
|
780
|
-
let alive = false;
|
|
781
|
-
while (Date.now() < deadline) {
|
|
782
|
-
if (isPortAliveSync(port)) { alive = true; break; }
|
|
783
|
-
sleepSync(300);
|
|
784
|
-
}
|
|
785
|
-
if (!alive) throw new Error(`Chrome failed to open debug port ${port}`);
|
|
786
|
-
const newR = runPlaywriter(pw, ['session', 'new', '--direct', `localhost:${port}`], 30000);
|
|
787
|
-
if (newR.status !== 0) throw new Error(`managed browser session start failed: ${scrubBrowserRunnerText(newR.stderr || newR.stdout || 'unknown')}`);
|
|
679
|
+
const browserPid = startManagedBrowser(pw, profileDir);
|
|
680
|
+
const newR = waitForExtensionReady(pw, 30000);
|
|
788
681
|
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
789
682
|
const out = stripAnsi(newR.stdout || '').trim();
|
|
790
683
|
let pwSessionId = null;
|
|
@@ -798,7 +691,7 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
798
691
|
try { const j = JSON.parse(out); pwSessionId = j.id || j.session_id || j.session; } catch (_) {}
|
|
799
692
|
}
|
|
800
693
|
if (!pwSessionId) throw new Error(`could not parse managed browser session id from: ${scrubBrowserRunnerText(out)}`);
|
|
801
|
-
ports[claudeSessionId] = {
|
|
694
|
+
ports[claudeSessionId] = { profileDir, pid: browserPid };
|
|
802
695
|
sessions[claudeSessionId] = [pwSessionId];
|
|
803
696
|
writeJsonFile(portsFile, ports);
|
|
804
697
|
writeJsonFile(sessionsFile, sessions);
|
|
@@ -1412,12 +1305,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1412
1305
|
if (!pw) return writeWasmJson(instanceRef.value, { ok: false, error: 'managed browser session runner not available' });
|
|
1413
1306
|
if (body.startsWith('session ')) {
|
|
1414
1307
|
const parts = body.slice(8).trim().split(/\s+/);
|
|
1415
|
-
const
|
|
1416
|
-
const existing = ports[sessionId];
|
|
1417
|
-
const directArgs = (existing && existing.port && isPortAliveSync(existing.port))
|
|
1418
|
-
? [`--direct=localhost:${existing.port}`]
|
|
1419
|
-
: [];
|
|
1420
|
-
const r = runPlaywriter(pw, ['session', ...parts, ...directArgs], 30000);
|
|
1308
|
+
const r = runPlaywriter(pw, ['session', ...parts], 30000);
|
|
1421
1309
|
return writeWasmJson(instanceRef.value, {
|
|
1422
1310
|
ok: r.status === 0,
|
|
1423
1311
|
stdout: scrubBrowserRunnerText(r.stdout || ''),
|
|
@@ -1426,12 +1314,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1426
1314
|
});
|
|
1427
1315
|
}
|
|
1428
1316
|
const pwSessionId = getOrCreateBrowserSession(cwd, sessionId, pw);
|
|
1429
|
-
const
|
|
1430
|
-
const livePort = portsAfter[sessionId] && portsAfter[sessionId].port;
|
|
1431
|
-
const directArgs = (livePort && isPortAliveSync(livePort))
|
|
1432
|
-
? [`--direct=localhost:${livePort}`]
|
|
1433
|
-
: [];
|
|
1434
|
-
const r = runPlaywriter(pw, ['-s', pwSessionId, '--timeout', '14000', ...directArgs, '-e', body], 60000);
|
|
1317
|
+
const r = runPlaywriter(pw, ['-s', pwSessionId, '--timeout', '14000', '-e', body], 60000);
|
|
1435
1318
|
return writeWasmJson(instanceRef.value, {
|
|
1436
1319
|
ok: r.status === 0,
|
|
1437
1320
|
stdout: scrubBrowserRunnerText(r.stdout || ''),
|
|
@@ -1800,9 +1683,9 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1800
1683
|
const ports = readJsonFile(browserPortsFile(process.cwd()), {});
|
|
1801
1684
|
let browserAlive = false;
|
|
1802
1685
|
for (const entry of Object.values(ports)) {
|
|
1803
|
-
if (entry && Number.isFinite(entry.
|
|
1686
|
+
if (entry && Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid)) { browserAlive = true; break; }
|
|
1804
1687
|
}
|
|
1805
|
-
if (browserAlive) { markActivity('browser-
|
|
1688
|
+
if (browserAlive) { markActivity('browser-pid-alive'); return; }
|
|
1806
1689
|
} catch (_) {}
|
|
1807
1690
|
try {
|
|
1808
1691
|
let anyRunning = false;
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1270",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"gm.json"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"gm-plugkit": "^2.0.
|
|
42
|
+
"gm-plugkit": "^2.0.1270"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=16.0.0"
|