gm-plugkit 2.0.2059 → 2.0.2061

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.
Files changed (2) hide show
  1. package/bootstrap.js +61 -186
  2. package/package.json +1 -1
package/bootstrap.js CHANGED
@@ -6,8 +6,35 @@ const path = require('path');
6
6
  const os = require('os');
7
7
  const crypto = require('crypto');
8
8
  const { spawn, spawnSync } = require('child_process');
9
- const { logEvent: _sharedLogEvent } = require('./gm-log');
10
- const { pidCommandLineForKillGuard: _sharedPidCommandLine, ensureDir, pidAlive, sha256OfFile, sha256OfFileSync } = require('./gm-process');
9
+ const { pidAlive, sha256OfFile, sha256OfFileSync } = require('./gm-process');
10
+ const shared = require('./bootstrap-shared');
11
+ const {
12
+ obsEvent,
13
+ cacheRoot,
14
+ fallbackCacheRoot,
15
+ gmToolsDir,
16
+ ensureDir,
17
+ acquireLock,
18
+ releaseLock,
19
+ isLockStale,
20
+ pruneOldVersions,
21
+ healIfShaMatches,
22
+ daemonVersionSentinel,
23
+ readDaemonVersion,
24
+ writeDaemonVersion,
25
+ pidCommandLineForKillGuard,
26
+ pidIsPlugkitProcess,
27
+ writeKillAttribution,
28
+ killPid,
29
+ killSpoolWatcherInCwd,
30
+ proactiveKillForNewInstall,
31
+ } = shared;
32
+
33
+ // ensureNextStepWiring here is intentionally NOT imported from
34
+ // bootstrap-shared -- this file's version does strictly more (CLAUDE.md
35
+ // prepend, AGENTS.md append, managed .npmignore block) than bin/bootstrap.js's
36
+ // leaner one, so it is genuinely-different logic, not accidental drift, and
37
+ // stays local to this file.
11
38
 
