gm-plugkit 2.0.2083 → 2.0.2084

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.
@@ -6,12 +6,6 @@ const os = require('os');
6
6
  const { spawnSync } = require('child_process');
7
7
  const { pidAlive, sha256OfFileSync } = require('./gm-process');
8
8
 
9
- // Functions shared byte-for-byte between gm-plugkit/bootstrap.js and
10
- // bin/bootstrap.js -- the two installer entry points diverge in install
11
- // STRATEGY (npm-install vs npx-extract, slim/fat artifact selection,
12
- // pinned-reexec) but the cache/lock/prune/kill/wiring mechanics underneath
13
- // were identical copy-paste, drifting apart in small accidental ways release
14
- // over release. Centralized here so a fix lands once for both callers.
15
9
 
16
10
  const LOCK_STALE_MS = 30 * 60 * 1000;
17
11
  const ATTEMPT_TIMEOUT_MS = 10 * 60 * 1000;
@@ -93,7 +87,6 @@ function acquireLock(lockPath) {
93
87
  }
94
88
 
95
89
  function releaseLock(lockPath) {
96
- // best-effort, missing file is fine
97
90
  try { fs.unlinkSync(lockPath); } catch (_) {}
98
91
  }
99
92
 
@@ -119,7 +112,7 @@ function pruneOldVersions(root, keepVersion) {
119
112
  if (fs.existsSync(lock)) { try { fs.unlinkSync(lock); } catch (_) {} }
120
113
  try {
121
114
  fs.rmSync(dir, { recursive: true, force: true, maxRetries: 1, retryDelay: 50 });
122
- } catch (_) { /* prune skip, non-fatal */ }
115
+ } catch (_) {}
123
116
  }
124
117
  } catch (_) {}
125
118
  }
package/bootstrap.js CHANGED
@@ -31,13 +31,6 @@ const {
31
31
  ensureNextStepWiring: ensureNextStepWiringShared,
32
32
  } = shared;
33
33
 
