gm-skill 2.0.2024 → 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/AGENTS.md CHANGED
@@ -24,23 +24,19 @@ Skills encode environment-specific constraints that override general knowledge.
24
24
 
25
25
  Repo root = package root = published `gm-skill` npm package; no factory, no separate build-output dir. Entry: `skills/gm/SKILL.md`. Orchestration lives in rs-plugkit, served on-demand via `instruction`. The 8 phase-prose files (entry/plan/execute/emit/verify/consolidate/update_docs/browser) live single-sourced inside rs-plugkit itself (`crates/plugkit-core/src/orchestrator/instructions/prose/*.md`, `include_str!`'d at build) -- a prose edit there requires a Rust rebuild via the normal cascade, no cross-repo sync step. Gate/residual text stays genuinely zero-rebuild: editable `gm-plugkit/instructions/gates/` and `gm-plugkit/instructions/residual/` in this repo, read at runtime with `prose.rs`'s per-key fallback to a compiled default (`.gm/instructions/<key>.md` per-project-overridable). Detail in the recall store (`recall: string-externalization project`).
26
26
 
27
- ## WASM guest, three interchangeable hosts (agentplug-runner is the live one)
27
+ ## WASM guest, one native host (agentplug-runner is the sole loader)
28
28
 
29
- Plugkit-core = the wasm cdylib guest (the gm brain), published as `plugkit.wasm`/`plugkit-slim.wasm` from `AnEntrypoint/plugkit-bin` by the cascade. It is host-agnostic: THREE distinct native/JS hosts load that same byte-identical wasm and serve the byte-identical spool ABI (`in/`/`out/` layout, verb names), so a gm-driven session works identically regardless of which booted it. In precedence order (`gm-plugkit/cli.js::tryDelegateToRunner`):
29
+ Plugkit-core = the wasm cdylib guest (the gm brain), published as `plugkit.wasm`/`plugkit-slim.wasm` from `AnEntrypoint/plugkit-bin` by the cascade. It is host-agnostic, and `agentplug-runner` is the SOLE host that loads it. The JS wasm-host (`plugkit-wasm-wrapper.js` + `gm-plugkit/wrapper/`) and the redundant `gm-runner` native host are both retired -- there is no fallback loader; agentplug-runner is required.
30
30
 
31
- 1. **`agentplug-runner`** (repo `AnEntrypoint/agentplug`, published to `AnEntrypoint/agentplug-bin`) -- the LIVE, preferred host. A real native wasmtime binary that loads gm.wasm as one plugin among siblings (`bert`/`libsql`/`treesitter`), routing gm.wasm's `host_plugin_call`/`host_vec_embed` to those shared plugins. This is why gm fetches `plugkit-slim.wasm` (no baked-in embedding weights -- bert supplies them out-of-wasm). `.status.json` `runtime` reads `agentplug` when this host is serving. Browser is driven via direct CDP and task via a native process registry, both native in agentplug-host.
32
- 2. **`gm-runner`** (rs-plugkit `crates/gm-runner/`, published to `AnEntrypoint/gm-runner-bin`) -- a native FALLBACK, tried second. Loads the same wasm as a single module with a stub `host_plugin_call` and no shared-plugin routing; strictly a subset of agentplug-runner. Its own CDP browser + task registry were ported to match agentplug. Retirement is tracked (`gm-runner-redundant-host-consolidation`).
33
- 3. **`plugkit-wasm-wrapper.js`** (JS/bun, in `gm-plugkit/`) -- the original JS host, tried last, only when no native runner is published for the platform. The `gm-plugkit` npm identity + `bootstrap.js` (seed/refresh) + `cli.js` (delegation) stay load-bearing as the thin launcher edge (`bun x gm-plugkit@latest spool` re-execs into the first available native runner and exits); the wrapper's own wasm-host code + `supervisor.js` only run as the pure-JS fallback.
31
+ **`agentplug-runner`** (repo `AnEntrypoint/agentplug`, published to `AnEntrypoint/agentplug-bin`) is a real native wasmtime binary that loads gm.wasm as one plugin among siblings (`bert`/`libsql`/`treesitter`), routing gm.wasm's `host_plugin_call`/`host_vec_embed` to those shared plugins. This is why gm fetches `plugkit-slim.wasm` (no baked-in embedding weights -- bert supplies them out-of-wasm). `.status.json` `runtime` reads `agentplug`. Browser is driven via direct CDP and task via a native process registry, both native in agentplug-host (agentplug's `crates/agentplug-host/src/browser.rs` has its own embedded `cdp_eval.js`, so the browser verb needs nothing from the retired JS wrapper).
34
32
 