12
39
  function resolveWindowsExe(cmd) {
13
40
  if (process.platform !== 'win32') return cmd;
@@ -40,10 +67,6 @@ function log(msg) {
40
67
  try { process.stderr.write(`[gm-plugkit] ${msg}\n`); } catch (_) {}
41
68
  }
42
69
 
43
- function obsEvent(subsystem, event, fields) {
44
- _sharedLogEvent(subsystem, event, fields, { cwd: false });
45
- }
46
-
47
70
  function writeBootstrapError(spec) {
48
71
  try {
49
72
  const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
@@ -54,6 +77,7 @@ function writeBootstrapError(spec) {
54
77
  }
55
78
 
56
79
  function clearBootstrapError() {
80
+ // best-effort, missing file is fine
57
81
  try {
58
82
  const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
59
83
  fs.unlinkSync(path.join(projectDir, '.gm', 'exec-spool', '.bootstrap-error.json'));
@@ -79,7 +103,12 @@ function instructionsManifestPath(cwd) {
79
103
 
80
104
  function readInstructionsManifest(cwd) {
81
105
  try { return JSON.parse(fs.readFileSync(instructionsManifestPath(cwd), 'utf-8')); }
82
- catch (_) { return {}; }
106
+ catch (e) {
107
+ if (e && e.code !== 'ENOENT') {
108
+ obsEvent('bootstrap', 'instructions-bundle.manifest-read-failed', { error: e.message });
109
+ }
110
+ return {};
111
+ }
83
112
  }
84
113
 
85
114
  function writeInstructionsManifest(cwd, manifest) {
@@ -229,31 +258,6 @@ function ensureNextStepWiring(cwd) {
229
258
  }
230
259
 
231
260
 
232
- function cacheRoot() {
233
- const home = os.homedir();
234
- if (process.env.PLUGKIT_CACHE_DIR) return process.env.PLUGKIT_CACHE_DIR;
235
- if (os.platform() === 'win32') {
236
- const base = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
237
- return path.join(base, 'plugkit', 'bin');
238
- }
239
- if (os.platform() === 'darwin') return path.join(home, 'Library', 'Caches', 'plugkit', 'bin');
240
- const xdg = process.env.XDG_CACHE_HOME || path.join(home, '.cache');
241
- return path.join(xdg, 'plugkit', 'bin');
242
- }
243
-
244
- function fallbackCacheRoot() {
245
- return path.join(os.tmpdir(), 'plugkit-cache', 'bin');
246
- }
247
-
248
- function gmToolsDir() {
249
- const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
250
- const primary = path.join(home, '.gm-tools');
251
- const fallback = path.join(home, '.claude', 'gm-tools');
252
- if (fs.existsSync(primary)) return primary;
253
- if (fs.existsSync(fallback)) return fallback;
254
- return primary;
255
- }
256
-
257
261
  // Slim-artifact eligibility: plugkit-core's embed.rs probes host_vec_embed
258
262
  // before ever loading its wasm-embedded safetensors fallback (see
259
263
  // rs-plugkit/crates/plugkit-core/src/embed.rs::init_ctx) -- a slim build
@@ -311,6 +315,13 @@ function readVersionFile() {
311
315
  return fs.readFileSync(p, 'utf8').trim();
312
316
  }
313
317
 
318
+ // readVersionFile() throws loudly on its own (missing/unreadable file is a
319
+ // real, surfaced error) -- callers below that swallow it are choosing to
320
+ // no-op rather than propagate, so they log what they swallowed instead of
321
+ // going silent. This is the actual behavior at every call site already
322
+ // (killStaleDaemonIfVersionChanged just returns, ensureReady leaves
323
+ // pinnedVersion null) -- only the missing observability was the gap.
324
+
314
325
  function readShaManifest() {
315
326
  const p = path.join(wrapperDir, 'plugkit.sha256');
316
327
  if (!fs.existsSync(p)) return null;
@@ -340,37 +351,6 @@ function readShaManifest() {
340
351
  return out;
341
352
  }
342
353
 
343
- function acquireLock(lockPath) {
344
- const start = Date.now();
345
- for (;;) {
346
- try {
347
- const fd = fs.openSync(lockPath, 'wx');
348
- fs.writeSync(fd, String(process.pid));
349
- fs.closeSync(fd);
350
- return true;
351
- } catch (err) {
352
- if (err.code !== 'EEXIST') throw err;
353
- let stale = false;
354
- try {
355
- const st = fs.statSync(lockPath);
356
- if (Date.now() - st.mtimeMs > LOCK_STALE_MS) stale = true;
357
- const owner = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10);
358
- if (Number.isFinite(owner) && owner !== process.pid && !pidAlive(owner)) stale = true;
359
- } catch (_) { stale = true; }
360
- if (stale) {
361
- try { fs.unlinkSync(lockPath); } catch (_) {}
362
- continue;
363
- }
364
- if (Date.now() - start > ATTEMPT_TIMEOUT_MS) throw new Error(`lock wait timeout: ${lockPath}`);
365
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 2000);
366
- }
367
- }
368
- }
369
-
370
- function releaseLock(lockPath) {
371
- try { fs.unlinkSync(lockPath); } catch (_) {}
372
- }
373
-
374
354
  async function extractNpmPackageWasm(destPath, version) {
375
355
  const tempDir = path.join(path.dirname(destPath), '.npm-extract-' + Date.now());
376
356
  try {
@@ -395,7 +375,6 @@ async function extractNpmPackageWasm(destPath, version) {
395
375
  encoding: 'utf8',
396
376
  windowsHide: true,
397
377
  ...(isCmdShim ? { shell: true } : {}),
398
- ...(process.platform === 'win32' ? { creationFlags: 0x08000000 } : {}),
399
378
  });
400
379
 
401
380
  if (result.error) throw result.error;
@@ -533,125 +512,13 @@ async function extractNpmPackageWithRetry(destPath, version) {
533
512
  }
534
513
 
535
514
 
536
- function healIfShaMatches(binPath, expectedSha, sentinelPath, partialPath, kind) {
537
- if (!fs.existsSync(binPath)) return false;
538
- if (partialPath) { try { if (fs.existsSync(partialPath)) fs.unlinkSync(partialPath); } catch (_) {} }
539
- if (!expectedSha) return false;
540
- let got;
541
- try { got = sha256OfFileSync(binPath); }
542
- catch (_) { return false; }
543
- if (got !== expectedSha) {
544
- try { fs.unlinkSync(binPath); } catch (_) {}
545
- return false;
546
- }
547
- try { fs.writeFileSync(sentinelPath, new Date().toISOString()); } catch (_) { return false; }
548
- obsEvent('bootstrap', 'cache.heal', { path: binPath, kind });
549
- return true;
550
- }
551
-
552
- function daemonVersionSentinel() {
553
- const root = (() => {
554
- try { const r = cacheRoot(); ensureDir(r); return r; }
555
- catch (_) { const r = fallbackCacheRoot(); ensureDir(r); return r; }
556
- })();
557
- return path.join(root, '.daemon-version');
558
- }
559
-
560
- function readDaemonVersion() {
561
- try { return fs.readFileSync(daemonVersionSentinel(), 'utf8').trim(); }
562
- catch (_) { return null; }
563
- }
564
-
565
- function writeDaemonVersion(v) {
566
- try { fs.writeFileSync(daemonVersionSentinel(), String(v)); } catch (_) {}
567
- }
568
-
569
- function pidCommandLineForKillGuard(pid) {
570
- return _sharedPidCommandLine(pid);
571
- }
572
-
573
- function pidIsPlugkitProcess(pid) {
574
- return /agentplug-runner(\.exe)?/i.test(pidCommandLineForKillGuard(pid));
575
- }
576
-
577
- function writeKillAttribution(targetSpoolDir, info) {
578
- try {
579
- fs.mkdirSync(targetSpoolDir, { recursive: true });
580
- fs.writeFileSync(path.join(targetSpoolDir, '.kill-attribution.json'), JSON.stringify({ killer_pid: process.pid, killer_cwd: process.cwd(), killer_script: __filename, ts: Date.now(), ...info }, null, 2));
581
- } catch (_) {}
582
- }
583
-
584
- function killPid(pid) {
585
- if (!Number.isFinite(pid) || pid === process.pid || !pidAlive(pid)) return false;
586
- try { process.kill(pid, 'SIGTERM'); }
587
- catch (_) { try { process.kill(pid); } catch (_) {} }
588
- if (os.platform() === 'win32' && pidAlive(pid)) {
589
- try { spawnSync('taskkill', ['/F', '/PID', String(pid)], { stdio: 'ignore', windowsHide: true, timeout: 3000, killSignal: 'SIGKILL' }); } catch (_) {}
590
- }
591
- return true;
592
- }
593
-
594
- function killSpoolWatcherInCwd(reason) {
595
- try {
596
- const pidPath = path.join(process.cwd(), '.gm', 'exec-spool', '.watcher.pid');
597
- if (!fs.existsSync(pidPath)) return null;
598
- const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
599
- if (pidAlive(pid) && !pidIsPlugkitProcess(pid)) {
600
- obsEvent('bootstrap', 'watcher.kill-skipped-pid-reused', { pid, reason });
601
- try { fs.unlinkSync(pidPath); } catch (_) {}
602
- return null;
603
- }
604
- writeKillAttribution(path.join(process.cwd(), '.gm', 'exec-spool'), { reason, target_pid: pid, via: 'killSpoolWatcherInCwd' });
605
- if (killPid(pid)) {
606
- obsEvent('bootstrap', 'watcher.killed', { pid, reason });
607
- try { fs.unlinkSync(pidPath); } catch (_) {}
608
- return pid;
609
- }
610
- try { fs.unlinkSync(pidPath); } catch (_) {}
611
- } catch (_) {}
612
- return null;
613
- }
614
-
615
-
616
- function isLockStale(lockPath) {
617
- try {
618
- const st = fs.statSync(lockPath);
619
- if (Date.now() - st.mtimeMs > LOCK_STALE_MS) return true;
620
- const owner = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10);
621
- if (Number.isFinite(owner) && !pidAlive(owner)) return true;
622
- } catch (_) { return true; }
623
- return false;
624
- }
625
-
626
- function pruneOldVersions(root, keepVersion) {
627
- try {
628
- const entries = fs.readdirSync(root);
629
- for (const e of entries) {
630
- if (!e.startsWith('v')) continue;
631
- if (e === `v${keepVersion}`) continue;
632
- const dir = path.join(root, e);
633
- const lock = path.join(dir, '.lock');
634
- if (fs.existsSync(lock) && !isLockStale(lock)) continue;
635
- if (fs.existsSync(lock)) { try { fs.unlinkSync(lock); } catch (_) {} }
636
- try {
637
- fs.rmSync(dir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 });
638
- log(`pruned ${dir}`);
639
- } catch (err) { log(`prune skip ${dir}: ${err.message}`); }
640
- }
641
- } catch (_) {}
642
- }
643
-
644
- function proactiveKillForNewInstall(installedVersion) {
645
- try {
646
- const reason = `install:v${installedVersion}`;
647
- killSpoolWatcherInCwd(reason);
648
- writeDaemonVersion(installedVersion);
649
- } catch (_) {}
650
- }
651
-
652
515
  function killStaleDaemonIfVersionChanged() {
653
516
  let currentVersion;
654
- try { currentVersion = readVersionFile(); } catch (_) { return; }
517
+ try { currentVersion = readVersionFile(); }
518
+ catch (e) {
519
+ obsEvent('bootstrap', 'kill-stale-daemon.version-read-failed', { error: e.message });
520
+ return;
521
+ }
655
522
  const cached = resolveCachedBinary({ version: currentVersion });
656
523
  if (cached) {
657
524
  proactiveKillForNewInstall(currentVersion, cached);
@@ -962,7 +829,7 @@ function discoverBundledSkillsAndSources() {
962
829
  const m = f.match(/^SKILL-(.+)\.md$/);
963
830
  if (m) found.set(m[1], path.join(__dirname, f));
964
831
  }
965
- } catch (_) {}
832
+ } catch (e) { obsEvent('bootstrap', 'discover-bundled-skills.readdir-failed', { dir: __dirname, error: e.message }); }
966
833
  const devSkillsRoots = [
967
834
  path.join(__dirname, '..', 'gm-skill', 'skills'),
968
835
  path.join(__dirname, '..', '..', 'gm-skill', 'skills'),
@@ -976,7 +843,9 @@ function discoverBundledSkillsAndSources() {
976
843
  const p = path.join(root, e.name, 'SKILL.md');
977
844
  if (fs.existsSync(p) && !found.has(e.name)) found.set(e.name, p);
978
845
  }
979
- } catch (_) {}
846
+ } catch (e) {
847
+ obsEvent('bootstrap', 'discover-bundled-skills.dir-read-failed', { root, error: e.message });
848
+ }
980
849
  }
981
850
  return found;
982
851
  }
@@ -1060,7 +929,9 @@ async function resolveLatestRemoteVersion(timeoutMs) {
1060
929
  const hasPlugkitWasm = Array.isArray(rel.assets) && rel.assets.some(a => a && a.name === 'plugkit.wasm');
1061
930
  if (hasPlugkitWasm) return m[1];
1062
931
  }
1063
- } catch (_) {}
932
+ } catch (e) {
933
+ obsEvent('bootstrap', 'resolve-latest-remote-version.failed', { error: e.message });
934
+ }
1064
935
  return null;
