gm-skill 2.0.1512 → 2.0.1514
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/bin/plugkit-supervisor.js +26 -0
- package/gm-plugkit/package.json +1 -1
- package/gm-plugkit/plugkit-wasm-wrapper.js +12 -2
- package/gm-plugkit/supervisor.js +29 -0
- package/gm.json +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -28,6 +28,8 @@ This repo IS the published `gm-skill` npm package. The repo root is the package
|
|
|
28
28
|
|
|
29
29
|
The plugkit stack runs as a wasm cdylib loaded by `plugkit-wasm-wrapper.js` under Node/bun. No native binaries are built, downloaded, or published. The shipped `plugkit.wasm` (~149MB, embeds bge-small-en-v1.5 for offline in-wasm embeddings) is fetched at bootstrap from `plugkit-wasm` npm / `plugkit-bin` gh-releases, sha256-pinned, not bundled in `gm-skill`. Full size/embedding mechanics in rs-learn (`recall: WASM-only plugkit size mechanics`).
|
|
30
30
|
|
|
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
|
+
|
|
31
33
|
## Spool dispatch ABI
|
|
32
34
|
|
|
33
35
|
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.
|
|
@@ -5,6 +5,13 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const { spawn, spawnSync } = require('child_process');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
function wrapperSha12OnDisk() {
|
|
11
|
+
try {
|
|
12
|
+
return crypto.createHash('sha256').update(fs.readFileSync(resolveWrapper())).digest('hex').slice(0, 12);
|
|
13
|
+
} catch (_) { return null; }
|
|
14
|
+
}
|
|
8
15
|
|
|
9
16
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
10
17
|
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
@@ -248,6 +255,25 @@ function checkWatcherHealth() {
|
|
|
248
255
|
severity: 'critical',
|
|
249
256
|
});
|
|
250
257
|
killChild('supervisor-killed-stale-heartbeat');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// A published wrapper-only fix (no wasm version bump) is copied to ~/.gm-tools by the next
|
|
261
|
+
// bootstrap's ensureWrapperFresh, but a healthy running watcher keeps the old wrapper until it
|
|
262
|
+
// restarts. Compare the watcher's reported wrapper_sha against the on-disk wrapper; on drift,
|
|
263
|
+
// recycle so the fix goes live without a manual kill. Skip while busy (a long verb is running).
|
|
264
|
+
const status = readStatus();
|
|
265
|
+
if (status && !(status.busy_until && status.busy_until > Date.now())) {
|
|
266
|
+
const reported = status.wrapper_sha || null;
|
|
267
|
+
const onDisk = wrapperSha12OnDisk();
|
|
268
|
+
if (reported && onDisk && reported !== onDisk) {
|
|
269
|
+
logEvent('supervisor.wrapper-sha-drift', {
|
|
270
|
+
watcher_pid: currentChildPid,
|
|
271
|
+
reported_sha: reported,
|
|
272
|
+
on_disk_sha: onDisk,
|
|
273
|
+
severity: 'info',
|
|
274
|
+
});
|
|
275
|
+
killChild('supervisor-killed-wrapper-sha-drift');
|
|
276
|
+
}
|
|
251
277
|
}
|
|
252
278
|
}
|
|
253
279
|
|
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.1514",
|
|
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": {
|
|
@@ -12,6 +12,13 @@ import { fileURLToPath } from 'url';
|
|
|
12
12
|
// (browser/chromium spawn, long exec) stamp a busy_until window into .status.json before the
|
|
13
13
|
// blocking call, so a liveness probe reads "busy" not "dead" while the event loop is blocked.
|
|
14
14
|
let _writeStatusBusy = () => {};
|
|
15
|
+
// Latest busy_until epoch ms stamped by a long synchronous verb (codesearch rebuild, chromium
|
|
16
|
+
// spawn). scanStalledTurns reads it so a busy watcher is not mis-flagged as an idle stall.
|
|
17
|
+
let _lastBusyUntil = 0;
|
|
18
|
+
// First 12 hex of sha256 of this watcher's own gmTools wrapper. Module-scoped so writeStatus
|
|
19
|
+
// (a different function scope) can stamp status.wrapper_sha, which the supervisor compares
|
|
20
|
+
// against the on-disk wrapper to recycle a watcher running a stale wrapper-only fix.
|
|
21
|
+
let _ownWrapperSha12 = '';
|
|
15
22
|
|
|
16
23
|
function spawnSync(cmd, args, opts) {
|
|
17
24
|
return _rawSpawnSync(cmd, args, { windowsHide: true, ...(opts || {}) });
|
|
@@ -373,6 +380,10 @@ function turnTick(sess, verb, taskBase, phase, prdPending) {
|
|
|
373
380
|
const STALL_MS = 300_000;
|
|
374
381
|
function scanStalledTurns() {
|
|
375
382
|
const now = Date.now();
|
|
383
|
+
// A long synchronous verb (codesearch index rebuild, chromium spawn) stamps busy_until and
|
|
384
|
+
// blocks the spool — the agent is legitimately waiting, not stalled. Honor it exactly as
|
|
385
|
+
// supervisor.js checkWatcherHealth does, so a busy watcher never emits a false mid-chain-stall.
|
|
386
|
+
if (_lastBusyUntil && _lastBusyUntil > now) return;
|
|
376
387
|
for (const [key, t] of _turns) {
|
|
377
388
|
if (!t || typeof t !== 'object' || !Number.isFinite(t.startTs)) continue;
|
|
378
389
|
if (t.stallEmitted) continue;
|
|
@@ -2071,7 +2082,6 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
2071
2082
|
|
|
2072
2083
|
|
|
2073
2084
|
const LOCK_PATH = path.join(spoolDir, '.watcher.lock');
|
|
2074
|
-
let _ownWrapperSha12 = '';
|
|
2075
2085
|
try {
|
|
2076
2086
|
const _crypto = require('crypto');
|
|
2077
2087
|
const _wp = path.join(GM_TOOLS_ROOT, 'plugkit-wasm-wrapper.js');
|
|
@@ -3095,7 +3105,7 @@ async function runSpoolWatcher(instance, spoolDir) {
|
|
|
3095
3105
|
// busy_until tells probes "alive but synchronously busy until this epoch ms" — read it
|
|
3096
3106
|
// alongside ts: a stale ts whose busy_until is still in the future is a busy watcher, not
|
|
3097
3107
|
// a dead one. The pre-verb writeStatus(busyMs) stamps it before the blocking call.
|
|
3098
|
-
if (busyMs && busyMs > 0) rec.busy_until = now + busyMs;
|
|
3108
|
+
if (busyMs && busyMs > 0) { rec.busy_until = now + busyMs; _lastBusyUntil = rec.busy_until; }
|
|
3099
3109
|
fs.writeFileSync(STATUS_PATH, JSON.stringify(rec));
|
|
3100
3110
|
} catch (_) {}
|
|
3101
3111
|
}
|
package/gm-plugkit/supervisor.js
CHANGED
|
@@ -4,8 +4,18 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const crypto = require('crypto');
|
|
7
8
|
const { spawn, spawnSync } = require('child_process');
|
|
8
9
|
|
|
10
|
+
function wrapperSha12OnDisk() {
|
|
11
|
+
try {
|
|
12
|
+
const primary = path.join(os.homedir(), '.gm-tools', 'plugkit-wasm-wrapper.js');
|
|
13
|
+
const fallback = path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit-wasm-wrapper.js');
|
|
14
|
+
const wp = fs.existsSync(primary) ? primary : fallback;
|
|
15
|
+
return crypto.createHash('sha256').update(fs.readFileSync(wp)).digest('hex').slice(0, 12);
|
|
16
|
+
} catch (_) { return null; }
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
10
20
|
const spoolDir = path.join(projectDir, '.gm', 'exec-spool');
|
|
11
21
|
fs.mkdirSync(spoolDir, { recursive: true });
|
|
@@ -227,6 +237,25 @@ function checkWatcherHealth() {
|
|
|
227
237
|
if (process.platform === 'win32') {
|
|
228
238
|
try { spawnSync('taskkill', ['/F', '/T', '/PID', String(currentChildPid)], { stdio: 'ignore', windowsHide: true, timeout: 3000 }); } catch (_) {}
|
|
229
239
|
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// A published wrapper-only fix (no wasm version bump) lands in ~/.gm-tools via the next
|
|
243
|
+
// bootstrap's ensureWrapperFresh, but a healthy running watcher keeps the old wrapper until it
|
|
244
|
+
// restarts. On wrapper_sha drift (watcher's reported sha != on-disk), recycle so the fix goes
|
|
245
|
+
// live without a manual kill. busy_until already returned above, so the watcher is not mid-verb.
|
|
246
|
+
const reported = status.wrapper_sha || null;
|
|
247
|
+
const onDisk = wrapperSha12OnDisk();
|
|
248
|
+
if (reported && onDisk && reported !== onDisk) {
|
|
249
|
+
logEvent('supervisor.wrapper-sha-drift', {
|
|
250
|
+
watcher_pid: currentChildPid,
|
|
251
|
+
reported_sha: reported,
|
|
252
|
+
on_disk_sha: onDisk,
|
|
253
|
+
severity: 'info',
|
|
254
|
+
});
|
|
255
|
+
try { process.kill(currentChildPid, 'SIGTERM'); } catch (_) {}
|
|
256
|
+
if (process.platform === 'win32') {
|
|
257
|
+
try { spawnSync('taskkill', ['/F', '/T', '/PID', String(currentChildPid)], { stdio: 'ignore', windowsHide: true, timeout: 3000 }); } catch (_) {}
|
|
258
|
+
}
|
|
230
259
|
}
|
|
231
260
|
}
|
|
232
261
|
|
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.1514",
|
|
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",
|