gm-codex 2.0.951 → 2.0.953

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-codex",
3
- "version": "2.0.951",
3
+ "version": "2.0.953",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",
package/bin/bootstrap.js CHANGED
@@ -299,7 +299,7 @@ function pruneOldVersions(root, keepVersion, keepRtkVersion) {
299
299
  if (fs.existsSync(lock) && !isLockStale(lock)) continue;
300
300
  if (fs.existsSync(lock)) { try { fs.unlinkSync(lock); } catch (_) {} }
301
301
  try {
302
- fs.rmSync(dir, { recursive: true, force: true });
302
+ fs.rmSync(dir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 });
303
303
  log(`pruned ${dir}`);
304
304
  } catch (err) { log(`prune skip ${dir}: ${err.message}`); }
305
305
  }
@@ -327,12 +327,14 @@ async function bootstrap(opts) {
327
327
 
328
328
  if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
329
329
  if (!opts.silent) log(`cache hit: ${finalPath}`);
330
+ proactiveKillForNewInstall(version, finalPath);
330
331
  pruneOldVersions(root, version, readRtkVersion(wrapperDir));
331
332
  return finalPath;
332
333
  }
333
334
 
334
335
  if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
335
336
  if (!opts.silent) log(`cache heal (sha match): ${finalPath}`);
337
+ proactiveKillForNewInstall(version, finalPath);
336
338
  pruneOldVersions(root, version, readRtkVersion(wrapperDir));
337
339
  try { await bootstrapRtk(verDir, version, wrapperDir, opts.silent, root); }
338
340
  catch (err) { log(`rtk fetch skipped: ${err.message}`); }
