gm-skill 2.0.1970 → 2.0.1972
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/gm-plugkit/bootstrap.js +60 -0
- package/gm-plugkit/cli.js +36 -1
- package/gm-plugkit/gm-process.js +1 -1
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +66 -0
- package/gm-plugkit/supervisor.js +20 -1
- package/gm.json +1 -1
- package/package.json +1 -1
package/gm-plugkit/bootstrap.js
CHANGED
|
@@ -849,6 +849,63 @@ function ensureGmPlugkitVersionFresh() {
|
|
|
849
849
|
} catch (_) { return false; }
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
853
|
+
|
|
854
|
+
function readPinnedGmPlugkitVersion() {
|
|
855
|
+
try {
|
|
856
|
+
const p = path.join(gmToolsDir(), 'gm-plugkit.version');
|
|
857
|
+
if (!fs.existsSync(p)) return null;
|
|
858
|
+
const v = fs.readFileSync(p, 'utf-8').trim();
|
|
859
|
+
if (!v || !SEMVER_RE.test(v)) return null;
|
|
860
|
+
return v;
|
|
861
|
+
} catch (_) { return null; }
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function resolveBunRuntime() {
|
|
865
|
+
const candidates = process.platform === 'win32' ? ['bun.exe', 'bun'] : ['bun'];
|
|
866
|
+
for (const c of candidates) {
|
|
867
|
+
try {
|
|
868
|
+
const r = spawnSync('where', [c], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true, timeout: 800 });
|
|
869
|
+
if (r.status === 0) {
|
|
870
|
+
const lines = (r.stdout || '').split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
871
|
+
const exe = lines.find(l => /\.exe$/i.test(l)) || lines[0];
|
|
872
|
+
if (exe) return exe;
|
|
873
|
+
}
|
|
874
|
+
} catch (_) {}
|
|
875
|
+
}
|
|
876
|
+
return process.platform === 'win32' ? 'bun.exe' : 'bun';
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function spawnPinnedBoot(extraArgs) {
|
|
880
|
+
const args = Array.isArray(extraArgs) ? extraArgs : [];
|
|
881
|
+
const pinned = readPinnedGmPlugkitVersion();
|
|
882
|
+
if (!pinned) {
|
|
883
|
+
return { ok: false, reason: 'no-pin-file', fallback: '@latest' };
|
|
884
|
+
}
|
|
885
|
+
const runtime = resolveBunRuntime();
|
|
886
|
+
const bunxArgs = ['x', `gm-plugkit@${pinned}`, ...args];
|
|
887
|
+
const startedMs = Date.now();
|
|
888
|
+
let result;
|
|
889
|
+
try {
|
|
890
|
+
result = spawnSync(runtime, bunxArgs, {
|
|
891
|
+
stdio: 'inherit',
|
|
892
|
+
windowsHide: true,
|
|
893
|
+
shell: false,
|
|
894
|
+
env: { ...process.env, GM_PLUGKIT_PINNED_REEXEC: '1' },
|
|
895
|
+
});
|
|
896
|
+
} catch (e) {
|
|
897
|
+
return { ok: false, reason: 'spawn-failed', error: e.message, pinned_version: pinned, fallback: '@latest' };
|
|
898
|
+
}
|
|
899
|
+
const durationMs = Date.now() - startedMs;
|
|
900
|
+
if (result.error) {
|
|
901
|
+
return { ok: false, reason: 'spawn-error', error: result.error.message, pinned_version: pinned, fallback: '@latest' };
|
|
902
|
+
}
|
|
903
|
+
if (typeof result.status === 'number' && result.status !== 0) {
|
|
904
|
+
return { ok: false, reason: 'pinned-invocation-nonzero-exit', status: result.status, pinned_version: pinned, duration_ms: durationMs, fallback: '@latest' };
|
|
905
|
+
}
|
|
906
|
+
return { ok: true, pinned_version: pinned, duration_ms: durationMs, status: result.status };
|
|
907
|
+
}
|
|
908
|
+
|
|
852
909
|
function discoverBundledSkillsAndSources() {
|
|
853
910
|
const found = new Map();
|
|
854
911
|
found.set('gm', path.join(__dirname, 'SKILL.md'));
|
|
@@ -1213,6 +1270,9 @@ module.exports = {
|
|
|
1213
1270
|
ensureGmPlugkitVersionFresh,
|
|
1214
1271
|
ensureSkillMdFresh,
|
|
1215
1272
|
ensureWrapperFresh,
|
|
1273
|
+
readPinnedGmPlugkitVersion,
|
|
1274
|
+
resolveBunRuntime,
|
|
1275
|
+
spawnPinnedBoot,
|
|
1216
1276
|
};
|
|
1217
1277
|
|
|
1218
1278
|
if (require.main === module) {
|
package/gm-plugkit/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 } = require('./bootstrap');
|
|
8
|
+
const { ensureReady, startSpoolDaemon, gmToolsDir, readVersionFile, ensureGmPlugkitVersionFresh, ensureSkillMdFresh, ensureWrapperFresh, isReady, getWasmPath, readPinnedGmPlugkitVersion, spawnPinnedBoot } = require('./bootstrap');
|
|
9
9
|
|
|
10
10
|
function getWasmPathSafe() {
|
|
11
11
|
try { return getWasmPath(); } catch (_) { return null; }
|
|
@@ -77,6 +77,22 @@ Usage:
|
|
|
77
77
|
wrapper sha differs from on-disk
|
|
78
78
|
(lets new wrapper code load on next bootstrap)
|
|
79
79
|
bun x gm-plugkit@latest --help Show this help
|
|
80
|
+
|
|
81
|
+
Opt-in pinned fast-path (skips bunx's own @latest npm-registry resolution):
|
|
82
|
+
GM_PLUGKIT_PREFER_PINNED=1 bun x gm-plugkit@latest spool
|
|
83
|
+
bun x gm-plugkit@latest spool --pinned
|
|
84
|
+
Reads ~/.gm-tools/gm-plugkit.version
|
|
85
|
+
(written by every successful boot) and
|
|
86
|
+
re-execs 'bun x gm-plugkit@<pinned-exact-version>'
|
|
87
|
+
with the remaining args -- bunx skips registry
|
|
88
|
+
resolution for an exact version once cached, so
|
|
89
|
+
this is genuinely faster than @latest on repeat
|
|
90
|
+
boots. Falls back to normal @latest-driven
|
|
91
|
+
behavior automatically when no pin file exists
|
|
92
|
+
yet (first-ever boot) or the pinned invocation
|
|
93
|
+
fails (stale/unpublished pinned version).
|
|
94
|
+
Opt-in only -- the default boot line above is
|
|
95
|
+
unaffected.
|
|
80
96
|
`;
|
|
81
97
|
|
|
82
98
|
function readDiskWasmVersion() {
|
|
@@ -238,6 +254,25 @@ function writeCliError(phase, err) {
|
|
|
238
254
|
process.exit(killStaleWatchers());
|
|
239
255
|
}
|
|
240
256
|
|
|
257
|
+
const wantsPinned = process.env.GM_PLUGKIT_PREFER_PINNED === '1' || args.includes('--pinned');
|
|
258
|
+
const alreadyReexecedPinned = process.env.GM_PLUGKIT_PINNED_REEXEC === '1';
|
|
259
|
+
if (wantsPinned && !alreadyReexecedPinned) {
|
|
260
|
+
const passArgs = args.filter(a => a !== '--pinned');
|
|
261
|
+
const pinned = readPinnedGmPlugkitVersion();
|
|
262
|
+
if (pinned) {
|
|
263
|
+
writeCliStatus({ phase: 'pinned-fast-path', pinned_version: pinned });
|
|
264
|
+
const result = spawnPinnedBoot(passArgs);
|
|
265
|
+
if (result.ok) {
|
|
266
|
+
process.exit(typeof result.status === 'number' ? result.status : 0);
|
|
267
|
+
}
|
|
268
|
+
writeCliStatus({ phase: 'pinned-fast-path-fallback', reason: result.reason, pinned_version: result.pinned_version || null });
|
|
269
|
+
console.error(`[gm-plugkit] pinned fast-path failed (${result.reason}), falling back to @latest resolution`);
|
|
270
|
+
} else {
|
|
271
|
+
writeCliStatus({ phase: 'pinned-fast-path-no-pin-file' });
|
|
272
|
+
console.error('[gm-plugkit] GM_PLUGKIT_PREFER_PINNED set but no ~/.gm-tools/gm-plugkit.version pin file yet -- falling back to @latest resolution (this boot will write the pin for next time)');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
241
276
|
ensureSpoolDir();
|
|
242
277
|
writeCliStatus({ phase: 'starting', args });
|
|
243
278
|
|
package/gm-plugkit/gm-process.js
CHANGED
|
@@ -5,7 +5,7 @@ const { spawnSync } = require('child_process');
|
|
|
5
5
|
function pidCommandLineForKillGuard(pid) {
|
|
6
6
|
try {
|
|
7
7
|
if (process.platform === 'win32') {
|
|
8
|
-
const r = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `(Get-
|
|
8
|
+
const r = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `(Get-WmiObject Win32_Process -Filter "ProcessId=${Number(pid)}").CommandLine`], { encoding: 'utf8', windowsHide: true, timeout: 5000 });
|
|
9
9
|
return String((r && r.stdout) || '');
|
|
10
10
|
}
|
|
11
11
|
const r = spawnSync('ps', ['-p', String(pid), '-o', 'args='], { encoding: 'utf8', timeout: 5000 });
|
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1972",
|
|
4
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.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -2504,6 +2504,69 @@ function hostTaskProc(action, params) {
|
|
|
2504
2504
|
}
|
|
2505
2505
|
}
|
|
2506
2506
|
|
|
2507
|
+
let _gmRunnerEmbedBinPath;
|
|
2508
|
+
function resolveGmRunnerEmbedBin() {
|
|
2509
|
+
if (_gmRunnerEmbedBinPath !== undefined) return _gmRunnerEmbedBinPath;
|
|
2510
|
+
const exe = path.join(GM_TOOLS_ROOT, process.platform === 'win32' ? 'gm-runner.exe' : 'gm-runner');
|
|
2511
|
+
_gmRunnerEmbedBinPath = fs.existsSync(exe) ? exe : null;
|
|
2512
|
+
return _gmRunnerEmbedBinPath;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// Slim-build support: plugkit-core's embed.rs probes host_vec_embed BEFORE
|
|
2516
|
+
// ever loading the 133MB wasm-embedded safetensors fallback (see
|
|
2517
|
+
// rs-plugkit/crates/plugkit-core/src/embed.rs::init_ctx) -- if this function
|
|
2518
|
+
// returns a real embedding, the wasm-side model never loads at all. Wired
|
|
2519
|
+
// here to gm-runner's OWN native candle path (crates/gm-runner/src/embed.rs)
|
|
2520
|
+
// via its `embed-text` one-shot subcommand (stdin=text,
|
|
2521
|
+
// stdout={"embedding":[...]}), a synchronous spawnSync call so the wasm-side
|
|
2522
|
+
// caller (a synchronous extern "C" import) can block on it correctly. Used
|
|
2523
|
+
// ONLY when a real ~/.gm-tools/gm-runner(.exe) binary is present on this
|
|
2524
|
+
// host; if absent, returns null immediately and the caller (host_vec_embed
|
|
2525
|
+
// below) falls through to -1, which makes plugkit-core's own probe fail and
|
|
2526
|
+
// fall back to loading the wasm-embedded fat-build safetensors model exactly
|
|
2527
|
+
// as before -- embedding capability is never silently lost on a host with no
|
|
2528
|
+
// gm-runner installed, by design (this is the real fallback the
|
|
2529
|
+
// slim-wasm-default-flip PRD row required be live before any default-feature
|
|
2530
|
+
// flip).
|
|
2531
|
+
function hostEmbedViaGmRunner(text) {
|
|
2532
|
+
const bin = resolveGmRunnerEmbedBin();
|
|
2533
|
+
if (!bin) return null;
|
|
2534
|
+
try {
|
|
2535
|
+
const r = spawnSync(bin, ['embed-text'], {
|
|
2536
|
+
input: text,
|
|
2537
|
+
encoding: 'utf-8',
|
|
2538
|
+
windowsHide: true,
|
|
2539
|
+
timeout: 30000,
|
|
2540
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
2541
|
+
});
|
|
2542
|
+
if (r.error || r.status !== 0) return null;
|
|
2543
|
+
const parsed = JSON.parse(r.stdout || '{}');
|
|
2544
|
+
if (!Array.isArray(parsed.embedding)) return null;
|
|
2545
|
+
return parsed.embedding;
|
|
2546
|
+
} catch (_) {
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
globalThis.__hostEmbedSync = function __hostEmbedSync(textPtr, textLen, outPtr, outLen, instance) {
|
|
2552
|
+
try {
|
|
2553
|
+
const text = readWasmStr(instance, textPtr, textLen);
|
|
2554
|
+
if (!text) return -1;
|
|
2555
|
+
const values = hostEmbedViaGmRunner(text);
|
|
2556
|
+
if (!values || values.length === 0) return -1;
|
|
2557
|
+
const dim = Math.min(values.length, outLen >>> 0);
|
|
2558
|
+
const bytes = new Uint8Array(dim * 4);
|
|
2559
|
+
const view = new DataView(bytes.buffer);
|
|
2560
|
+
for (let i = 0; i < dim; i++) view.setFloat32(i * 4, values[i], true);
|
|
2561
|
+
const buffer = instance.exports.memory.buffer;
|
|
2562
|
+
guardWasmRange(buffer, outPtr, dim * 4, '__hostEmbedSync:write');
|
|
2563
|
+
new Uint8Array(buffer, outPtr, dim * 4).set(bytes);
|
|
2564
|
+
return dim;
|
|
2565
|
+
} catch (_) {
|
|
2566
|
+
return -1;
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
|
|
2507
2570
|
function makeHostFunctions(instanceRef) {
|
|
2508
2571
|
return {
|
|
2509
2572
|
host_fs_read: (pathPtr, pathLen) => {
|
|
@@ -4445,6 +4508,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
4445
4508
|
wasm_aborted: true,
|
|
4446
4509
|
}));
|
|
4447
4510
|
fs.renameSync(abortTmpPath, abortOutPath);
|
|
4511
|
+
try { fs.writeFileSync(abortOutPath + '.ready', ''); } catch (_) {}
|
|
4448
4512
|
try { fs.unlinkSync(filePath); } catch (_) {}
|
|
4449
4513
|
} catch (_) {}
|
|
4450
4514
|
unmarkProcessed(key);
|
|
@@ -4566,6 +4630,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
4566
4630
|
const outTmpPath = outPath + '.tmp.' + process.pid;
|
|
4567
4631
|
fs.writeFileSync(outTmpPath, resultStr);
|
|
4568
4632
|
fs.renameSync(outTmpPath, outPath);
|
|
4633
|
+
try { fs.writeFileSync(outPath + '.ready', ''); } catch (_) {}
|
|
4569
4634
|
const dur_ms = Date.now() - t0;
|
|
4570
4635
|
console.log(`[dispatch] <- verb=${verb} task=${taskBase} ms=${dur_ms} out=${resultStr.length}b`);
|
|
4571
4636
|
logEvent('plugkit', 'dispatch.end', { verb, task: taskBase, dur_ms, out_bytes: resultStr.length });
|
|
@@ -4611,6 +4676,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
4611
4676
|
const errTmpPath = errOutPath + '.tmp.' + process.pid;
|
|
4612
4677
|
fs.writeFileSync(errTmpPath, JSON.stringify({ ok: false, error: e.message }));
|
|
4613
4678
|
fs.renameSync(errTmpPath, errOutPath);
|
|
4679
|
+
try { fs.writeFileSync(errOutPath + '.ready', ''); } catch (_) {}
|
|
4614
4680
|
} catch (_) {}
|
|
4615
4681
|
try { fs.unlinkSync(filePath); } catch (_) {}
|
|
4616
4682
|
unmarkProcessed(key);
|
package/gm-plugkit/supervisor.js
CHANGED
|
@@ -29,7 +29,26 @@ const LOG_PATH = path.join(spoolDir, '.watcher.log');
|
|
|
29
29
|
const GM_LOG_ROOT = _sharedGmLogRoot;
|
|
30
30
|
|
|
31
31
|
const POLL_INTERVAL_MS = 10_000;
|
|
32
|
-
|
|
32
|
+
// Was 30s. Live-measured this session under real multi-agent concurrent load
|
|
33
|
+
// (13 parallel gm-skill-driven agents, each booting its own watcher on the
|
|
34
|
+
// same machine): 74 of 94 total watcher restarts logged in one session were
|
|
35
|
+
// heartbeat-stale kills, the dominant restart cause by a wide margin -- each
|
|
36
|
+
// restart drops the process's memory back to near-zero then it climbs again
|
|
37
|
+
// through a fresh cold-embed pass, which is the actual mechanism behind the
|
|
38
|
+
// "memory grows to ~2GB then clears" pattern a user observed and correctly
|
|
39
|
+
// flagged as inefficient. The busy_until shielding mechanism (see writeStatus
|
|
40
|
+
// call sites in plugkit-wasm-wrapper.js) already protects genuinely-busy
|
|
41
|
+
// single dispatches, but under real CPU contention from many concurrent
|
|
42
|
+
// processes, even the setInterval-based busy_until REFRESH callback itself
|
|
43
|
+
// can be delayed past the old 30s window before it fires -- a starvation
|
|
44
|
+
// problem one level above the per-dispatch busy-window fixes already shipped
|
|
45
|
+
// this session. Raising the stale threshold gives real contention more slack
|
|
46
|
+
// before concluding a process is dead, trading a slightly slower true-dead
|
|
47
|
+
// detection for far fewer false-positive kills-and-restarts (each restart is
|
|
48
|
+
// far more expensive than the extra wait -- a full cold embed pass observed
|
|
49
|
+
// taking 90-120s+ under load) -- a clear net win under the contention this
|
|
50
|
+
// session repeatedly measured.
|
|
51
|
+
const STATUS_STALE_MS = 90_000;
|
|
33
52
|
const MAX_RESTART_BURST = 5;
|
|
34
53
|
const RESTART_WINDOW_MS = 60_000;
|
|
35
54
|
const BURST_BACKOFF_MS = 60_000;
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1972",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|