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.
- package/bootstrap-shared.js +1 -8
- package/bootstrap.js +2 -112
- package/cli.js +1 -10
- package/gm-process.js +0 -8
- package/package.json +1 -1
package/bootstrap-shared.js
CHANGED
|
@@ -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 (_) {
|
|
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;
|
|
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 (_) {
|
|
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;
|
|
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.
|
|
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": {
|