@@ -343,11 +345,13 @@ async function bootstrap(opts) {
343
345
  acquireLock(lockPath);
344
346
  try {
345
347
  if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
348
+ proactiveKillForNewInstall(version, finalPath);
346
349
  pruneOldVersions(root, version, readRtkVersion(wrapperDir));
347
350
  return finalPath;
348
351
  }
349
352
  if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
350
353
  log(`cache heal (sha match) under lock: ${finalPath}`);
354
+ proactiveKillForNewInstall(version, finalPath);
351
355
  pruneOldVersions(root, version, readRtkVersion(wrapperDir));
352
356
  try { await bootstrapRtk(verDir, version, wrapperDir, opts.silent, root); }
353
357
  catch (err) { log(`rtk fetch skipped: ${err.message}`); }
@@ -392,8 +396,8 @@ async function bootstrap(opts) {
392
396
  fs.writeFileSync(okSentinel, new Date().toISOString());
393
397
  log(`installed ${finalPath}`);
394
398
  obsEvent('bootstrap', 'install.done', { path: finalPath, version, kind: 'plugkit' });
399
+ proactiveKillForNewInstall(version, finalPath);
395
400
  pruneOldVersions(root, version, readRtkVersion(wrapperDir));
396
- proactiveKillForNewInstall(version);
397
401
  try { await bootstrapRtk(verDir, version, wrapperDir, opts.silent, root); }
398
402
  catch (err) { log(`rtk fetch skipped: ${err.message}`); }
399
403
  return finalPath;
@@ -515,7 +519,7 @@ function killPid(pid) {
515
519
  if (os.platform() === 'win32' && pidAlive(pid)) {
516
520
  try {
517
521
  const { spawnSync } = require('child_process');
518
- spawnSync('taskkill', ['/F', '/PID', String(pid)], { stdio: 'ignore', windowsHide: true });
522
+ spawnSync('taskkill', ['/F', '/PID', String(pid)], { stdio: 'ignore', windowsHide: true, timeout: 3000, killSignal: 'SIGKILL' });
519
523
  } catch (_) {}
520
524
  }
521
525
  return true;
@@ -539,6 +543,73 @@ function killRunningDaemons(reason) {
539
543
  return killedPids;
540
544
  }
541
545
 
546
+ function listRunningPlugkitImagePaths() {
547
+ const out = [];
548
+ try {
549
+ const { spawnSync } = require('child_process');
550
+ if (os.platform() === 'win32') {
551
+ let parsed = null;
552
+ try {
553
+ const p = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', "Get-Process plugkit* -ErrorAction SilentlyContinue | Select-Object Id,Path | ConvertTo-Json -Compress"], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL' });
554
+ const text = ((p && p.stdout) || '').trim();
555
+ if (text) {
556
+ const j = JSON.parse(text);
557
+ parsed = Array.isArray(j) ? j : [j];
558
+ }
559
+ } catch (_) {}
560
+ if (parsed) {
561
+ for (const item of parsed) {
562
+ if (!item) continue;
563
+ const pid = parseInt(item.Id, 10);
564
+ if (!Number.isFinite(pid)) continue;
565
+ out.push({ pid, path: (item.Path || '').trim() });
566
+ }
567
+ } else {
568
+ const r = spawnSync('tasklist', ['/FI', 'IMAGENAME eq plugkit*', '/FO', 'CSV', '/NH'], { windowsHide: true, encoding: 'utf8', timeout: 5000, killSignal: 'SIGKILL' });
569
+ const text = (r && r.stdout) || '';
570
+ const seen = new Set();
571
+ for (const line of text.split(/\r?\n/)) {
572
+ const m = line.match(/^"([^"]+)","(\d+)"/);
573
+ if (!m) continue;
574
+ const pid = parseInt(m[2], 10);
575
+ if (!Number.isFinite(pid) || seen.has(pid)) continue;
576
+ seen.add(pid);
577
+ out.push({ pid, path: '' });
578
+ }
579
+ }
580
+ } else if (os.platform() === 'linux') {
581
+ let entries = [];
582
+ try { entries = fs.readdirSync('/proc'); } catch (_) {}
583
+ for (const e of entries) {
584
+ if (!/^\d+$/.test(e)) continue;
585
+ const pid = parseInt(e, 10);
586
+ let comm = '';
587
+ try { comm = fs.readFileSync(`/proc/${pid}/comm`, 'utf8').trim(); } catch (_) { continue; }
588
+ if (!/^plugkit/i.test(comm)) continue;
589
+ let imagePath = '';
590
+ try { imagePath = fs.readlinkSync(`/proc/${pid}/exe`); } catch (_) {}
591
+ out.push({ pid, path: imagePath });
592
+ }
593
+ } else {
594
+ const r = spawnSync('ps', ['-axo', 'pid=,comm='], { encoding: 'utf8' });
595
+ const text = (r && r.stdout) || '';
596
+ for (const line of text.split(/\r?\n/)) {
597
+ const m = line.match(/^\s*(\d+)\s+(.+?)\s*$/);
598
+ if (!m) continue;
599
+ if (!/plugkit/i.test(m[2])) continue;
600
+ const pid = parseInt(m[1], 10);
601
+ let imagePath = '';
602
+ try {
603
+ const p = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8' });
604
+ imagePath = ((p && p.stdout) || '').trim().split(/\s+/)[0] || '';
605
+ } catch (_) {}
606
+ out.push({ pid, path: imagePath });
607
+ }
608
+ }
609
+ } catch (_) {}
610
+ return out;
611
+ }
612
+
542
613
  function killSpoolWatcherInCwd(reason) {
543
614
  try {
544
615
  const pidPath = path.join(process.cwd(), '.gm', 'exec-spool', '.watcher.pid');
@@ -554,19 +625,27 @@ function killSpoolWatcherInCwd(reason) {
554
625
  return null;
555
626
  }
556
627
 
557
- function proactiveKillForNewInstall(installedVersion) {
628
+ function proactiveKillForNewInstall(installedVersion, finalPath) {
558
629
  try {
559
- const recorded = readDaemonVersion();
560
- if (recorded === installedVersion) return;
561
- const reason = `install:${recorded || 'none'}->${installedVersion}`;
562
- const killed = killRunningDaemons(reason);
563
- const watcherPid = killSpoolWatcherInCwd(reason);
564
- if (killed.length || watcherPid) {
565
- const parts = [];
566
- if (killed.length) parts.push(`daemon pid=${killed.join(',')} v${recorded || '?'}`);
567
- if (watcherPid) parts.push(`watcher pid=${watcherPid}`);
568
- try { process.stderr.write(`[bootstrap] killed stale ${parts.join(' + ')}, new binary ${installedVersion} ready\n`); } catch (_) {}
630
+ const reason = `install:v${installedVersion}`;
631
+ const target = finalPath ? path.resolve(finalPath).toLowerCase() : null;
632
+ const cacheRootNorm = (() => {
633
+ try { return path.resolve(cacheRoot()).toLowerCase(); } catch (_) { return null; }
634
+ })();
635
+ const procs = listRunningPlugkitImagePaths();
636
+ for (const { pid, path: imagePath } of procs) {
637
+ if (!Number.isFinite(pid) || pid === process.pid) continue;
638
+ if (!imagePath) continue;
639
+ const norm = path.resolve(imagePath).toLowerCase();
640
+ if (target && norm === target) continue;
641
+ if (!cacheRootNorm || !norm.startsWith(cacheRootNorm + path.sep.toLowerCase())) continue;
642
+ if (killPid(pid)) {
643
+ try { process.stderr.write(`[bootstrap] killed stale daemon pid=${pid} path=${imagePath} (current install: v${installedVersion})\n`); } catch (_) {}
644
+ obsEvent('bootstrap', 'daemon.killed', { pid, oldPath: imagePath, installedVersion, mechanism: 'process-path' });
645
+ }
569
646
  }
647
+ killRunningDaemons(reason);
648
+ killSpoolWatcherInCwd(reason);
570
649
  writeDaemonVersion(installedVersion);
571
650
  } catch (_) {}
572
651
  }
@@ -576,6 +655,11 @@ function proactiveKillForNewInstall(installedVersion) {
576
655
  function killStaleDaemonIfVersionChanged(wrapperDir) {
577
656
  let currentVersion;
578
657
  try { currentVersion = readVersionFile(wrapperDir); } catch (_) { return; }
658
+ const cached = resolveCachedBinary({ wrapperDir, version: currentVersion });
659
+ if (cached) {
660
+ proactiveKillForNewInstall(currentVersion, cached);
661
+ return;
662
+ }
579
663
  const recorded = readDaemonVersion();
580
664
  if (recorded === currentVersion) return;
581
665
  if (recorded) killRunningDaemons(`version_change:${recorded}->${currentVersion}`);
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.951",
3
+ "version": "2.0.953",
4
4
  "description": "State machine agent with hooks, 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-codex",
3
- "version": "2.0.951",
3
+ "version": "2.0.953",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.951",
3
+ "version": "2.0.953",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",