35
- `bin/install.js` best-effort-downloads BOTH native runners (agentplug-runner and gm-runner, each sha256-verified) but hard-requires neither -- a platform with no published binary silently falls through to the JS host. Each host fetches the same `plugkit.wasm`/`plugkit-slim.wasm` from `plugkit-bin`, sha256-pinned. Size + embedded-model mechanics: the recall store (`recall: WASM-only plugkit size mechanics`).
33
+ The `gm-plugkit` npm identity stays load-bearing as the thin launcher edge only: `bun x gm-plugkit@latest spool` (`cli.js::tryDelegateToRunner`) re-execs into `~/.gm-tools/agentplug-runner` and exits; `bootstrap.js` still seeds per-project wiring and downloads `plugkit.wasm`/`plugkit-slim.wasm` (sha256-pinned) from `plugkit-bin`. `startSpoolDaemon()` launches agentplug-runner or, when it is absent, fails loudly with an actionable install message -- there is no silent no-loader state. Size + embedded-model mechanics: the recall store (`recall: WASM-only plugkit size mechanics`).
36
34
 
37
- **Native runners auto-update the wasm they serve, not themselves.** agentplug-runner and gm-runner each poll `plugkit-bin`'s Releases API every 600s for a newer tag; on a mismatch they download+verify the new wasm and a local-version-skew check triggers a clean in-process reload. The runner *executables* self-update only via `bin/install.js` re-run (agentplug-runner additionally adopts a staged `.new` on the next start).
35
+ **`bin/install.js` hard-requires agentplug-runner.** It downloads the sha256-verified native runner from `AnEntrypoint/agentplug-bin` for the host platform, and if none is published (or the download/verify fails) it fails the install loudly with a clear message rather than leaving the user with no loader. There is no JS-host fallback to silently fall through to anymore.
38
36
 
39
- Wasm host-import link-module rule (`#[link(wasm_import_module="env")]` on every host-import extern block, every dep crate): the recall store (`recall: wasm host-import link-module trap`).
40
-
41
- **`plugkit-wasm-wrapper.js` is ESM; import node builtins at module scope, never inline `require()`** (recall store: `recall: wrapper require not defined under bun`).
37
+ **agentplug-runner auto-updates the wasm it serves, not itself.** It polls `plugkit-bin`'s Releases API every 600s for a newer tag; on a mismatch it downloads+verifies the new wasm and a local-version-skew check triggers a clean in-process reload. The runner *executable* self-updates only via `bin/install.js` re-run (it additionally adopts a staged `.new` on the next start).
42
38
 
43
- **`~/.gm-tools/` is one shared install across every project's watcher on the machine -- never run gm-plugkit's own `cli.js`/`bootstrap.js` directly against the gm source checkout on a live machine.** `bun x gm-plugkit@latest` fetches an isolated npm-temp copy and is always safe; a direct `node`/`bun` invocation of the repo's own `gm-plugkit/cli.js` copies the local dev-tree wrapper over the shared install and cascades a wrapper-sha-drift kill across every other project's watcher. `ensureWrapperFresh()` refuses this unless `GM_PLUGKIT_ALLOW_DEV_WRAPPER_OVERWRITE=1` is set (recall store: `recall: shared install machine-wide wrapper-drift storm`).
39
+ Wasm host-import link-module rule (`#[link(wasm_import_module="env")]` on every host-import extern block, every dep crate): the recall store (`recall: wasm host-import link-module trap`).
44
40
 
45
41
  **Every single-instance/lock guard is atomic** (O_EXCL / atomic-rename), never check-then-act (recall store: `recall: supervisor churn TOCTOU atomic guard`).
46
42
 
package/bin/bootstrap.js CHANGED
@@ -595,7 +595,7 @@ function pidCommandLineForKillGuard(pid) {
595
595
  }
596
596
 
597
597
  function pidIsPlugkitProcess(pid) {
598
- return /plugkit-wasm-wrapper\.js|plugkit-supervisor\.js|gm-plugkit[\\\/]supervisor\.js/i.test(pidCommandLineForKillGuard(pid));
598
+ return /agentplug-runner(\.exe)?/i.test(pidCommandLineForKillGuard(pid));
599
599
  }
600
600
 
