gm-skill 2.0.1269 → 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 -158
- 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,106 +420,6 @@ function writeJsonFile(fp, value) {
|
|
|
420
420
|
try { fs.writeFileSync(fp, JSON.stringify(value, null, 2)); } catch (_) {}
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
function bundledBrowserCacheRoots() {
|
|
424
|
-
const roots = [];
|
|
425
|
-
const envOverride = process.env.PLAYWRIGHT_BROWSERS_PATH;
|
|
426
|
-
if (envOverride) roots.push(envOverride);
|
|
427
|
-
if (process.platform === 'win32') {
|
|
428
|
-
if (process.env.LOCALAPPDATA) roots.push(path.join(process.env.LOCALAPPDATA, 'ms-playwright'));
|
|
429
|
-
} else if (process.platform === 'darwin') {
|
|
430
|
-
if (process.env.HOME) roots.push(path.join(process.env.HOME, 'Library', 'Caches', 'ms-playwright'));
|
|
431
|
-
} else {
|
|
432
|
-
if (process.env.HOME) roots.push(path.join(process.env.HOME, '.cache', 'ms-playwright'));
|
|
433
|
-
}
|
|
434
|
-
return roots.filter(r => r && fs.existsSync(r));
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function chromiumExeFromCacheRoot(root) {
|
|
438
|
-
try {
|
|
439
|
-
const entries = fs.readdirSync(root)
|
|
440
|
-
.filter(n => /^chromium-\d+$/.test(n))
|
|
441
|
-
.sort((a, b) => parseInt(b.split('-')[1], 10) - parseInt(a.split('-')[1], 10));
|
|
442
|
-
const subdirs = process.platform === 'win32'
|
|
443
|
-
? ['chrome-win64', 'chrome-win']
|
|
444
|
-
: process.platform === 'darwin'
|
|
445
|
-
? ['chrome-mac/Chromium.app/Contents/MacOS/Chromium']
|
|
446
|
-
: ['chrome-linux'];
|
|
447
|
-
const exeName = process.platform === 'win32' ? 'chrome.exe'
|
|
448
|
-
: process.platform === 'darwin' ? null
|
|
449
|
-
: 'chrome';
|
|
450
|
-
for (const e of entries) {
|
|
451
|
-
for (const sub of subdirs) {
|
|
452
|
-
const p = exeName ? path.join(root, e, sub, exeName) : path.join(root, e, sub);
|
|
453
|
-
if (fs.existsSync(p)) return p;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
} catch (_) {}
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function findBundledChromium() {
|
|
461
|
-
for (const root of bundledBrowserCacheRoots()) {
|
|
462
|
-
const exe = chromiumExeFromCacheRoot(root);
|
|
463
|
-
if (exe) return exe;
|
|
464
|
-
}
|
|
465
|
-
try {
|
|
466
|
-
const npmR = spawnSync('npm', ['root', '-g'], { encoding: 'utf-8', shell: true, windowsHide: true, timeout: 5000 });
|
|
467
|
-
if (npmR.status === 0 && npmR.stdout.trim()) {
|
|
468
|
-
const root = npmR.stdout.trim().split(/\r?\n/).pop();
|
|
469
|
-
const localBrowsers = path.join(root, 'playwriter', 'node_modules', '@xmorse', 'playwright-core', '.local-browsers');
|
|
470
|
-
if (fs.existsSync(localBrowsers)) {
|
|
471
|
-
const exe = chromiumExeFromCacheRoot(localBrowsers);
|
|
472
|
-
if (exe) return exe;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
} catch (_) {}
|
|
476
|
-
return null;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function ensureBundledChromium(pw) {
|
|
480
|
-
const existing = findBundledChromium();
|
|
481
|
-
if (existing) return { exe: existing, installed: false };
|
|
482
|
-
if (!pw || !pw.cmd) {
|
|
483
|
-
return { exe: null, installed: false, error: 'playwriter not available to install browser' };
|
|
484
|
-
}
|
|
485
|
-
const installer = { cmd: pw.cmd, args: [...pw.baseArgs, 'install', 'chromium'], shell: pw.shell };
|
|
486
|
-
logEvent('bootstrap', 'browser.bundled-install.start', { via: 'playwriter' });
|
|
487
|
-
const r = spawnSync(installer.cmd, installer.args, {
|
|
488
|
-
encoding: 'utf-8',
|
|
489
|
-
timeout: 600000,
|
|
490
|
-
shell: installer.shell,
|
|
491
|
-
windowsHide: true,
|
|
492
|
-
});
|
|
493
|
-
logEvent('bootstrap', 'browser.bundled-install.done', { status: r.status });
|
|
494
|
-
const after = findBundledChromium();
|
|
495
|
-
if (after) return { exe: after, installed: true };
|
|
496
|
-
return { exe: null, installed: false, error: r.stderr || r.stdout || 'install failed' };
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function findChrome() {
|
|
500
|
-
const bundled = findBundledChromium();
|
|
501
|
-
if (bundled) return bundled;
|
|
502
|
-
if (process.platform === 'win32') {
|
|
503
|
-
const candidates = [
|
|
504
|
-
path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
505
|
-
path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
506
|
-
path.join(process.env.LOCALAPPDATA || '', 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
507
|
-
];
|
|
508
|
-
for (const c of candidates) { if (c && fs.existsSync(c)) return c; }
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
if (process.platform === 'darwin') {
|
|
512
|
-
const mac = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
513
|
-
if (fs.existsSync(mac)) return mac;
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
for (const bin of ['google-chrome', 'chromium', 'chromium-browser']) {
|
|
517
|
-
const r = spawnSync('which', [bin], { encoding: 'utf-8' });
|
|
518
|
-
if (r.status === 0 && r.stdout.trim()) return r.stdout.trim();
|
|
519
|
-
}
|
|
520
|
-
return null;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
423
|
function findPlaywriter() {
|
|
524
424
|
const npmR = spawnSync('npm', ['root', '-g'], { encoding: 'utf-8', shell: true });
|
|
525
425
|
if (npmR.status === 0 && npmR.stdout.trim()) {
|
|
@@ -716,6 +616,35 @@ function scrubBrowserRunnerText(s) {
|
|
|
716
616
|
return t;
|
|
717
617
|
}
|
|
718
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
|
+
|
|
719
648
|
function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
720
649
|
migrateLegacyBrowserState(cwd);
|
|
721
650
|
const portsFile = browserPortsFile(cwd);
|
|
@@ -723,20 +652,18 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
723
652
|
const ports = readJsonFile(portsFile, {});
|
|
724
653
|
const sessions = readJsonFile(sessionsFile, {});
|
|
725
654
|
const existing = ports[claudeSessionId];
|
|
726
|
-
if (existing && existing.
|
|
655
|
+
if (existing && existing.pid) {
|
|
727
656
|
const wantProfile = path.join(cwd, '.gm', 'browser-profile');
|
|
728
|
-
const pidOk =
|
|
657
|
+
const pidOk = isProcessAliveSync(existing.pid);
|
|
729
658
|
const profileOk = !existing.profileDir || existing.profileDir === wantProfile || existing.profileDir.startsWith(path.join(cwd, '.gm', 'browser-profile'));
|
|
730
|
-
|
|
731
|
-
if (pidOk && profileOk && portOk) {
|
|
659
|
+
if (pidOk && profileOk) {
|
|
732
660
|
const pwIds = sessions[claudeSessionId] || [];
|
|
733
661
|
if (pwIds.length > 0) return pwIds[0];
|
|
734
662
|
} else {
|
|
735
|
-
const reason = !pidOk ? 'pid-dead' :
|
|
663
|
+
const reason = !pidOk ? 'pid-dead' : 'profile-drift';
|
|
736
664
|
logEvent('hook', 'deviation.browser-profile-collision', {
|
|
737
665
|
sid: claudeSessionId,
|
|
738
666
|
stale_pid: existing.pid || null,
|
|
739
|
-
stale_port: existing.port || null,
|
|
740
667
|
stale_profile: existing.profileDir || null,
|
|
741
668
|
want_profile: wantProfile,
|
|
742
669
|
reason,
|
|
@@ -748,44 +675,9 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
748
675
|
}
|
|
749
676
|
}
|
|
750
677
|
cleanDeadProfileFragments(cwd);
|
|
751
|
-
let chrome = findBundledChromium();
|
|
752
|
-
if (!chrome) {
|
|
753
|
-
const ensured = ensureBundledChromium(pw);
|
|
754
|
-
if (ensured.exe) chrome = ensured.exe;
|
|
755
|
-
}
|
|
756
|
-
if (!chrome) chrome = findChrome();
|
|
757
|
-
if (!chrome) throw new Error('No chromium binary available. Run: bun x playwriter@latest install chromium');
|
|
758
678
|
const profileDir = acquireProfileDir(cwd);
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
`--remote-debugging-port=${port}`,
|
|
762
|
-
`--user-data-dir=${profileDir}`,
|
|
763
|
-
'--no-first-run',
|
|
764
|
-
'--no-default-browser-check',
|
|
765
|
-
'--disable-features=Translate',
|
|
766
|
-
];
|
|
767
|
-
// Chrome itself is GUI but its remote-debugging port creates conhost
|
|
768
|
-
// when launched from a console-attached parent. CREATE_NO_WINDOW
|
|
769
|
-
// (0x08000000) | DETACHED_PROCESS (0x00000008) prevents the helper
|
|
770
|
-
// processes (sandbox, GPU, network service) from allocating consoles.
|
|
771
|
-
// Inherited by the entire chrome subprocess tree on Windows.
|
|
772
|
-
const child = spawn(chrome, chromeArgs, {
|
|
773
|
-
detached: true,
|
|
774
|
-
stdio: 'ignore',
|
|
775
|
-
windowsHide: true,
|
|
776
|
-
...(process.platform === 'win32' ? { creationFlags: 0x08000000 | 0x00000008 } : {}),
|
|
777
|
-
});
|
|
778
|
-
const chromePid = child.pid;
|
|
779
|
-
child.unref();
|
|
780
|
-
const deadline = Date.now() + 10000;
|
|
781
|
-
let alive = false;
|
|
782
|
-
while (Date.now() < deadline) {
|
|
783
|
-
if (isPortAliveSync(port)) { alive = true; break; }
|
|
784
|
-
sleepSync(300);
|
|
785
|
-
}
|
|
786
|
-
if (!alive) throw new Error(`Chrome failed to open debug port ${port}`);
|
|
787
|
-
const newR = runPlaywriter(pw, ['session', 'new', '--direct', `localhost:${port}`], 30000);
|
|
788
|
-
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);
|
|
789
681
|
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
790
682
|
const out = stripAnsi(newR.stdout || '').trim();
|
|
791
683
|
let pwSessionId = null;
|
|
@@ -799,7 +691,7 @@ function getOrCreateBrowserSession(cwd, claudeSessionId, pw) {
|
|
|
799
691
|
try { const j = JSON.parse(out); pwSessionId = j.id || j.session_id || j.session; } catch (_) {}
|
|
800
692
|
}
|
|
801
693
|
if (!pwSessionId) throw new Error(`could not parse managed browser session id from: ${scrubBrowserRunnerText(out)}`);
|
|
802
|
-
ports[claudeSessionId] = {
|
|
694
|
+
ports[claudeSessionId] = { profileDir, pid: browserPid };
|
|
803
695
|
sessions[claudeSessionId] = [pwSessionId];
|
|
804
696
|
writeJsonFile(portsFile, ports);
|
|
805
697
|
writeJsonFile(sessionsFile, sessions);
|
|
@@ -1413,12 +1305,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1413
1305
|
if (!pw) return writeWasmJson(instanceRef.value, { ok: false, error: 'managed browser session runner not available' });
|
|
1414
1306
|
if (body.startsWith('session ')) {
|
|
1415
1307
|
const parts = body.slice(8).trim().split(/\s+/);
|
|
1416
|
-
const
|
|
1417
|
-
const existing = ports[sessionId];
|
|
1418
|
-
const directArgs = (existing && existing.port && isPortAliveSync(existing.port))
|
|
1419
|
-
? [`--direct=localhost:${existing.port}`]
|
|
1420
|
-
: [];
|
|
1421
|
-
const r = runPlaywriter(pw, ['session', ...parts, ...directArgs], 30000);
|
|
1308
|
+
const r = runPlaywriter(pw, ['session', ...parts], 30000);
|
|
1422
1309
|
return writeWasmJson(instanceRef.value, {
|
|
1423
1310
|
ok: r.status === 0,
|
|
1424
1311
|
stdout: scrubBrowserRunnerText(r.stdout || ''),
|
|
@@ -1427,12 +1314,7 @@ function makeHostFunctions(instanceRef) {
|
|
|
1427
1314
|
});
|
|
1428
1315
|
}
|
|
1429
1316
|
const pwSessionId = getOrCreateBrowserSession(cwd, sessionId, pw);
|
|
1430
|
-
const
|
|
1431
|
-
const livePort = portsAfter[sessionId] && portsAfter[sessionId].port;
|
|
1432
|
-
const directArgs = (livePort && isPortAliveSync(livePort))
|
|
1433
|
-
? [`--direct=localhost:${livePort}`]
|
|
1434
|
-
: [];
|
|
1435
|
-
const r = runPlaywriter(pw, ['-s', pwSessionId, '--timeout', '14000', ...directArgs, '-e', body], 60000);
|
|
1317
|
+
const r = runPlaywriter(pw, ['-s', pwSessionId, '--timeout', '14000', '-e', body], 60000);
|
|
1436
1318
|
return writeWasmJson(instanceRef.value, {
|
|
1437
1319
|
ok: r.status === 0,
|
|
1438
1320
|
stdout: scrubBrowserRunnerText(r.stdout || ''),
|
|
@@ -1801,9 +1683,9 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
1801
1683
|
const ports = readJsonFile(browserPortsFile(process.cwd()), {});
|
|
1802
1684
|
let browserAlive = false;
|
|
1803
1685
|
for (const entry of Object.values(ports)) {
|
|
1804
|
-
if (entry && Number.isFinite(entry.
|
|
1686
|
+
if (entry && Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid)) { browserAlive = true; break; }
|
|
1805
1687
|
}
|
|
1806
|
-
if (browserAlive) { markActivity('browser-
|
|
1688
|
+
if (browserAlive) { markActivity('browser-pid-alive'); return; }
|
|
1807
1689
|
} catch (_) {}
|
|
1808
1690
|
try {
|
|
1809
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"
|