1065
936
  }
1066
937
 
@@ -1069,7 +940,9 @@ async function resolveLatestGmPlugkitNpmVersion(timeoutMs) {
1069
940
  const buf = await httpGetBuffer('https://registry.npmjs.org/gm-plugkit/latest', timeoutMs || 3000);
1070
941
  const meta = JSON.parse(buf.toString('utf-8'));
1071
942
  if (meta && typeof meta.version === 'string') return meta.version;
1072
- } catch (_) {}
943
+ } catch (e) {
944
+ obsEvent('bootstrap', 'resolve-latest-npm-version.failed', { error: e.message });
945
+ }
1073
946
  return null;
1074
947
  }
1075
948
 
@@ -1120,7 +993,9 @@ async function ensureReady(opts) {
1120
993
  if (fs.existsSync(stalePath)) fs.unlinkSync(stalePath);
1121
994
  } catch (_) {}
1122
995
  }
1123
- } catch (_) {}
996
+ } catch (e) {
997
+ obsEvent('bootstrap', 'self-stale-check.failed', { error: e.message });
998
+ }
1124
999
  }
1125
1000
 
1126
1001
  let pinnedVersion = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.2059",
3
+ "version": "2.0.2061",
4
4
  "description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform wasm, verifies SHA256, and launches agentplug-runner (the native wasm host) as the spool watcher daemon.",
5
5
  "main": "index.js",
6
6
  "bin": {