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.
- package/bootstrap.js +61 -186
- 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 {
|
|
10
|
-
const
|
|
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 (
|
|
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(); }
|
|
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.
|
|
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": {
|