gm-skill 2.0.1515 → 2.0.1517
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 +2 -0
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +20 -19
- package/gm-plugkit/supervisor.js +37 -0
- package/gm.json +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -30,6 +30,8 @@ The plugkit stack runs as a wasm cdylib loaded by `plugkit-wasm-wrapper.js` unde
|
|
|
30
30
|
|
|
31
31
|
**Every wasm host-import `extern "C"` block carries `#[link(wasm_import_module = "env")]`.** The host provides every possible host fn (host_kv_get/put/query, host_vec_search, host_git, host_log, host_now_ms, host_fs_*, host_env_get, host_exec_js, host_random_fill, …) under the `env` import module (`plugkit-wasm-wrapper.js` `importObject.env`). A bare `extern "C"` block links only because lenient linkers tolerate the unresolved module; the strict Linux release `rust-lld` in CI errors `undefined symbol: host_*` and Build-WASM fails. This holds in rs-plugkit AND every dep crate linked into the cdylib (rs-learn) AND any sibling that builds wasm (rs-exec, rs-search). The trap: `cargo check` and even `cargo build --release` on a non-Linux host both pass while CI fails — the linker differs by host, so the only reproduction is a Linux release link; the CI job log is admin-gated, so Build-WASM echoes `::error::` annotations to surface the lld error publicly. Add a host import anywhere and the block carries the attribute or the cascade goes dark. Full incident in rs-learn (`recall: cascade outage wasm import module link`).
|
|
32
32
|
|
|
33
|
+
**`plugkit-wasm-wrapper.js` is ESM; never use inline `require()` for a node builtin — import it at module scope.** The wrapper runs under both node and bun, and the supervisor's `resolveRuntime()` prefers bun. Under bun's ESM, `require` is not a global, so an inline `const x = require('crypto'|'net'|'http'|'https'|'child_process')` throws `require is not defined` — and because those calls sit in `catch(_){}` blocks, the failure is silent: it broke `_ownWrapperSha12` (status.wrapper_sha stayed null, leaving the supervisor wrapper-sha-drift recycle inert), `_wrapperShaAtBoot` and its self-drift-restart, the synthetic-session cwd-hash, and the file-index sha — all only under the bun watcher, which is why it hid for so long (node-run watchers have `require` via CJS interop). Every node builtin is imported once at the top (`import crypto from 'crypto'`, etc.); inline `require` of a builtin is forbidden. Full incident in rs-learn (`recall: wrapper require not defined under bun`).
|
|
34
|
+
|
|
33
35
|
## Spool dispatch ABI
|
|
34
36
|
|
|
35
37
|
Agents dispatch verbs by writing to `.gm/exec-spool/in/<verb>/<N>.txt` (request body) and reading the response from `.gm/exec-spool/out/<verb>-<N>.json` (nested verbs) or `.gm/exec-spool/out/<N>.json` (root verbs). The wasm orchestrator services every possible verb; the harness never executes side effects directly.
|
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.1517",
|
|
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": {
|
|
@@ -3,9 +3,14 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import https from 'https';
|
|
6
|
+
import http from 'http';
|
|
6
7
|
import { watch } from 'fs';
|
|
8
|
+
import * as _childProcess from 'child_process';
|
|
7
9
|
import { spawn as _rawSpawn, spawnSync as _rawSpawnSync } from 'child_process';
|
|
8
10
|
import net from 'net';
|
|
11
|
+
const _netModule = net;
|
|
12
|
+
const _httpModule = http;
|
|
13
|
+
const _httpsModule = https;
|
|
9
14
|
import { fileURLToPath } from 'url';
|
|
10
15
|
|
|
11
16
|
// Set by the spool watcher's writeStatus closure once it is live. Lets long synchronous verbs
|
|
@@ -437,7 +442,7 @@ function readCurrentSess() {
|
|
|
437
442
|
let resolved = found || process.env.CLAUDE_SESSION_ID || process.env.GM_SESSION_ID || '';
|
|
438
443
|
if (!resolved) {
|
|
439
444
|
if (!__sessCache.syntheticSess) {
|
|
440
|
-
const cwdHash =
|
|
445
|
+
const cwdHash = crypto.createHash('sha1').update(process.cwd()).digest('hex').slice(0, 8);
|
|
441
446
|
__sessCache.syntheticSess = `cwd-${cwdHash}-pid${process.pid}`;
|
|
442
447
|
}
|
|
443
448
|
resolved = __sessCache.syntheticSess;
|
|
@@ -488,7 +493,7 @@ function autoRecordBrowserEditsFromBody(body, cwd, taskBase, verb) {
|
|
|
488
493
|
try { st = fs.statSync(abs); } catch (_) { continue; }
|
|
489
494
|
if (!st.isFile()) continue;
|
|
490
495
|
let hash = '';
|
|
491
|
-
try { hash =
|
|
496
|
+
try { hash = crypto.createHash('sha256').update(fs.readFileSync(abs)).digest('hex').slice(0, 12); } catch (_) {}
|
|
492
497
|
const idx = list.findIndex(e => e && e.file === rel);
|
|
493
498
|
const entry = { file: rel, ts: Date.now(), hash, source_verb: verb, source_task: taskBase };
|
|
494
499
|
if (idx === -1) { list.push(entry); added++; } else { list[idx] = entry; }
|
|
@@ -823,7 +828,7 @@ function resolveWindowsExeLocal(cmd) {
|
|
|
823
828
|
|
|
824
829
|
function isPortReachableSync(host, port, timeoutMs) {
|
|
825
830
|
const r = spawnSync(process.execPath, ['-e', `
|
|
826
|
-
const net =
|
|
831
|
+
const net = _netModule;
|
|
827
832
|
const s = net.connect({ port: ${port}, host: ${JSON.stringify(host)} });
|
|
828
833
|
let done = false;
|
|
829
834
|
s.on('connect', () => { done = true; s.destroy(); process.exit(0); });
|
|
@@ -835,7 +840,7 @@ function isPortReachableSync(host, port, timeoutMs) {
|
|
|
835
840
|
|
|
836
841
|
function findFreePortSync() {
|
|
837
842
|
const r = spawnSync(process.execPath, ['-e', `
|
|
838
|
-
const net =
|
|
843
|
+
const net = _netModule;
|
|
839
844
|
const srv = net.createServer();
|
|
840
845
|
srv.listen(0, '127.0.0.1', () => { const p = srv.address().port; srv.close(() => { process.stdout.write(String(p)); }); });
|
|
841
846
|
srv.on('error', e => { process.stderr.write(e.message); process.exit(1); });
|
|
@@ -846,7 +851,7 @@ function findFreePortSync() {
|
|
|
846
851
|
|
|
847
852
|
function isPortAliveSync(port) {
|
|
848
853
|
const r = spawnSync(process.execPath, ['-e', `
|
|
849
|
-
const net =
|
|
854
|
+
const net = _netModule;
|
|
850
855
|
const s = net.connect({ port: ${port}, host: '127.0.0.1' });
|
|
851
856
|
s.on('connect', () => { s.destroy(); process.exit(0); });
|
|
852
857
|
s.on('error', () => process.exit(1));
|
|
@@ -944,7 +949,7 @@ function findInstalledChromiumBinary() {
|
|
|
944
949
|
|
|
945
950
|
function fetchJsonSync(url, timeoutMs) {
|
|
946
951
|
const r = spawnSync(process.execPath, ['-e', `
|
|
947
|
-
const http =
|
|
952
|
+
const http = _httpModule;
|
|
948
953
|
const req = http.get(${JSON.stringify(url)}, (res) => {
|
|
949
954
|
let buf = '';
|
|
950
955
|
res.on('data', d => buf += d);
|
|
@@ -1052,7 +1057,7 @@ function gracefulCloseBrowser(entry, reason) {
|
|
|
1052
1057
|
const info = fetchJsonSync(`http://127.0.0.1:${port}/json/version`, 600);
|
|
1053
1058
|
if (info && info.webSocketDebuggerUrl) {
|
|
1054
1059
|
spawnSync(process.execPath, ['-e', `
|
|
1055
|
-
const http =
|
|
1060
|
+
const http = _httpModule;
|
|
1056
1061
|
const req = http.request({host:'127.0.0.1',port:${port},path:'/json/close/browser',method:'GET',timeout:1500},
|
|
1057
1062
|
res => { res.resume(); res.on('end', () => process.exit(0)); });
|
|
1058
1063
|
req.on('error', () => process.exit(1));
|
|
@@ -2083,10 +2088,8 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2083
2088
|
|
|
2084
2089
|
const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
|
|
2085
2090
|
try {
|
|
2086
|
-
const _crypto = require('crypto');
|
|
2087
2091
|
const _wp = path.join(GM_TOOLS_ROOT, 'plugkit-wasm-wrapper.js');
|
|
2088
|
-
_ownWrapperSha12 =
|
|
2089
|
-
try { logEvent('plugkit', 'watcher.own-wrapper-sha', { sha: _ownWrapperSha12, wrapper_path: _wp }); } catch (_) {}
|
|
2092
|
+
_ownWrapperSha12 = crypto.createHash('sha256').update(fs.readFileSync(_wp)).digest('hex').slice(0, 12);
|
|
2090
2093
|
} catch (e) {
|
|
2091
2094
|
try { logEvent('plugkit', 'watcher.own-wrapper-sha-failed', { error: String(e && e.message || e), gm_tools_root: GM_TOOLS_ROOT }); } catch (_) {}
|
|
2092
2095
|
}
|
|
@@ -2350,7 +2353,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2350
2353
|
try { own = JSON.parse(fs.readFileSync(ownPkgJsonFile, 'utf-8')).version; } catch (_) {}
|
|
2351
2354
|
}
|
|
2352
2355
|
if (!own) return;
|
|
2353
|
-
const https =
|
|
2356
|
+
const https = _httpsModule;
|
|
2354
2357
|
let _probeErrored = false;
|
|
2355
2358
|
const req = https.get('https://registry.npmjs.org/gm-plugkit/latest', { timeout: 10000, headers: { 'user-agent': 'plugkit-watcher' } }, (res) => {
|
|
2356
2359
|
let body = '';
|
|
@@ -2384,7 +2387,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2384
2387
|
try { logEvent('plugkit', 'gm-plugkit.self-stale', { running_version: own, latest_version: latest, detected_by: 'watcher-periodic-probe' }); } catch (_) {}
|
|
2385
2388
|
console.error(`[plugkit-wasm] gm-plugkit self-stale: running ${own}, latest npm ${latest} -> spawning replacement via bun x gm-plugkit@latest spool and exiting`);
|
|
2386
2389
|
try {
|
|
2387
|
-
const cp =
|
|
2390
|
+
const cp = _childProcess;
|
|
2388
2391
|
const bunPath = process.env.GM_BUN_PATH || 'bun';
|
|
2389
2392
|
const child = cp.spawn(bunPath, ['x', `gm-plugkit@${latest}`, 'spool'], {
|
|
2390
2393
|
cwd: process.cwd(),
|
|
@@ -2475,7 +2478,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2475
2478
|
console.error(`[plugkit-wasm] version drift detected: instance=${instV} file=${fileV} — spawning replacement via bun x gm-plugkit@latest spool, waiting for its heartbeat before exiting`);
|
|
2476
2479
|
let spawnOk = false;
|
|
2477
2480
|
try {
|
|
2478
|
-
const cp =
|
|
2481
|
+
const cp = _childProcess;
|
|
2479
2482
|
const bunPath = process.env.GM_BUN_PATH || 'bun';
|
|
2480
2483
|
const child = cp.spawn(bunPath, ['x', 'gm-plugkit@latest', 'spool'], {
|
|
2481
2484
|
cwd: process.cwd(),
|
|
@@ -2550,15 +2553,13 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2550
2553
|
const _wrapperPathInstalled = path.join(GM_TOOLS_ROOT, 'plugkit-wasm-wrapper.js');
|
|
2551
2554
|
let _wrapperShaAtBoot = '';
|
|
2552
2555
|
try {
|
|
2553
|
-
|
|
2554
|
-
_wrapperShaAtBoot = _crypto.createHash('sha256').update(fs.readFileSync(_wrapperPathInstalled)).digest('hex');
|
|
2556
|
+
_wrapperShaAtBoot = crypto.createHash('sha256').update(fs.readFileSync(_wrapperPathInstalled)).digest('hex');
|
|
2555
2557
|
} catch (_) {}
|
|
2556
2558
|
let _wrapperDriftLoggedOnce = false;
|
|
2557
2559
|
setInterval(() => {
|
|
2558
2560
|
try {
|
|
2559
2561
|
if (!_wrapperShaAtBoot) return;
|
|
2560
|
-
const
|
|
2561
|
-
const cur = _crypto.createHash('sha256').update(fs.readFileSync(_wrapperPathInstalled)).digest('hex');
|
|
2562
|
+
const cur = crypto.createHash('sha256').update(fs.readFileSync(_wrapperPathInstalled)).digest('hex');
|
|
2562
2563
|
if (cur === _wrapperShaAtBoot) return;
|
|
2563
2564
|
const bootReason = process.env.PLUGKIT_BOOT_REASON || 'unknown';
|
|
2564
2565
|
const unsupervised = bootReason === 'direct-no-supervisor';
|
|
@@ -2573,7 +2574,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2573
2574
|
});
|
|
2574
2575
|
console.error(`[plugkit-wasm] wrapper.js drift detected — spawning replacement directly from installed wrapper then exiting`);
|
|
2575
2576
|
try {
|
|
2576
|
-
const cp =
|
|
2577
|
+
const cp = _childProcess;
|
|
2577
2578
|
const child = cp.spawn(process.execPath, [_wrapperPathInstalled, 'spool'], {
|
|
2578
2579
|
cwd: process.cwd(),
|
|
2579
2580
|
detached: true,
|
|
@@ -2585,7 +2586,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2585
2586
|
} catch (e) {
|
|
2586
2587
|
console.error(`[plugkit-wasm] direct node spawn failed: ${e.message}; falling back to bun x`);
|
|
2587
2588
|
try {
|
|
2588
|
-
const cp =
|
|
2589
|
+
const cp = _childProcess;
|
|
2589
2590
|
const bunPath = process.env.GM_BUN_PATH || 'bun';
|
|
2590
2591
|
const child = cp.spawn(bunPath, ['x', 'gm-plugkit@latest', 'spool'], {
|
|
2591
2592
|
cwd: process.cwd(),
|
package/gm-plugkit/supervisor.js
CHANGED
|
@@ -23,6 +23,7 @@ fs.mkdirSync(spoolDir, { recursive: true });
|
|
|
23
23
|
const STATUS_PATH = path.join(spoolDir, '.status.json');
|
|
24
24
|
const SHUTDOWN_REASON_PATH = path.join(spoolDir, '.shutdown-reason.json');
|
|
25
25
|
const SUPERVISOR_PATH = path.join(spoolDir, '.supervisor.json');
|
|
26
|
+
const SUPERVISOR_PID_PATH = path.join(spoolDir, '.supervisor.pid');
|
|
26
27
|
const LOG_PATH = path.join(spoolDir, '.watcher.log');
|
|
27
28
|
const GM_LOG_ROOT = process.env.GM_LOG_DIR || path.join(os.homedir(), '.claude', 'gm-log');
|
|
28
29
|
|
|
@@ -66,6 +67,36 @@ function pidAlive(pid) {
|
|
|
66
67
|
try { process.kill(pid, 0); return true; } catch (_) { return false; }
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
// Single-instance guard. findSupervisorPid (skill-bootstrap.js) reads .supervisor.pid to early-return
|
|
71
|
+
// when a supervisor is already running; without it every bootstrap spawns a duplicate supervisor,
|
|
72
|
+
// and duplicates spawn duplicate watchers that lock-fight in an endless spawn-reject churn. Write the
|
|
73
|
+
// pid file on startup and refuse to start if a live peer already holds it.
|
|
74
|
+
function acquireSingleInstance() {
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(SUPERVISOR_PID_PATH)) {
|
|
77
|
+
const other = parseInt(fs.readFileSync(SUPERVISOR_PID_PATH, 'utf-8').trim(), 10);
|
|
78
|
+
if (Number.isFinite(other) && other !== process.pid && pidAlive(other)) {
|
|
79
|
+
logEvent('supervisor.refused-duplicate', { existing_pid: other, severity: 'warn' });
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
fs.writeFileSync(SUPERVISOR_PID_PATH, String(process.pid));
|
|
84
|
+
return true;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
logEvent('supervisor.pid-write-failed', { error: e.message, severity: 'warn' });
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function releaseSingleInstance() {
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync(SUPERVISOR_PID_PATH)) {
|
|
94
|
+
const raw = fs.readFileSync(SUPERVISOR_PID_PATH, 'utf-8').trim();
|
|
95
|
+
if (parseInt(raw, 10) === process.pid) fs.unlinkSync(SUPERVISOR_PID_PATH);
|
|
96
|
+
}
|
|
97
|
+
} catch (_) {}
|
|
98
|
+
}
|
|
99
|
+
|
|
69
100
|
function readStatus() {
|
|
70
101
|
try { return JSON.parse(fs.readFileSync(STATUS_PATH, 'utf-8')); } catch (_) { return null; }
|
|
71
102
|
}
|
|
@@ -273,9 +304,15 @@ process.on('SIGTERM', () => {
|
|
|
273
304
|
if (currentChildPid && pidAlive(currentChildPid)) {
|
|
274
305
|
try { process.kill(currentChildPid, 'SIGTERM'); } catch (_) {}
|
|
275
306
|
}
|
|
307
|
+
releaseSingleInstance();
|
|
276
308
|
process.exit(0);
|
|
277
309
|
});
|
|
310
|
+
process.on('exit', () => { releaseSingleInstance(); });
|
|
278
311
|
|
|
312
|
+
if (!acquireSingleInstance()) {
|
|
313
|
+
process.stderr.write('[plugkit-supervisor] another supervisor is alive; exiting\n');
|
|
314
|
+
process.exit(0);
|
|
315
|
+
}
|
|
279
316
|
writeSupervisorStatus('starting', {});
|
|
280
317
|
logEvent('supervisor.starting', { spool_dir: spoolDir });
|
|
281
318
|
try { fs.unlinkSync(path.join(spoolDir, '.pre-supervised-watcher.json')); } catch (_) {}
|
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.1517",
|
|
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",
|