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 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.1269` — 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,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.port) {
655
+ if (existing && existing.pid) {
727
656
  const wantProfile = path.join(cwd, '.gm', 'browser-profile');
728
- const pidOk = existing.pid && isProcessAliveSync(existing.pid);
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
- const portOk = isPortAliveSync(existing.port);
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' : !profileOk ? 'profile-drift' : 'port-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 port = findFreePortSync();
760
- const chromeArgs = [
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] = { port, profileDir, pid: chromePid };
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 ports = readJsonFile(browserPortsFile(cwd), {});
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 portsAfter = readJsonFile(browserPortsFile(cwd), {});
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.port) && isPortAliveSync(entry.port)) { browserAlive = true; break; }
1686
+ if (entry && Number.isFinite(entry.pid) && isProcessAliveSync(entry.pid)) { browserAlive = true; break; }
1805
1687
  }
1806
- if (browserAlive) { markActivity('browser-port-alive'); return; }
1688
+ if (browserAlive) { markActivity('browser-pid-alive'); return; }
1807
1689
  } catch (_) {}
1808
1690
  try {
1809
1691
  let anyRunning = false;
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1269",
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.1269",
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.1269"
42
+ "gm-plugkit": "^2.0.1270"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"