34
- // This file's own ensureNextStepWiring below delegates the shared prefix
35
- // (seed next-step.md, prepend CLAUDE.md, append AGENTS.md) to
36
- // bootstrap-shared's ensureNextStepWiringShared, then adds strictly more
37
- // (a managed .npmignore block) that bootstrap-shared's leaner version does
38
- // not need -- genuinely-different logic on top of a shared base, not two
39
- // independent copies of the same first 40 lines.
40
-
41
34
  function resolveWindowsExe(cmd) {
42
35
  if (process.platform !== 'win32') return cmd;
43
36
  try {
@@ -79,7 +72,6 @@ function writeBootstrapError(spec) {
79
72
  }
80
73
 
81
74
  function clearBootstrapError() {
82
- // best-effort, missing file is fine
83
75
  try {
84
76
  const projectDir = resolveProjectRoot(process.env.CLAUDE_PROJECT_DIR || process.cwd());
85
77
  fs.unlinkSync(path.join(projectDir, '.gm', 'exec-spool', '.bootstrap-error.json'));
@@ -90,15 +82,6 @@ function sha256Hex(buf) {
90
82
  return crypto.createHash('sha256').update(buf).digest('hex');
91
83
  }
92
84
 
93
- // User/agent edits to .gm/instructions/*.md are the whole point of vendoring
94
- // them per-project -- a bare content-diff overwrite treats "user diverged
95
- // from the shipped default" identically to "file is just stale", silently
96
- // clobbering local edits on every routine bootstrap/auto-update. The
97
- // manifest records the sha256 of what THIS install last shipped for each
98
- // key; a local file matching that hash is safe to refresh (it's untouched),
99
- // but a local file that differs from BOTH the manifest AND the new default
100
- // is a real user edit -- write the new default beside it as .md.new instead
101
- // of overwriting, so the edit survives and the update is still visible.
102
85
  function instructionsManifestPath(cwd) {
103
86
  return path.join(cwd, '.gm', '.instructions-shipped-manifest.json');
104
87
  }
@@ -145,21 +128,15 @@ function ensureInstructionsBundle(cwd) {
145
128
  }
146
129
  if (prev.equals(next)) {
147
130
  manifest[childRel] = nextHash;
148
- continue; // already current, nothing to do
131
+ continue;
149
132
  }
150
133
  const lastShippedHash = manifest[childRel];
151
134
  const localMatchesLastShipped = lastShippedHash && sha256Hex(prev) === lastShippedHash;
152
135
  if (localMatchesLastShipped || !lastShippedHash) {
153
- // Untouched since we last wrote it (or first time we've ever
154
- // recorded a hash for this key -- pre-manifest install, treat as
155
- // ours) -- safe to refresh with the new default.
156
136
  fs.writeFileSync(dst, next);
157
137
  manifest[childRel] = nextHash;
158
138
  copied++;
159
139
  } else {
160
- // Local content diverges from what we shipped: a real user/agent
161
- // edit. Never overwrite it -- stage the new default beside it so
162
- // the update is visible without destroying the edit.
163
140
  try { fs.writeFileSync(dst + '.new', next); } catch (_) {}
164
141
  preserved++;
165
142
  obsEvent('bootstrap', 'instructions-bundle.user-edit-preserved', { target: dst });
@@ -181,13 +158,6 @@ function ensureInstructionsBundle(cwd) {
181
158
  function ensureNextStepWiring(cwd) {
182
159
  const changes = ensureNextStepWiringShared(cwd);
183
160
 
184
- // gm writes its own runtime data into .gm/ in every project it drives; if that
185
- // project is an npm package, that data must never be published. Maintain a
186
- // managed .npmignore block excluding .gm/, mirroring the managed-gitignore
187
- // mechanism: append a marker-delimited block to any existing .npmignore
188
- // without clobbering the user's own entries, and skip entirely when a
189
- // package.json `files:` allowlist is present (an allowlist already excludes
190
- // everything not listed, so .gm/ is safe without an .npmignore).
191
161
  try {
192
162
  const pkgPath = path.join(cwd, 'package.json');
193
163
  let hasFilesAllowlist = false;
@@ -218,17 +188,6 @@ function ensureNextStepWiring(cwd) {
218
188
  }
219
189
  }
220
190
 
221
-
222
- // Slim-artifact eligibility: plugkit-core's embed.rs probes host_vec_embed
223
- // before ever loading its wasm-embedded safetensors fallback (see
224
- // rs-plugkit/crates/plugkit-core/src/embed.rs::init_ctx) -- a slim build
225
- // (feature=slim, no embedded weights at all) is only safe to fetch on a host
226
- // that actually answers host_vec_embed for real. agentplug-runner answers it
227
- // via its shared bert plugin daemon; the check is gated on the runner binary
228
- // existing under ~/.gm-tools so bootstrap-time artifact selection and runtime
229
- // embed-delegation eligibility never disagree. Absence means no host_vec_embed
230
- // answer will ever come, so fetching fat (which carries its own wasm-side
231
- // embedding fallback) is the only safe choice.
232
191
  function hasNativeEmbedRunner() {
233
192
  const dir = gmToolsDir();
234
193
  const names = process.platform === 'win32'
@@ -237,26 +196,6 @@ function hasNativeEmbedRunner() {
237
196
  return names.some(n => { try { return fs.existsSync(path.join(dir, n)); } catch (_) { return false; } });
238
197
  }
239
198
 
240
- // Root a project-dir resolution at the git COMMON dir, not the raw cwd/
241
- // CLAUDE_PROJECT_DIR -- a worktree (e.g. Workflow's isolation:'worktree'
242
- // agents, each `git worktree add`-ing a fresh physical directory) shares the
243
- // SAME underlying repo as its main checkout but has its own separate
244
- // directory tree. Every cwd-derived project-dir computation in this file and
245
- // in cli.js must funnel through this so a worktree-spawned process resolves to
246
- // the SAME .gm/exec-spool/ as its main-repo sibling -- otherwise the
247
- // single-instance watcher guard can never see a sibling worktree's
248
- // already-running watcher, and every worktree cold-boots its own independent
249
- // watcher (each loading the full embed model + running its own cold reindex of
250
- // what is, conceptually, the same project). Live-measured
251
- // this session under real concurrent multi-agent load: this was the actual
252
- // root cause of a user-flagged "memory grows to ~2GB then clears" churn
253
- // pattern -- N worktrees of one repo each independently paying full
254
- // cold-embed cost instead of sharing one already-warm watcher, and the
255
- // resulting CPU contention pushed genuinely-busy processes past their
256
- // heartbeat deadline into a restart cycle that produces the sawtooth memory
257
- // pattern. agentplug-runner roots its own browser-session state at the git
258
- // common dir for the same reason (same fix, same reason, applied one layer up
259
- // at the process-boot-dedup level).
260
199
  function resolveProjectRoot(start) {
261
200
  const resolved = path.resolve(start);
262
201
  try {
@@ -276,24 +215,10 @@ function readVersionFile() {
276
215
  return fs.readFileSync(p, 'utf8').trim();
277
216
  }
278
217
 
279
- // readVersionFile() throws loudly on its own (missing/unreadable file is a
280
- // real, surfaced error) -- callers below that swallow it are choosing to
281
- // no-op rather than propagate, so they log what they swallowed instead of
282
- // going silent. This is the actual behavior at every call site already
283
- // (killStaleDaemonIfVersionChanged just returns, ensureReady leaves
284
- // pinnedVersion null) -- only the missing observability was the gap.
285
-
286
218
  function readShaManifest() {
287
219
  const p = path.join(wrapperDir, 'plugkit.sha256');
288
220
  if (!fs.existsSync(p)) return null;
289
221
  const raw = fs.readFileSync(p, 'utf8');
290
- // Two real formats seen in the wild for this file: the checked-in local
291
- // gm-plugkit/plugkit.sha256 is a JSON manifest ({"plugkit.wasm":"<sha>"}),
292
- // written by the version-bump automation; a GitHub-release .sha256 sidecar
293
- // (fetched directly from plugkit-bin releases) is a standard sha256sum-format
294
- // line ("<hash> <filename>"). Try JSON first since that's this file's own
295
- // real on-disk shape -- the sha256sum-line regex never matched it, silently
296
- // returning {} and skipping verification on every bootstrap call.
297
222
  try {
298
223
  const parsed = JSON.parse(raw);
299
224
  if (parsed && typeof parsed === 'object') {
@@ -303,7 +228,7 @@ function readShaManifest() {
303
228
  }
304
229
  return out;
305
230
  }
306
- } catch (_) { /* not JSON, fall through to sha256sum-line parsing */ }
231
+ } catch (_) {}
307
232
  const out = {};
308
233
  for (const line of raw.split(/\r?\n/)) {
309
234
  const m = line.match(/^([0-9a-f]{64})\s+(\S+)\s*$/i);
@@ -399,15 +324,6 @@ function httpGetBuffer(url, timeoutMs) {
399
324
  });
400
325
  }
401
326
 
402
- // artifactName selects the REMOTE release asset ('plugkit.wasm' fat or
403
- // 'plugkit-slim.wasm' slim) -- the local destPath filename is unaffected,
404
- // same convention gm-runner's own download.rs::bootstrap_plugkit_wasm uses
405
- // (fixed local name, artifact-selected remote source). A slim fetch that 404s
406
- // (older release predating the slim publish step, or the asset genuinely
407
- // missing) falls back to fetching fat rather than failing the whole
408
- // bootstrap -- a host capable of running the slim build is also always a
409
- // valid fat-build host (fat is a strict superset of slim's capability), so
410
- // this fallback never produces incorrect behavior, only a larger download.
411
327
  async function downloadFromGithubReleases(destPath, version, artifactName) {
412
328
  const name = artifactName || 'plugkit.wasm';
413
329
  const base = `https://github.com/AnEntrypoint/plugkit-bin/releases/download/v${version}`;
@@ -472,7 +388,6 @@ async function extractNpmPackageWithRetry(destPath, version) {
472
388
  throw lastErr;
473
389
  }
474
390
 
475
-
476
391
  function killStaleDaemonIfVersionChanged() {
477
392
  let currentVersion;
478
393
  try { currentVersion = readVersionFile(); }
@@ -491,22 +406,10 @@ function killStaleDaemonIfVersionChanged() {
491
406
  writeDaemonVersion(currentVersion);
492
407
  }
493
408
 
494
-
495
409
  async function bootstrap(opts) {
496
410
  opts = opts || {};
497
411
  const version = readVersionFile();
498
412
  const shaManifest = readShaManifest();
499
- // Artifact selection: slim (no wasm-embedded safetensors, ~130MB smaller)
500
- // only when this host has a native host_vec_embed answerer on disk
501
- // (gm-runner or agentplug-runner under ~/.gm-tools -- see
502
- // hasNativeEmbedRunner) -- everyone else fetches fat, unchanged from
503
- // before this selection logic existed. remoteArtifact is the release-asset
504
- // name; the LOCAL cache filename stays 'plugkit.wasm' either way (matching
505
- // gm-runner's own download.rs convention) so nothing downstream that reads
506
- // the local wasm path needs to know which variant landed. The cache
507
- // sub-directory is kept distinct per-kind (v<version> vs v<version>-slim)
508
- // so a fat and slim download of the same version never collide under the
509
- // same sha/sentinel check.
510
413
  const useSlim = hasNativeEmbedRunner();
511
414
  const remoteArtifact = useSlim ? 'plugkit-slim.wasm' : 'plugkit.wasm';
512
415
  const wasmName = 'plugkit.wasm';
@@ -581,11 +484,6 @@ async function bootstrap(opts) {
581
484
  } catch (_) {}
582
485
  }
583
486
  if (useSlim) {
584
- // The plugkit-wasm npm package only ever ships the fat artifact (see
585
- // release.yml's npm-publish step, which cp's release-assets/plugkit.wasm
586
- // -- never plugkit-slim.wasm -- into the package). Skip the npm-extract
587
- // attempt entirely for a slim fetch and go straight to GitHub Releases,
588
- // which is where slim is actually published.
589
487
  try {
590
488
  await downloadFromGithubReleases(partialPath, version, remoteArtifact);
591
489
  } catch (ghErr) {
@@ -642,14 +540,6 @@ async function bootstrap(opts) {
642
540
  log(`decision: fetch reason: install-complete (${finalPath})`);
643
541
  obsEvent('bootstrap', 'install.done', { path: finalPath, version, kind: 'plugkit-wasm' });
644
542
  proactiveKillForNewInstall(version);
645
- // pruneOldVersions keeps only the dir literally named v<keepVersion> --
646
- // verDir uses a '-slim' suffix for the slim cache slot (see useSlim
647
- // above), so the keep-token passed here must match that same suffix or
648
- // pruneOldVersions deletes the directory just populated by THIS run
649
- // before copyWasmToGmTools below can read from it (live-witnessed this
650
- // session: a real end-to-end bootstrap() run on a native-runner host hit
651
- // exactly this -- 'pruned .../v0.1.906-slim' followed by an ENOENT on the
652
- // immediately-following copy, because 'v0.1.906' != 'v0.1.906-slim').
653
543
  pruneOldVersions(root, useSlim ? `${version}-slim` : version);
654
544
  copyWasmToGmTools(finalPath, version);
655
545
  clearBootstrapError();
package/cli.js CHANGED
@@ -121,15 +121,6 @@ function writeCliError(phase, err) {
121
121
  } catch (_) {}
122
122
  }
123
123
 
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).
133
124
  function tryDelegateToRunner(args) {
134
125
  if (process.env.GM_PLUGKIT_NO_RUNNER_DELEGATE === '1') return false;
135
126
  const exeName = process.platform === 'win32' ? 'agentplug-runner.exe' : 'agentplug-runner';
@@ -137,7 +128,7 @@ function tryDelegateToRunner(args) {
137
128
  if (!fs.existsSync(runnerPath)) return false;
138
129
  try {
139
130
  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
131
+ if (result.error) return false;
141
132
  process.exit(typeof result.status === 'number' ? result.status : 0);
142
133
  } catch (_) {
143
134
  return false;
package/gm-process.js CHANGED
@@ -4,9 +4,6 @@ const fs = require('fs');
4
4
  const crypto = require('crypto');
5
5
  const { spawnSync } = require('child_process');
6
6
 
7
- // Pure-leaf helpers shared byte-for-byte between bin/bootstrap.js and
8
- // gm-plugkit/bootstrap.js (they were identical inline copies in both). Only
9
- // node builtins, no bootstrap-local state -- safe to centralize here.
10
7
  function ensureDir(dir) {
11
8
  fs.mkdirSync(dir, { recursive: true });
12
9
  }
@@ -50,15 +47,10 @@ function pidCommandLineForKillGuard(pid) {
50
47
  } catch (_) { return ''; }
51
48
  }
52
49
 
53
- // Is `pid` still alive? signal-0 probe (throws ESRCH once the process is gone).
54
50
  function pidAliveSync(pid) {
55
51
  try { process.kill(pid, 0); return true; } catch (_) { return false; }
56
52
  }
57
53
 
58
- // Block (via a short spawnSync sleep, no async) until `pid` dies or timeoutMs
59
- // elapses. Shared by cli.js (daemon recycle) and supervisor.js (killChild) —
60
- // both previously carried a byte-divergent inline copy (execFileSync vs
61
- // spawnSync) of this exact loop.
62
54
  function waitForPidDeath(pid, timeoutMs) {
63
55
  const deadline = Date.now() + timeoutMs;
64
56
  while (Date.now() < deadline) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.2083",
3
+ "version": "2.0.2084",
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": {