gm-plugkit 2.0.2025 → 2.0.2027

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 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. plugkit-wasm-wrapper.js
262
- // wires that answer through gm-runner's native candle path
263
- // (hostEmbedViaGmRunner) or agentplug-runner's shared daemon, both gated on
264
- // one of these two binaries existing under ~/.gm-tools -- mirrors that same
265
- // on-disk presence check here so bootstrap-time artifact selection and
266
- // runtime embed-delegation eligibility never disagree. Absence of both means
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
- ? ['gm-runner.exe', 'agentplug-runner.exe']
273
- : ['gm-runner', 'agentplug-runner'];
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 supervisor.js/cli.js must funnel through this so a worktree-spawned
283
- // process resolves to the SAME .gm/exec-spool/ as its main-repo sibling --
284
- // otherwise the single-instance supervisor lock can never see a sibling
285
- // worktree's already-running watcher, and every worktree cold-boots its own
286
- // independent watcher (each loading the full embed model + running its own
287
- // cold reindex of what is, conceptually, the same project). Live-measured
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. Mirrors the existing browserRootDir() resolution already used for
295
- // browser-session state in plugkit-wasm-wrapper.js (same fix, same reason,
296
- // applied one layer up at the process-boot-dedup level).
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 /plugkit-wasm-wrapper\.js|plugkit-supervisor\.js|gm-plugkit[\\\/]supervisor\.js/i.test(pidCommandLineForKillGuard(pid));
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: (wrapperUpdated || versionMarkerUpdated) ? 'wrapper-refreshed' : 'already-ready', version: installed };
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 wrapper = path.join(gmToolsDir(), 'plugkit-wasm-wrapper.js');
1302
- if (!fs.existsSync(wrapper)) {
1303
- return { ok: false, error: `wrapper not at ${wrapper} -- ensureReady() must run first` };
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} (js-host fallback, native runner absent) ---\n`); } catch (_) {}
1322
- const child = require('child_process').spawn(cmd, args, {
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: 'js-host-fallback' },
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, wrapper, runtime: cmd, logPath, supervised: false };
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, ensureWrapperFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot, resolveProjectRoot } = require('./bootstrap');
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 sole native host and a sha256-verified drop-in
234
- // replacement for this entire bun/node boot path -- when it's installed,
235
- // delegate to it immediately and exit, before any bun/node bootstrap runs.
236
- // It serves the identical spool ABI (same in/out layout, same verb names;
237
- // gm.wasm is one of its loadable plugins alongside libsql/bert/treesitter),
238
- // so bun/node are only ever exercised as the pure-JS fallback on a platform
239
- // agentplug-bin has no published binary for, or during the one-time install
240
- // before the runner lands on disk. (gm-runner, a strictly-inferior
241
- // single-module duplicate, was retired once agentplug-runner reached full
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
- // agentplug-runner is the sole native host now -- gm-runner (a strictly
246
- // inferior single-module duplicate) was retired once agentplug-runner had
247
- // full 6-platform parity published to agentplug-bin. The JS wasm-host below
248
- // is the only fallback, for a platform with no published native binary.
249
- const candidates = process.platform === 'win32'
250
- ? ['agentplug-runner.exe']
251
- : ['agentplug-runner'];
252
- for (const exeName of candidates) {
253
- const runnerPath = path.join(gmToolsDir(), exeName);
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 false;
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
- let wrapperRefreshed = false;
315
- try { wrapperRefreshed = ensureWrapperFresh(); } catch (_) {}
316
- if (wrapperRefreshed) {
317
- writeCliStatus({ phase: 'wrapper-drift-detected', reason: 'on-disk-wrapper-refreshed', running_pid: already.pid });
318
- console.error(`[gm-plugkit] running watcher (pid=${already.pid}) serves a stale wrapper; on-disk copy just refreshed -- forcing reboot`);
319
- try {
320
- if (process.platform === 'win32') cp.execFileSync('taskkill', ['/F', '/T', '/PID', String(already.pid)], { stdio: 'ignore', windowsHide: true });
321
- else process.kill(already.pid, 'SIGTERM');
322
- } catch (_) {}
323
- waitForPidDeath(already.pid, 5000);
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
- const wrapperPath = path.join(gmToolsDir(), 'plugkit-wasm-wrapper.js');
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.2025",
4
- "description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform binary, verifies SHA256, and starts the spool watcher daemon. Includes plugkit-wasm-wrapper for WASM-based spool watching.",
3
+ "version": "2.0.2027",
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",
package/plugkit.version CHANGED
@@ -1 +1 @@
1
- 0.1.936
1
+ 0.1.937