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 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.1268` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
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.port) {
655
+ if (existing && existing.pid) {
726
656
  const wantProfile = path.join(cwd, '.gm', 'browser-profile');
727
- const pidOk = existing.pid && isProcessAliveSync(existing.pid);
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
- const portOk = isPortAliveSync(existing.port);
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' : !profileOk ? 'profile-drift' : 'port-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 port = findFreePortSync();
759
- const chromeArgs = [
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] = { port, profileDir, pid: chromePid };
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 ports = readJsonFile(browserPortsFile(cwd), {});
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 portsAfter = readJsonFile(browserPortsFile(cwd), {});
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.port) && isPortAliveSync(entry.port)) { browserAlive = true; break; }
1686
+ if (entry && Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid)) { browserAlive = true; break; }
1804
1687
  }
1805
- if (browserAlive) { markActivity('browser-port-alive'); return; }
1688
+ if (browserAlive) { markActivity('browser-pid-alive'); return; }
1806
1689
  } catch (_) {}
1807
1690
  try {
1808
1691
  let anyRunning = false;
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1268",
3
+ "version": "2.0.1270",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1268",
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.1268"
42
+ "gm-plugkit": "^2.0.1270"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"