601
601
  function writeKillAttribution(targetSpoolDir, info) {
package/bin/install.js CHANGED
@@ -176,7 +176,9 @@ function runPlugkitBootstrap() {
176
176
  // agentplug-runner's CI (AnEntrypoint/agentplug's .github/workflows/release.yml)
177
177
  // publishing to AnEntrypoint/agentplug-bin -- must stay byte-identical to that
178
178
  // workflow's `artifact:` matrix values. Returns null for a host combination CI
179
- // does not build, so the caller falls back to the JS host without crashing.
179
+ // does not build; agentplug-runner is the sole spool loader now (the JS host
180
+ // was retired), so a null here means there is no loader for this platform and
181
+ // the installer fails loudly rather than leaving a silent no-loader state.
180
182
  function agentplugRunnerAssetName() {
181
183
  const plat = process.platform;
182
184
  const arch = process.arch;
@@ -231,15 +233,14 @@ function gmToolsDir() {
231
233
  }
232
234
 
233
235
 
234
- // Downloads agentplug-runner (the sole native wasm plugin-host now -- hosts
235
- // gm.wasm plus the separate libsql/bert/treesitter plugin wasm modules via a
236
+ // Downloads agentplug-runner (the SOLE native wasm plugin-host -- hosts gm.wasm
237
+ // plus the separate libsql/bert/treesitter plugin wasm modules via a
236
238
  // host-mediated plugin_call ABI) from agentplug-bin's GitHub Releases,
237
239
  // sha256-sidecar-verified with an atomic rename, so a corrupt/partial download
238
- // is never accepted. Best-effort: any failure (offline, unpublished platform,
239
- // network) resolves false rather than throwing, so install never hard-fails
240
- // over a runner fetch -- cli.js's tryDelegateToRunner then falls back to the
241
- // pure-JS wasm host. gm-runner was retired once agentplug-runner reached full
242
- // 6-platform parity (it was a strictly-inferior single-module duplicate).
240
+ // is never accepted. Returns true on success, false on any failure (offline,
241
+ // unpublished platform, network, sha mismatch). The JS wasm-host was retired,
242
+ // so agentplug-runner is required: main() hard-fails the install when this
243
+ // returns false, rather than leaving the user with no spool loader at all.
243
244
  async function downloadAgentplugRunner({ silent } = {}) {
244
245
  const assetName = agentplugRunnerAssetName();
245
246
  if (!assetName) {
@@ -350,7 +351,28 @@ async function main() {
350
351
  }
351
352
 
352
353
  await runPlugkitBootstrap();
353
- await downloadAgentplugRunner({ silent: false });
354
+
355
+ // agentplug-runner is the sole spool loader (the JS wasm-host was retired).
356
+ // Its download is a HARD requirement -- if the platform has no published
357
+ // binary, or the download/verification fails, there is no loader at all, so
358
+ // fail loudly with an actionable message rather than silently leaving the
359
+ // user with an installed skill that cannot boot a spool watcher.
360
+ const runnerReady = await downloadAgentplugRunner({ silent: false });
361
+ if (!runnerReady) {
362
+ const asset = agentplugRunnerAssetName();
363
+ err('');
364
+ err('FATAL: agentplug-runner could not be installed, and it is the sole spool loader.');
365
+ if (!asset) {
366
+ err(`No agentplug-runner binary is published for this platform (${process.platform}/${process.arch}).`);
367
+ err('The JS wasm-host fallback has been retired, so there is no loader available for this platform.');
368
+ err('Request a build at https://github.com/AnEntrypoint/agentplug-bin/issues, or install on a supported');
369
+ err('platform: linux-x64, linux-arm64, macos-x64, macos-arm64, windows-x64, windows-arm64.');
370
+ } else {
371
+ err(`The download or sha256 verification of ${asset} from AnEntrypoint/agentplug-bin failed`);
372
+ err('(see the messages above). Check your network/proxy and re-run: npx gm-skill install');
373
+ }
374
+ return 1;
375
+ }
354
376
 
355
377
  out('');
356
378
  out('Done. Open Claude Code and run /gm. New top-level skill dirs may need one restart to register.');
@@ -1 +1 @@
1
- acd7bc0e2ee1d9dc3311ee37e74e2cb5b59f15559a1d29aacc703ddacf323199 plugkit-slim.wasm
1
+ fc7364d869507c548db7846b67e71084c8d9c0c5a18c39a09afaf3f28155d4cd plugkit-slim.wasm
@@ -1 +1 @@
1
- 0.1.935
1
+ 0.1.936
@@ -1 +1 @@
1
- 89e0937c8879886b66420c807e7decc29d6723df91300c63028de6c6cefeed1b plugkit.wasm
1
+ 6492759f1cf535d6b899f22d1a31c00296397ae493aa755a6c735940128d9b18 plugkit.wasm
@@ -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,