gm-skill 2.0.1414 → 2.0.1416

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.
@@ -1 +1 @@
1
- 0.1.559
1
+ 0.1.560
@@ -1 +1 @@
1
- d08baf97ddce9070182d3fa82316628c9469fbbc55b52908d238d75afd218a30 plugkit.wasm
1
+ c10f254d5910788be2eaecacedcd173666f92f1c5c7a10b4f11730dcb02d48ed plugkit.wasm
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1414",
3
+ "version": "2.0.1416",
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": {
@@ -8,6 +8,11 @@ import { spawn as _rawSpawn, spawnSync as _rawSpawnSync } from 'child_process';
8
8
  import net from 'net';
9
9
  import { fileURLToPath } from 'url';
10
10
 
11
+ // Set by the spool watcher's writeStatus closure once it is live. Lets long synchronous verbs
12
+ // (browser/chromium spawn, long exec) stamp a busy_until window into .status.json before the
13
+ // blocking call, so a liveness probe reads "busy" not "dead" while the event loop is blocked.
14
+ let _writeStatusBusy = () => {};
15
+
11
16
  function spawnSync(cmd, args, opts) {
12
17
  return _rawSpawnSync(cmd, args, { windowsHide: true, ...(opts || {}) });
13
18
  }
@@ -725,6 +730,9 @@ function runBrowserRunner(pw, args, timeoutMs) {
725
730
  const useShell = !!pw.shell;
726
731
  const spawnCmd = useShell && /\s/.test(pw.cmd) ? `"${pw.cmd}"` : pw.cmd;
727
732
  const spawnArgs = useShell ? allArgs.map(a => /[\s"]/.test(String(a)) ? `"${String(a).replace(/"/g, '\\"')}"` : a) : allArgs;
733
+ // Stamp a busy window before the synchronous spawn so the blocked event loop's stale heartbeat
734
+ // is not misread as a dead watcher. Pad past the spawn timeout for teardown.
735
+ _writeStatusBusy((timeoutMs || 30000) + 5000);
728
736
  return spawnSync(spawnCmd, spawnArgs, {
729
737
  encoding: 'utf-8',
730
738
  timeout: timeoutMs,
@@ -2774,15 +2782,16 @@ async function runSpoolWatcher(instance, spoolDir) {
2774
2782
  }
2775
2783
 
2776
2784
  const STATUS_PATH = path.join(spoolDir, '.status.json');
2777
- function writeStatus() {
2785
+ function writeStatus(busyMs) {
2778
2786
  try {
2779
2787
  const fileV = readFileVersionOnly() || null;
2780
2788
  const instV = _instanceVersionAtBoot || null;
2781
2789
  const version = instV || fileV;
2782
2790
  const drifted = !!(fileV && instV && fileV !== instV);
2783
- fs.writeFileSync(STATUS_PATH, JSON.stringify({
2791
+ const now = Date.now();
2792
+ const rec = {
2784
2793
  pid: process.pid,
2785
- ts: Date.now(),
2794
+ ts: now,
2786
2795
  version,
2787
2796
  instance_version: instV,
2788
2797
  file_version: fileV,
@@ -2791,10 +2800,19 @@ async function runSpoolWatcher(instance, spoolDir) {
2791
2800
  supervisor_pid: _supervisorPid,
2792
2801
  wrapper_sha: _ownWrapperSha12 || null,
2793
2802
  idle_limit_ms: IDLE_LIMIT_MS,
2794
- }));
2803
+ };
2804
+ // A synchronous verb (chromium spawn, long exec) blocks the event loop, so the 5s
2805
+ // heartbeat interval cannot fire for the duration. Without a hint, a liveness probe that
2806
+ // checks ts-within-15s reads the busy watcher as dead and may kill/respawn it mid-verb.
2807
+ // busy_until tells probes "alive but synchronously busy until this epoch ms" — read it
2808
+ // alongside ts: a stale ts whose busy_until is still in the future is a busy watcher, not
2809
+ // a dead one. The pre-verb writeStatus(busyMs) stamps it before the blocking call.
2810
+ if (busyMs && busyMs > 0) rec.busy_until = now + busyMs;
2811
+ fs.writeFileSync(STATUS_PATH, JSON.stringify(rec));
2795
2812
  } catch (_) {}
2796
2813
  }
2797
- setInterval(writeStatus, 5000);
2814
+ _writeStatusBusy = (ms) => { try { writeStatus(ms); } catch (_) {} };
2815
+ setInterval(() => writeStatus(), 5000);
2798
2816
  writeStatus();
2799
2817
 
2800
2818
  const TURN_SUMMARY_PATH = path.join(spoolDir, '.turn-summary.json');
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1414",
3
+ "version": "2.0.1416",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -17,5 +17,5 @@
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  },
20
- "plugkitVersion": "0.1.559"
20
+ "plugkitVersion": "0.1.560"
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1414",
3
+ "version": "2.0.1416",
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",
@@ -32,7 +32,7 @@ cat .gm/exec-spool/.status.json 2>/dev/null; echo ---; cat .gm/exec-spool/.turn-
32
32
 
33
33
  `.turn-summary.json` carries `phase`, `last_skill`, `prd_pending`, `last_instruction_ts`, `last_instruction_age_ms`, `long_gap_threshold_ms`, `browser_sessions_alive`, `update_available`, `deviations_30m`, `watcher_uptime_ms`. When age exceeds the threshold, your next non-orienting verb will be gated, dispatch `instruction` first. When `update_available` is non-null, the watcher has detected drift: kill the watcher then re-run the boot command below, which calls `ensureReady` to download the latest wasm and refresh the installed wrapper, then starts a fresh watcher loaded with the new code. The one-line sequence: `bun x gm-plugkit@latest --kill-stale-watchers; bun x gm-plugkit@latest spool > /dev/null 2>&1 &`. Then wait 8s and re-read `.status.json` to confirm `version` matches `update_available.latest`. `deviations_30m` is the rolling count of `deviation.*` events from hook+plugkit logs in the past 30 min, read it instead of running gmsniff at session start; non-zero indicates active drift worth investigating before continuing. `watcher_uptime_ms` shows how long the current watcher process has been alive; combined with `watcher_version` and `update_available` it tells you whether the watcher's loaded code is recent, a multi-hour uptime with no `update_available` means you're on latest, an uptime of seconds means a fresh boot just happened.
34
34
 
35
- Compare `.status.json` `ts` field to the printed epoch ms. If the gap is >15000, the watcher is dead, boot it:
35
+ Compare `.status.json` `ts` field to the printed epoch ms. If the gap is >15000, the watcher is dead, boot it. One exception: if `.status.json` carries `busy_until` and that value is still in the future (greater than the printed epoch ms), the watcher is alive but synchronously busy with a long verb (a browser/chromium spawn blocks the heartbeat for ~15-18s), wait for it, do not boot a second watcher. A stale `ts` with a future `busy_until` is busy, not dead.
36
36
 
37
37
  ```bash
38
38
  bun x gm-plugkit@latest spool > /dev/null 2>&1 &
@@ -44,7 +44,7 @@ bun x gm-plugkit@latest spool > /dev/null 2>&1 &
44
44
 
45
45
  Never poll the spool dir with `sleep && ls` or `Start-Sleep && Test-Path`, plugkit is synchronous from your view; if the response is not there, the watcher is dead (re-check `.status.json` mtime) or the verb is slow (check `.gm/exec-spool/.watcher.log`), not "still processing."
46
46
 
47
- **Dead-watcher recovery is mandatory, never abandon the dispatch.** If two consecutive re-Reads return "file does not exist" AND `.status.json` ts is stale (>15s gap from current epoch), the watcher is dead. Your next call is `bun x gm-plugkit@latest spool` to boot a fresh watcher (the wrapper has self-respawn paths now, one boot deploys every queued fix to disk). Then re-dispatch the original verb. Do NOT reach for an alternative tool, puppeteer-core, agent-browser, WebFetch, raw `chrome.exe`, none of these substitute for the `browser` verb. Reaching outside plugkit when the spool surface is reachable orphans state the next session cannot reap, bypasses paper §23 witness gates, and ages the project's discipline. The recovery is always: notice dead → boot → re-dispatch. The full chain from spool-write to disk-Read-success is the only admissible loop; any short-circuit produces unreconcilable state.
47
+ **Dead-watcher recovery is mandatory, never abandon the dispatch.** If two consecutive re-Reads return "file does not exist" AND `.status.json` ts is stale (>15s gap from current epoch) AND `busy_until` is absent or in the past, the watcher is dead. (A future `busy_until` means a long synchronous verb is running, the response will land when it finishes; wait, do not boot.) Your next call is `bun x gm-plugkit@latest spool` to boot a fresh watcher (the wrapper has self-respawn paths now, one boot deploys every queued fix to disk). Then re-dispatch the original verb. Do NOT reach for an alternative tool, puppeteer-core, agent-browser, WebFetch, raw `chrome.exe`, none of these substitute for the `browser` verb. Reaching outside plugkit when the spool surface is reachable orphans state the next session cannot reap, bypasses paper §23 witness gates, and ages the project's discipline. The recovery is always: notice dead → boot → re-dispatch. The full chain from spool-write to disk-Read-success is the only admissible loop; any short-circuit produces unreconcilable state.
48
48
 
49
49
  When writing the spool input from PowerShell, pass `-Encoding utf8` (or use `[System.IO.File]::WriteAllText($path, $body)` which defaults to UTF-8 no-BOM). PowerShell 5.1's default `Out-File` / `Set-Content` write UTF-16 LE with BOM, which the watcher detects and re-decodes (`spool.body-encoding-recoded` event in gmsniff), but the deviation is a fingerprint of an instruction you missed. Use `bash -c "echo -n '...' > ..."` or `Write` tool instead when the body is structured JSON.
50
50