gm-plugkit 2.0.2025 → 2.0.2026
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 +35 -152
- package/cli.js +31 -168
- package/package.json +3 -5
- package/plugkit-wasm-wrapper.js +0 -4939
package/bootstrap.js
CHANGED
|
@@ -258,19 +258,17 @@ function gmToolsDir() {
|
|
|
258
258
|
// before ever loading its wasm-embedded safetensors fallback (see
|
|
259
259
|
// rs-plugkit/crates/plugkit-core/src/embed.rs::init_ctx) -- a slim build
|
|
260
260
|
// (feature=slim, no embedded weights at all) is only safe to fetch on a host
|
|
261
|
-
// that actually answers host_vec_embed for real.
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
// no host_vec_embed answer will ever come, so fetching fat (which carries its
|
|
268
|
-
// own wasm-side embedding fallback) is the only safe choice.
|
|
261
|
+
// that actually answers host_vec_embed for real. agentplug-runner answers it
|
|
262
|
+
// via its shared bert plugin daemon; the check is gated on the runner binary
|
|
263
|
+
// existing under ~/.gm-tools so bootstrap-time artifact selection and runtime
|
|
264
|
+
// embed-delegation eligibility never disagree. Absence means no host_vec_embed
|
|
265
|
+
// answer will ever come, so fetching fat (which carries its own wasm-side
|
|
266
|
+
// embedding fallback) is the only safe choice.
|
|
269
267
|
function hasNativeEmbedRunner() {
|
|
270
268
|
const dir = gmToolsDir();
|
|
271
269
|
const names = process.platform === 'win32'
|
|
272
|
-
? ['
|
|
273
|
-
: ['
|
|
270
|
+
? ['agentplug-runner.exe']
|
|
271
|
+
: ['agentplug-runner'];
|
|
274
272
|
return names.some(n => { try { return fs.existsSync(path.join(dir, n)); } catch (_) { return false; } });
|
|
275
273
|
}
|
|
276
274
|
|
|
@@ -279,21 +277,21 @@ function hasNativeEmbedRunner() {
|
|
|
279
277
|
// agents, each `git worktree add`-ing a fresh physical directory) shares the
|
|
280
278
|
// SAME underlying repo as its main checkout but has its own separate
|
|
281
279
|
// directory tree. Every cwd-derived project-dir computation in this file and
|
|
282
|
-
// in
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
280
|
+
// in cli.js must funnel through this so a worktree-spawned process resolves to
|
|
281
|
+
// the SAME .gm/exec-spool/ as its main-repo sibling -- otherwise the
|
|
282
|
+
// single-instance watcher guard can never see a sibling worktree's
|
|
283
|
+
// already-running watcher, and every worktree cold-boots its own independent
|
|
284
|
+
// watcher (each loading the full embed model + running its own cold reindex of
|
|
285
|
+
// what is, conceptually, the same project). Live-measured
|
|
288
286
|
// this session under real concurrent multi-agent load: this was the actual
|
|
289
287
|
// root cause of a user-flagged "memory grows to ~2GB then clears" churn
|
|
290
288
|
// pattern -- N worktrees of one repo each independently paying full
|
|
291
289
|
// cold-embed cost instead of sharing one already-warm watcher, and the
|
|
292
290
|
// resulting CPU contention pushed genuinely-busy processes past their
|
|
293
291
|
// heartbeat deadline into a restart cycle that produces the sawtooth memory
|
|
294
|
-
// pattern.
|
|
295
|
-
//
|
|
296
|
-
//
|
|
292
|
+
// pattern. agentplug-runner roots its own browser-session state at the git
|
|
293
|
+
// common dir for the same reason (same fix, same reason, applied one layer up
|
|
294
|
+
// at the process-boot-dedup level).
|
|
297
295
|
function resolveProjectRoot(start) {
|
|
298
296
|
const resolved = path.resolve(start);
|
|
299
297
|
try {
|
|
@@ -373,23 +371,6 @@ function releaseLock(lockPath) {
|
|
|
373
371
|
try { fs.unlinkSync(lockPath); } catch (_) {}
|
|
374
372
|
}
|
|
375
373
|
|
|
376
|
-
function resolveNpxJsCli() {
|
|
377
|
-
if (process.platform !== 'win32') return null;
|
|
378
|
-
const candidates = [];
|
|
379
|
-
if (process.env.npm_config_prefix) {
|
|
380
|
-
candidates.push(path.join(process.env.npm_config_prefix, 'node_modules', 'npm', 'bin', 'npx-cli.js'));
|
|
381
|
-
}
|
|
382
|
-
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
|
|
383
|
-
candidates.push(path.join(programFiles, 'nodejs', 'node_modules', 'npm', 'bin', 'npx-cli.js'));
|
|
384
|
-
candidates.push(path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js'));
|
|
385
|
-
const appdata = process.env.APPDATA;
|
|
386
|
-
if (appdata) candidates.push(path.join(appdata, 'npm', 'node_modules', 'npm', 'bin', 'npx-cli.js'));
|
|
387
|
-
for (const c of candidates) {
|
|
388
|
-
try { if (fs.existsSync(c)) return c; } catch (_) {}
|
|
389
|
-
}
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
374
|
async function extractNpmPackageWasm(destPath, version) {
|
|
394
375
|
const tempDir = path.join(path.dirname(destPath), '.npm-extract-' + Date.now());
|
|
395
376
|
try {
|
|
@@ -590,7 +571,7 @@ function pidCommandLineForKillGuard(pid) {
|
|
|
590
571
|
}
|
|
591
572
|
|
|
592
573
|
function pidIsPlugkitProcess(pid) {
|
|
593
|
-
return /
|
|
574
|
+
return /agentplug-runner(\.exe)?/i.test(pidCommandLineForKillGuard(pid));
|
|
594
575
|
}
|
|
595
576
|
|
|
596
577
|
function writeKillAttribution(targetSpoolDir, info) {
|
|
@@ -854,8 +835,6 @@ function copyWasmToGmTools(wasmPath, version) {
|
|
|
854
835
|
const dst = gmToolsDir();
|
|
855
836
|
fs.mkdirSync(dst, { recursive: true });
|
|
856
837
|
const target = path.join(dst, 'plugkit.wasm');
|
|
857
|
-
const wrapperSrc = path.join(__dirname, 'plugkit-wasm-wrapper.js');
|
|
858
|
-
const wrapperDst = path.join(dst, 'plugkit-wasm-wrapper.js');
|
|
859
838
|
|
|
860
839
|
let wasmFresh = false;
|
|
861
840
|
if (fs.existsSync(target)) {
|
|
@@ -888,17 +867,6 @@ function copyWasmToGmTools(wasmPath, version) {
|
|
|
888
867
|
}
|
|
889
868
|
} catch (_) {}
|
|
890
869
|
|
|
891
|
-
if (fs.existsSync(wrapperSrc)) {
|
|
892
|
-
let wrapperFresh = false;
|
|
893
|
-
if (fs.existsSync(wrapperDst)) {
|
|
894
|
-
try {
|
|
895
|
-
const cur = sha256OfFileSync(wrapperDst);
|
|
896
|
-
const src = sha256OfFileSync(wrapperSrc);
|
|
897
|
-
if (cur === src) wrapperFresh = true;
|
|
898
|
-
} catch (_) {}
|
|
899
|
-
}
|
|
900
|
-
if (!wrapperFresh) fs.copyFileSync(wrapperSrc, wrapperDst);
|
|
901
|
-
}
|
|
902
870
|
}
|
|
903
871
|
|
|
904
872
|
function getWasmPath() {
|
|
@@ -915,60 +883,6 @@ function isReady() {
|
|
|
915
883
|
return fs.existsSync(wasm);
|
|
916
884
|
}
|
|
917
885
|
|
|
918
|
-
function runningFromGmSourceRepo() {
|
|
919
|
-
try {
|
|
920
|
-
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
921
|
-
if (!fs.existsSync(pkgPath)) return false;
|
|
922
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
923
|
-
return pkg && pkg.name === 'gm-skill';
|
|
924
|
-
} catch (_) { return false; }
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function ensureWrapperFresh() {
|
|
928
|
-
try {
|
|
929
|
-
const wrapperSrc = path.join(__dirname, 'plugkit-wasm-wrapper.js');
|
|
930
|
-
const wrapperDst = path.join(gmToolsDir(), 'plugkit-wasm-wrapper.js');
|
|
931
|
-
if (!fs.existsSync(wrapperSrc)) return false;
|
|
932
|
-
if (runningFromGmSourceRepo() && process.env.GM_PLUGKIT_ALLOW_DEV_WRAPPER_OVERWRITE !== '1') {
|
|
933
|
-
log(`refusing to overwrite the shared ~/.gm-tools wrapper from the gm source repo (${wrapperSrc}) -- this machine-wide install is shared by every project's watcher; set GM_PLUGKIT_ALLOW_DEV_WRAPPER_OVERWRITE=1 to opt in, or use 'bun x gm-plugkit@latest' which fetches an isolated npm copy instead`);
|
|
934
|
-
return false;
|
|
935
|
-
}
|
|
936
|
-
let same = false;
|
|
937
|
-
if (fs.existsSync(wrapperDst)) {
|
|
938
|
-
try {
|
|
939
|
-
const a = sha256OfFileSync(wrapperSrc);
|
|
940
|
-
const b = sha256OfFileSync(wrapperDst);
|
|
941
|
-
if (a === b) same = true;
|
|
942
|
-
} catch (_) {}
|
|
943
|
-
}
|
|
944
|
-
if (same) return false;
|
|
945
|
-
// Many independent per-project watchers share this one gmToolsDir() install --
|
|
946
|
-
// concurrent CLI invocations from different projects can race this copy, so
|
|
947
|
-
// it's lock-guarded (atomic O_EXCL) + tmp-write-then-rename, never a direct
|
|
948
|
-
// in-place copyFileSync another reader could observe half-written.
|
|
949
|
-
fs.mkdirSync(gmToolsDir(), { recursive: true });
|
|
950
|
-
const lockPath = wrapperDst + '.lock';
|
|
951
|
-
acquireLock(lockPath);
|
|
952
|
-
try {
|
|
953
|
-
let stillSame = false;
|
|
954
|
-
if (fs.existsSync(wrapperDst)) {
|
|
955
|
-
try {
|
|
956
|
-
const a = sha256OfFileSync(wrapperSrc);
|
|
957
|
-
const b = sha256OfFileSync(wrapperDst);
|
|
958
|
-
if (a === b) stillSame = true;
|
|
959
|
-
} catch (_) {}
|
|
960
|
-
}
|
|
961
|
-
if (stillSame) return false;
|
|
962
|
-
const tmpDst = wrapperDst + '.tmp.' + process.pid;
|
|
963
|
-
fs.copyFileSync(wrapperSrc, tmpDst);
|
|
964
|
-
fs.renameSync(tmpDst, wrapperDst);
|
|
965
|
-
return true;
|
|
966
|
-
} finally {
|
|
967
|
-
releaseLock(lockPath);
|
|
968
|
-
}
|
|
969
|
-
} catch (_) { return false; }
|
|
970
|
-
}
|
|
971
|
-
|
|
972
886
|
function ensureGmPlugkitVersionFresh() {
|
|
973
887
|
try {
|
|
974
888
|
const ownPkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
@@ -1223,10 +1137,9 @@ async function ensureReady(opts) {
|
|
|
1223
1137
|
|
|
1224
1138
|
if (isReady() && !versionDrift) {
|
|
1225
1139
|
const wasmPath = getWasmPath();
|
|
1226
|
-
const wrapperUpdated = ensureWrapperFresh();
|
|
1227
1140
|
const versionMarkerUpdated = ensureGmPlugkitVersionFresh();
|
|
1228
1141
|
ensureSkillMdFresh();
|
|
1229
|
-
return { ok: true, wasmPath, binaryPath: wasmPath, status:
|
|
1142
|
+
return { ok: true, wasmPath, binaryPath: wasmPath, status: versionMarkerUpdated ? 'version-refreshed' : 'already-ready', version: installed };
|
|
1230
1143
|
}
|
|
1231
1144
|
if (targetVersion && targetVersion !== pinnedVersion) {
|
|
1232
1145
|
try {
|
|
@@ -1243,7 +1156,6 @@ async function ensureReady(opts) {
|
|
|
1243
1156
|
if (versionDrift && isReady()) {
|
|
1244
1157
|
log(`bootstrap for ${targetVersion} failed (${bootErr.message || bootErr}); keeping running watcher on installed ${installed} (no kill, serve cached wasm)`);
|
|
1245
1158
|
const cachedPath = getWasmPath();
|
|
1246
|
-
ensureWrapperFresh();
|
|
1247
1159
|
ensureSkillMdFresh();
|
|
1248
1160
|
return { ok: true, wasmPath: cachedPath, binaryPath: cachedPath, status: 'bootstrap-failed-served-cached', version: installed };
|
|
1249
1161
|
}
|
|
@@ -1254,7 +1166,6 @@ async function ensureReady(opts) {
|
|
|
1254
1166
|
try { killSpoolWatcherInCwd(`version_drift:${installed}->${targetVersion}`); } catch (_) {}
|
|
1255
1167
|
}
|
|
1256
1168
|
|
|
1257
|
-
ensureWrapperFresh();
|
|
1258
1169
|
ensureSkillMdFresh();
|
|
1259
1170
|
return { ok: true, wasmPath, binaryPath: wasmPath, status: 'bootstrapped', version: targetVersion || installed };
|
|
1260
1171
|
}
|
|
@@ -1263,46 +1174,21 @@ function getBinaryPath() {
|
|
|
1263
1174
|
return getWasmPath();
|
|
1264
1175
|
}
|
|
1265
1176
|
|
|
1266
|
-
function resolveNodeRuntime() {
|
|
1267
|
-
const candidates = [];
|
|
1268
|
-
if (process.env.PLUGKIT_RUNTIME) candidates.push(process.env.PLUGKIT_RUNTIME);
|
|
1269
|
-
candidates.push('bun');
|
|
1270
|
-
try {
|
|
1271
|
-
const which = process.platform === 'win32' ? 'where' : 'which';
|
|
1272
|
-
const out = require('child_process').spawnSync(which, ['bun'], { encoding: 'utf8', windowsHide: true });
|
|
1273
|
-
if (out && out.stdout) {
|
|
1274
|
-
const first = out.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
|
|
1275
|
-
if (first) candidates.push(first);
|
|
1276
|
-
}
|
|
1277
|
-
} catch (_) {}
|
|
1278
|
-
for (const c of candidates) {
|
|
1279
|
-
try { const r = require('child_process').spawnSync(c, ['--version'], { stdio: 'ignore', windowsHide: true }); if (r && r.status === 0) return c; } catch (_) {}
|
|
1280
|
-
}
|
|
1281
|
-
const isNodeExe = (p) => /(^|[\\/])node(\.exe)?$/i.test(String(p || ''));
|
|
1282
|
-
const nodeCandidates = [];
|
|
1283
|
-
if (isNodeExe(process.env.GM_NODE_PATH)) nodeCandidates.push(process.env.GM_NODE_PATH);
|
|
1284
|
-
if (isNodeExe(process.execPath)) nodeCandidates.push(process.execPath);
|
|
1285
|
-
try {
|
|
1286
|
-
const which = process.platform === 'win32' ? 'where' : 'which';
|
|
1287
|
-
const out = require('child_process').spawnSync(which, ['node'], { encoding: 'utf8', windowsHide: true });
|
|
1288
|
-
if (out && out.stdout) {
|
|
1289
|
-
const first = out.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
|
|
1290
|
-
if (first) nodeCandidates.push(first);
|
|
1291
|
-
}
|
|
1292
|
-
} catch (_) {}
|
|
1293
|
-
for (const c of nodeCandidates) {
|
|
1294
|
-
try { const r = require('child_process').spawnSync(c, ['--version'], { stdio: 'ignore', windowsHide: true }); if (r && r.status === 0) return c; } catch (_) {}
|
|
1295
|
-
}
|
|
1296
|
-
return process.execPath;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
1177
|
function startSpoolDaemon() {
|
|
1300
1178
|
try {
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
|
|
1179
|
+
const runnerName = process.platform === 'win32' ? 'agentplug-runner.exe' : 'agentplug-runner';
|
|
1180
|
+
const runner = path.join(gmToolsDir(), runnerName);
|
|
1181
|
+
if (!fs.existsSync(runner)) {
|
|
1182
|
+
return {
|
|
1183
|
+
ok: false,
|
|
1184
|
+
error:
|
|
1185
|
+
`agentplug-runner is not installed at ${runner} and is the sole supported spool loader. ` +
|
|
1186
|
+
`The JS wasm-host has been retired. Install it with 'bun x gm-skill install' (or 'npx gm-skill install'), ` +
|
|
1187
|
+
`which downloads the sha256-verified native runner from AnEntrypoint/agentplug-bin for this platform ` +
|
|
1188
|
+
`(${process.platform}/${process.arch}). If no binary is published for this platform yet, there is no ` +
|
|
1189
|
+
`loader available -- file an issue at https://github.com/AnEntrypoint/agentplug-bin so a binary is built for it.`,
|
|
1190
|
+
};
|
|
1304
1191
|
}
|
|
1305
|
-
const runtime = resolveNodeRuntime();
|
|
1306
1192
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
1307
1193
|
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
1308
1194
|
fs.mkdirSync(spoolDir, { recursive: true });
|
|
@@ -1315,20 +1201,18 @@ function startSpoolDaemon() {
|
|
|
1315
1201
|
}
|
|
1316
1202
|
} catch (_) {}
|
|
1317
1203
|
|
|
1318
|
-
const cmd = runtime;
|
|
1319
|
-
const args = [wrapper, 'spool'];
|
|
1320
1204
|
const logFd = fs.openSync(logPath, 'a');
|
|
1321
|
-
try { fs.writeSync(logFd, `\n--- daemon spawn ${new Date().toISOString()} parent=${process.pid} (
|
|
1322
|
-
const child = require('child_process').spawn(
|
|
1205
|
+
try { fs.writeSync(logFd, `\n--- daemon spawn ${new Date().toISOString()} parent=${process.pid} (agentplug-runner) ---\n`); } catch (_) {}
|
|
1206
|
+
const child = require('child_process').spawn(runner, ['spool'], {
|
|
1323
1207
|
detached: true,
|
|
1324
1208
|
stdio: ['ignore', logFd, logFd],
|
|
1325
1209
|
windowsHide: true,
|
|
1326
|
-
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir, PLUGKIT_BOOT_REASON: '
|
|
1210
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir, PLUGKIT_BOOT_REASON: 'agentplug-runner' },
|
|
1327
1211
|
});
|
|
1328
1212
|
try { fs.closeSync(logFd); } catch (_) {}
|
|
1329
1213
|
const pid = child.pid;
|
|
1330
1214
|
child.unref();
|
|
1331
|
-
return { ok: true, pid,
|
|
1215
|
+
return { ok: true, pid, runner, logPath, supervised: false };
|
|
1332
1216
|
} catch (e) {
|
|
1333
1217
|
return { ok: false, error: e.message };
|
|
1334
1218
|
}
|
|
@@ -1356,7 +1240,6 @@ module.exports = {
|
|
|
1356
1240
|
readVersionFile,
|
|
1357
1241
|
ensureGmPlugkitVersionFresh,
|
|
1358
1242
|
ensureSkillMdFresh,
|
|
1359
|
-
ensureWrapperFresh,
|
|
1360
1243
|
readPinnedGmPlugkitVersion,
|
|
1361
1244
|
resolveBunRuntime,
|
|
1362
1245
|
spawnPinnedBoot,
|
package/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const cp = require('child_process');
|
|
8
|
-
const { ensureReady, startSpoolDaemon, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh,
|
|
8
|
+
const { ensureReady, startSpoolDaemon, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot, resolveProjectRoot } = require('./bootstrap');
|
|
9
9
|
const { pidAliveSync, waitForPidDeath } = require('./gm-process');
|
|
10
10
|
|
|
11
11
|
function getWasmPathSafe() {
|
|
@@ -60,10 +60,6 @@ Usage:
|
|
|
60
60
|
bun x gm-plugkit@latest --daemon Same as default
|
|
61
61
|
bun x gm-plugkit@latest --binary Print binary path only
|
|
62
62
|
bun x gm-plugkit@latest --status JSON status check
|
|
63
|
-
bun x gm-plugkit@latest --kill-stale-watchers
|
|
64
|
-
Kill plugkit watchers whose in-memory
|
|
65
|
-
wrapper sha differs from on-disk
|
|
66
|
-
(lets new wrapper code load on next bootstrap)
|
|
67
63
|
bun x gm-plugkit@latest --help Show this help
|
|
68
64
|
|
|
69
65
|
Opt-in pinned fast-path (skips bunx's own @latest npm-registry resolution):
|
|
@@ -83,111 +79,6 @@ Opt-in pinned fast-path (skips bunx's own @latest npm-registry resolution):
|
|
|
83
79
|
unaffected.
|
|
84
80
|
`;
|
|
85
81
|
|
|
86
|
-
function readDiskWasmVersion() {
|
|
87
|
-
try {
|
|
88
|
-
const versionFile = path.join(gmToolsDir(), 'plugkit.version');
|
|
89
|
-
return fs.readFileSync(versionFile, 'utf-8').trim() || null;
|
|
90
|
-
} catch (_) { return null; }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function readWatcherInstanceVersion(pid) {
|
|
94
|
-
try {
|
|
95
|
-
const ps = process.platform === 'win32'
|
|
96
|
-
? `(Get-WmiObject Win32_Process -Filter "ProcessId=${pid}").CommandLine`
|
|
97
|
-
: null;
|
|
98
|
-
if (!ps) return null;
|
|
99
|
-
const out = cp.execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { encoding: 'utf-8', windowsHide: true });
|
|
100
|
-
const m = out.match(/([A-Z]:\\[^"\s]+\.gm[\\/]exec-spool)/i);
|
|
101
|
-
if (!m) return null;
|
|
102
|
-
const statusPath = path.join(m[1].replace(/[\\/]exec-spool.*$/, ''), 'exec-spool', '.status.json');
|
|
103
|
-
if (!fs.existsSync(statusPath)) return null;
|
|
104
|
-
const status = JSON.parse(fs.readFileSync(statusPath, 'utf-8'));
|
|
105
|
-
return status && status.instance_version ? status.instance_version : null;
|
|
106
|
-
} catch (_) { return null; }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function killStaleWatchers() {
|
|
110
|
-
try {
|
|
111
|
-
const wrapperPath = path.join(gmToolsDir(), 'plugkit-wasm-wrapper.js');
|
|
112
|
-
if (!fs.existsSync(wrapperPath)) {
|
|
113
|
-
console.log(JSON.stringify({ ok: false, error: `wrapper not installed at ${wrapperPath}` }));
|
|
114
|
-
return 1;
|
|
115
|
-
}
|
|
116
|
-
const diskMtime = fs.statSync(wrapperPath).mtimeMs;
|
|
117
|
-
const diskWasmVersion = readDiskWasmVersion();
|
|
118
|
-
let localStatus = null;
|
|
119
|
-
try {
|
|
120
|
-
const localStatusPath = path.join(process.cwd(), '.gm', 'exec-spool', '.status.json');
|
|
121
|
-
if (fs.existsSync(localStatusPath)) localStatus = JSON.parse(fs.readFileSync(localStatusPath, 'utf-8'));
|
|
122
|
-
} catch (_) { localStatus = null; }
|
|
123
|
-
const stale = [];
|
|
124
|
-
const fresh = [];
|
|
125
|
-
function consider(pid, startedMs) {
|
|
126
|
-
const reasons = [];
|
|
127
|
-
if (startedMs < diskMtime) reasons.push('wrapper-mtime');
|
|
128
|
-
let instV = readWatcherInstanceVersion(pid);
|
|
129
|
-
if (!instV && localStatus && localStatus.pid === pid) {
|
|
130
|
-
instV = localStatus.instance_version || localStatus.version || null;
|
|
131
|
-
}
|
|
132
|
-
if (diskWasmVersion && instV && instV !== diskWasmVersion) reasons.push(`wasm-drift:${instV}->${diskWasmVersion}`);
|
|
133
|
-
if (reasons.length > 0) stale.push({ pid, started_ms: startedMs, instance_version: instV, reasons });
|
|
134
|
-
else fresh.push({ pid, started_ms: startedMs, instance_version: instV });
|
|
135
|
-
}
|
|
136
|
-
if (process.platform === 'win32') {
|
|
137
|
-
const ps = `Get-WmiObject Win32_Process -Filter "name='node.exe' OR name='bun.exe'" | Where-Object { $_.CommandLine -match 'plugkit-wasm-wrapper' } | ForEach-Object { $_.ProcessId.ToString() + '|' + $_.CreationDate }`;
|
|
138
|
-
const out = cp.execFileSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { encoding: 'utf-8', windowsHide: true });
|
|
139
|
-
for (const line of out.split(/\r?\n/).filter(Boolean)) {
|
|
140
|
-
const [pidStr, creation] = line.split('|');
|
|
141
|
-
const pid = parseInt(pidStr, 10);
|
|
142
|
-
if (!Number.isFinite(pid)) continue;
|
|
143
|
-
const m = creation && creation.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(?:\.(\d+))?(?:([+-])(\d+))?/);
|
|
144
|
-
if (!m) continue;
|
|
145
|
-
const localMs = new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6], m[7] ? Math.round(+('0.' + m[7]) * 1000) : 0).getTime();
|
|
146
|
-
consider(pid, localMs);
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
const out = cp.execFileSync('ps', ['-eo', 'pid,lstart,command'], { encoding: 'utf-8' });
|
|
150
|
-
for (const line of out.split('\n').slice(1)) {
|
|
151
|
-
if (!line.includes('plugkit-wasm-wrapper')) continue;
|
|
152
|
-
const m = line.match(/^\s*(\d+)\s+(.+?\d{4})\s+/);
|
|
153
|
-
if (!m) continue;
|
|
154
|
-
const pid = parseInt(m[1], 10);
|
|
155
|
-
const start = Date.parse(m[2]);
|
|
156
|
-
if (!Number.isFinite(pid) || !Number.isFinite(start)) continue;
|
|
157
|
-
consider(pid, start);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
const killed = [];
|
|
161
|
-
const failed = [];
|
|
162
|
-
for (const s of stale) {
|
|
163
|
-
try {
|
|
164
|
-
if (process.platform === 'win32') {
|
|
165
|
-
cp.execFileSync('taskkill', ['/F', '/T', '/PID', String(s.pid)], { stdio: 'ignore', windowsHide: true });
|
|
166
|
-
} else {
|
|
167
|
-
process.kill(s.pid, 'SIGTERM');
|
|
168
|
-
}
|
|
169
|
-
killed.push(s.pid);
|
|
170
|
-
} catch (e) {
|
|
171
|
-
failed.push({ pid: s.pid, error: e.message });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
console.log(JSON.stringify({
|
|
175
|
-
ok: true,
|
|
176
|
-
disk_wrapper_mtime_ms: diskMtime,
|
|
177
|
-
disk_wasm_version: diskWasmVersion,
|
|
178
|
-
stale_found: stale.length,
|
|
179
|
-
fresh_found: fresh.length,
|
|
180
|
-
stale_detail: stale,
|
|
181
|
-
killed,
|
|
182
|
-
failed,
|
|
183
|
-
}, null, 2));
|
|
184
|
-
return 0;
|
|
185
|
-
} catch (e) {
|
|
186
|
-
console.log(JSON.stringify({ ok: false, error: e.message }));
|
|
187
|
-
return 1;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
82
|
function spoolDir() {
|
|
192
83
|
const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
193
84
|
return path.join(projectDir, '.gm', 'exec-spool');
|
|
@@ -230,38 +121,28 @@ function writeCliError(phase, err) {
|
|
|
230
121
|
} catch (_) {}
|
|
231
122
|
}
|
|
232
123
|
|
|
233
|
-
// agentplug-runner is the
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
// 6-platform parity.)
|
|
124
|
+
// agentplug-runner is the SOLE spool loader -- a native wasmtime binary that
|
|
125
|
+
// loads gm.wasm as one plugin alongside its shared libsql/bert/treesitter
|
|
126
|
+
// plugins and serves the full spool ABI (in/out layout, verb names, browser
|
|
127
|
+
// via direct CDP, task via a native registry). When it is installed, delegate
|
|
128
|
+
// to it immediately and exit before any bun/node bootstrap runs. The JS
|
|
129
|
+
// wasm-host was retired; there is no pure-JS fallback anymore. If no runner is
|
|
130
|
+
// installed, the bootstrap path below downloads the wasm and startSpoolDaemon()
|
|
131
|
+
// either launches the runner or fails loudly with an actionable message
|
|
132
|
+
// (there is no silent no-loader state).
|
|
243
133
|
function tryDelegateToRunner(args) {
|
|
244
134
|
if (process.env.GM_PLUGKIT_NO_RUNNER_DELEGATE === '1') return false;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!fs.existsSync(runnerPath)) continue;
|
|
255
|
-
try {
|
|
256
|
-
const result = cp.spawnSync(runnerPath, args, { stdio: 'inherit', windowsHide: true });
|
|
257
|
-
if (result.error) continue; // this candidate genuinely failed to start -- try the next one
|
|
258
|
-
process.exit(typeof result.status === 'number' ? result.status : 0);
|
|
259
|
-
} catch (_) {
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
return true;
|
|
135
|
+
const exeName = process.platform === 'win32' ? 'agentplug-runner.exe' : 'agentplug-runner';
|
|
136
|
+
const runnerPath = path.join(gmToolsDir(), exeName);
|
|
137
|
+
if (!fs.existsSync(runnerPath)) return false;
|
|
138
|
+
try {
|
|
139
|
+
const result = cp.spawnSync(runnerPath, args, { stdio: 'inherit', windowsHide: true });
|
|
140
|
+
if (result.error) return false; // genuinely failed to start -- fall through to bootstrap+relaunch
|
|
141
|
+
process.exit(typeof result.status === 'number' ? result.status : 0);
|
|
142
|
+
} catch (_) {
|
|
143
|
+
return false;
|
|
263
144
|
}
|
|
264
|
-
return
|
|
145
|
+
return true;
|
|
265
146
|
}
|
|
266
147
|
|
|
267
148
|
(async () => {
|
|
@@ -274,10 +155,6 @@ function tryDelegateToRunner(args) {
|
|
|
274
155
|
|
|
275
156
|
tryDelegateToRunner(args);
|
|
276
157
|
|
|
277
|
-
if (args.includes('--kill-stale-watchers')) {
|
|
278
|
-
process.exit(killStaleWatchers());
|
|
279
|
-
}
|
|
280
|
-
|
|
281
158
|
const wantsPinned = process.env.GM_PLUGKIT_PREFER_PINNED === '1' || args.includes('--pinned');
|
|
282
159
|
const alreadyReexecedPinned = process.env.GM_PLUGKIT_PINNED_REEXEC === '1';
|
|
283
160
|
if (wantsPinned && !alreadyReexecedPinned) {
|
|
@@ -311,29 +188,16 @@ function tryDelegateToRunner(args) {
|
|
|
311
188
|
try { ensureGmPlugkitVersionFresh(); } catch (_) {}
|
|
312
189
|
let skillRefresh = null;
|
|
313
190
|
try { skillRefresh = ensureSkillMdFresh(); } catch (_) {}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
} else {
|
|
325
|
-
writeCliStatus({ phase: 'ready', already_serving: true, watcher_pid: already.pid });
|
|
326
|
-
console.log(JSON.stringify({
|
|
327
|
-
ok: true,
|
|
328
|
-
already_serving: true,
|
|
329
|
-
watcher_pid: already.pid,
|
|
330
|
-
version: already.version,
|
|
331
|
-
skills_refreshed: skillRefresh && skillRefresh.refreshed || [],
|
|
332
|
-
wrapper_refreshed: false,
|
|
333
|
-
message: 'plugkit already serving, no bootstrap/spawn needed',
|
|
334
|
-
}));
|
|
335
|
-
process.exit(0);
|
|
336
|
-
}
|
|
191
|
+
writeCliStatus({ phase: 'ready', already_serving: true, watcher_pid: already.pid });
|
|
192
|
+
console.log(JSON.stringify({
|
|
193
|
+
ok: true,
|
|
194
|
+
already_serving: true,
|
|
195
|
+
watcher_pid: already.pid,
|
|
196
|
+
version: already.version,
|
|
197
|
+
skills_refreshed: skillRefresh && skillRefresh.refreshed || [],
|
|
198
|
+
message: 'plugkit already serving, no bootstrap/spawn needed',
|
|
199
|
+
}));
|
|
200
|
+
process.exit(0);
|
|
337
201
|
}
|
|
338
202
|
if (versionDrifted) {
|
|
339
203
|
const targetVersion = remoteVersionDrifted ? remoteUpdate.latest : onDiskVersion;
|
|
@@ -347,8 +211,7 @@ function tryDelegateToRunner(args) {
|
|
|
347
211
|
waitForPidDeath(already.pid, 5000);
|
|
348
212
|
}
|
|
349
213
|
|
|
350
|
-
|
|
351
|
-
if (isReady() && fs.existsSync(wrapperPath)) {
|
|
214
|
+
if (isReady()) {
|
|
352
215
|
let installedVersion = null;
|
|
353
216
|
try { installedVersion = readVersionFile(); } catch (_) { installedVersion = null; }
|
|
354
217
|
writeCliStatus({ phase: 'bootstrapped', version: installedVersion, binary: getWasmPathSafe() });
|
package/package.json
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform
|
|
3
|
+
"version": "2.0.2026",
|
|
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": {
|
|
7
|
-
"gm-plugkit": "./cli.js"
|
|
8
|
-
"plugkit-wasm-wrapper": "./plugkit-wasm-wrapper.js"
|
|
7
|
+
"gm-plugkit": "./cli.js"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
10
|
"cli.js",
|
|
12
11
|
"index.js",
|
|
13
12
|
"bootstrap.js",
|
|
14
|
-
"plugkit-wasm-wrapper.js",
|
|
15
13
|
"gm-log.js",
|
|
16
14
|
"gm-process.js",
|
|
17
15
|
"plugkit.version",
|