gm-plugkit 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1512",
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/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