gm-skill 2.0.1849 → 2.0.1851

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.
@@ -1047,13 +1047,12 @@ function probeUnsupervisedWatcher(spoolDir) {
1047
1047
  }
1048
1048
 
1049
1049
  function resolveNodeRuntime() {
1050
- const isNodeExe = (p) => /(^|[\\/])node(\.exe)?$/i.test(String(p || ''));
1051
1050
  const candidates = [];
1052
- if (isNodeExe(process.env.GM_NODE_PATH)) candidates.push(process.env.GM_NODE_PATH);
1053
- if (isNodeExe(process.execPath)) candidates.push(process.execPath);
1051
+ if (process.env.PLUGKIT_RUNTIME) candidates.push(process.env.PLUGKIT_RUNTIME);
1052
+ candidates.push('bun');
1054
1053
  try {
1055
1054
  const which = process.platform === 'win32' ? 'where' : 'which';
1056
- const out = require('child_process').spawnSync(which, ['node'], { encoding: 'utf8', windowsHide: true });
1055
+ const out = require('child_process').spawnSync(which, ['bun'], { encoding: 'utf8', windowsHide: true });
1057
1056
  if (out && out.stdout) {
1058
1057
  const first = out.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
1059
1058
  if (first) candidates.push(first);
@@ -1062,6 +1061,21 @@ function resolveNodeRuntime() {
1062
1061
  for (const c of candidates) {
1063
1062
  try { const r = require('child_process').spawnSync(c, ['--version'], { stdio: 'ignore', windowsHide: true }); if (r && r.status === 0) return c; } catch (_) {}
1064
1063
  }
1064
+ const isNodeExe = (p) => /(^|[\\/])node(\.exe)?$/i.test(String(p || ''));
1065
+ const nodeCandidates = [];
1066
+ if (isNodeExe(process.env.GM_NODE_PATH)) nodeCandidates.push(process.env.GM_NODE_PATH);
1067
+ if (isNodeExe(process.execPath)) nodeCandidates.push(process.execPath);
1068
+ try {
1069
+ const which = process.platform === 'win32' ? 'where' : 'which';
1070
+ const out = require('child_process').spawnSync(which, ['node'], { encoding: 'utf8', windowsHide: true });
1071
+ if (out && out.stdout) {
1072
+ const first = out.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
1073
+ if (first) nodeCandidates.push(first);
1074
+ }
1075
+ } catch (_) {}
1076
+ for (const c of nodeCandidates) {
1077
+ try { const r = require('child_process').spawnSync(c, ['--version'], { stdio: 'ignore', windowsHide: true }); if (r && r.status === 0) return c; } catch (_) {}
1078
+ }
1065
1079
  return process.execPath;
1066
1080
  }
1067
1081
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1849",
3
+ "version": "2.0.1851",
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": {
@@ -117,12 +117,14 @@ function clearVerbActive() {
117
117
 
118
118
  process.on('uncaughtException', (err) => {
119
119
  try { console.error('[plugkit-wasm] uncaught:', err && err.stack || err); } catch (_) {}
120
+ try { killAllTasks('crash:uncaughtException'); } catch (_) {}
120
121
  emitShutdownReason('uncaughtException', err);
121
122
  process.exit(1);
122
123
  });
123
124
 
124
125
  process.on('unhandledRejection', (reason) => {
125
126
  try { console.error('[plugkit-wasm] unhandled rejection:', reason && reason.stack || reason); } catch (_) {}
127
+ try { killAllTasks('crash:unhandledRejection'); } catch (_) {}
126
128
  emitShutdownReason('unhandledRejection', reason instanceof Error ? reason : new Error(String(reason)));
127
129
  process.exit(1);
128
130
  });
@@ -744,24 +746,38 @@ function aggregateCpuProfile(profile, topN) {
744
746
 
745
747
  const BROWSER_RUNNER_BIN = process.env.GM_BROWSER_RUNNER_BIN || 'playwriter';
746
748
 
749
+ function findCachedBunRunnerBin() {
750
+ try {
751
+ const cacheDir = path.join(os.homedir(), '.bun', 'install', 'cache');
752
+ const entries = fs.readdirSync(cacheDir).filter(n => n.startsWith(`${BROWSER_RUNNER_BIN}@`));
753
+ for (const name of entries) {
754
+ const binJs = path.join(cacheDir, name, 'bin.js');
755
+ if (fs.existsSync(binJs)) return binJs;
756
+ }
757
+ } catch (_) {}
758
+ return null;
759
+ }
760
+
747
761
  function findBrowserRunner() {
748
- const npmR = spawnSync('npm', ['root', '-g'], { encoding: 'utf-8', shell: true });
749
- if (npmR.status === 0 && npmR.stdout.trim()) {
750
- const root = npmR.stdout.trim().split(/\r?\n/).pop();
751
- const binJs = path.join(root, BROWSER_RUNNER_BIN, 'bin.js');
762
+ const bunGlobalRoots = [
763
+ path.join(os.homedir(), '.bun', 'install', 'global', 'node_modules', BROWSER_RUNNER_BIN, 'bin.js'),
764
+ ];
765
+ for (const binJs of bunGlobalRoots) {
752
766
  if (fs.existsSync(binJs)) return { cmd: process.execPath, baseArgs: [binJs], shell: false };
753
767
  }
768
+ const cachedBin = findCachedBunRunnerBin();
769
+ if (cachedBin) return { cmd: process.execPath, baseArgs: [cachedBin], shell: false };
754
770
  const whichCmd = process.platform === 'win32' ? 'where' : 'which';
771
+ const bunR = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', shell: true });
772
+ if (bunR.status === 0 && bunR.stdout.trim()) {
773
+ return { cmd: 'bun', baseArgs: ['x', `${BROWSER_RUNNER_BIN}@latest`], shell: true };
774
+ }
755
775
  const r = spawnSync(whichCmd, [BROWSER_RUNNER_BIN], { encoding: 'utf-8', shell: true });
756
776
  if (r.status === 0 && r.stdout.trim()) {
757
777
  const candidates = r.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
758
778
  const cmd = candidates.find(c => c.toLowerCase().endsWith('.cmd')) || candidates.find(c => !c.toLowerCase().endsWith('.ps1')) || candidates[0];
759
779
  if (cmd) return { cmd, baseArgs: [], shell: process.platform === 'win32' };
760
780
  }
761
- const bunR = spawnSync(whichCmd, ['bun'], { encoding: 'utf-8', shell: true });
762
- if (bunR.status === 0 && bunR.stdout.trim()) {
763
- return { cmd: 'bun', baseArgs: ['x', `${BROWSER_RUNNER_BIN}@latest`], shell: true };
764
- }
765
781
  const npxR = spawnSync(whichCmd, ['npx'], { encoding: 'utf-8', shell: true });
766
782
  if (npxR.status === 0 && npxR.stdout.trim()) {
767
783
  return { cmd: 'npx', baseArgs: ['-y', BROWSER_RUNNER_BIN], shell: true };
@@ -827,8 +843,7 @@ function isProfileLocked(profileDir) {
827
843
  }
828
844
 
829
845
  function sessionProfileSlug(claudeSessionId) {
830
- const s = String(claudeSessionId || 'default').replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 64);
831
- return s || 'default';
846
+ return 'default';
832
847
  }
833
848
 
834
849
  function sessionProfileDir(cwd, claudeSessionId) {
@@ -864,6 +879,7 @@ function cleanDeadProfileFragments(cwd) {
864
879
  }
865
880
  continue;
866
881
  }
882
+ if (name === 'browser-profile-default') continue;
867
883
  try {
868
884
  if (fs.existsSync(dir) && !isProfileLocked(dir)) {
869
885
  fs.rmSync(dir, { recursive: true, force: true });
@@ -2116,8 +2132,25 @@ function nextTaskId(cwd) {
2116
2132
  return `t${n}`;
2117
2133
  }
2118
2134
 
2135
+ let _jsRuntimeCmd = null;
2136
+ function resolveJsRuntimeCmd() {
2137
+ if (_jsRuntimeCmd) return _jsRuntimeCmd;
2138
+ if (!/(^|[\\/])node(\.exe)?$/i.test(String(process.execPath || ''))) {
2139
+ _jsRuntimeCmd = process.execPath;
2140
+ return _jsRuntimeCmd;
2141
+ }
2142
+ try {
2143
+ const which = process.platform === 'win32' ? 'where' : 'which';
2144
+ const out = spawnSync(which, ['bun'], { encoding: 'utf-8', windowsHide: true });
2145
+ const first = (out && out.stdout || '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
2146
+ if (first) { _jsRuntimeCmd = first; return _jsRuntimeCmd; }
2147
+ } catch (_) {}
2148
+ _jsRuntimeCmd = process.execPath;
2149
+ return _jsRuntimeCmd;
2150
+ }
2151
+
2119
2152
  function langToCmd(lang, code) {
2120
- if (lang === 'nodejs' || lang === 'js' || lang === 'javascript' || lang === 'node') return { cmd: process.execPath, args: ['-e', code], stdinCode: null };
2153
+ if (lang === 'nodejs' || lang === 'js' || lang === 'javascript' || lang === 'node') return { cmd: resolveJsRuntimeCmd(), args: ['-e', code], stdinCode: null };
2121
2154
  if (lang === 'python' || lang === 'py') return { cmd: 'python', args: ['-c', code], stdinCode: null };
2122
2155
  if (lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh') return { cmd: 'bash', args: ['-c', code], stdinCode: null };
2123
2156
  if (lang === 'powershell' || lang === 'ps1') return { cmd: 'powershell', args: ['-NoProfile', '-NonInteractive', '-Command', code], stdinCode: null };
@@ -2125,10 +2158,15 @@ function langToCmd(lang, code) {
2125
2158
  return null;
2126
2159
  }
2127
2160
 
2161
+ const TASK_MAX_TIMEOUT_MS = 10 * 60 * 1000;
2162
+
2128
2163
  function spawnTask({ cwd, lang, code, timeoutMs }) {
2129
2164
  const id = nextTaskId(cwd);
2130
2165
  const built = langToCmd(lang, code);
2131
2166
  if (!built) return { ok: false, error: `unsupported lang: ${lang}` };
2167
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > TASK_MAX_TIMEOUT_MS) {
2168
+ timeoutMs = TASK_MAX_TIMEOUT_MS;
2169
+ }
2132
2170
  const outLog = taskOutPath(cwd, id, 'stdout');
2133
2171
  const errLog = taskOutPath(cwd, id, 'stderr');
2134
2172
  let outFd = null, errFd = null;
@@ -2261,6 +2299,43 @@ function killAllTasks(reason) {
2261
2299
  return killed;
2262
2300
  }
2263
2301
 
2302
+ function pidAliveLocal(pid) {
2303
+ if (!Number.isFinite(pid) || pid <= 0) return false;
2304
+ try { process.kill(pid, 0); return true; } catch (_) { return false; }
2305
+ }
2306
+
2307
+ function sweepOrphanedTaskMetaOnBoot(cwd) {
2308
+ let swept = 0;
2309
+ try {
2310
+ const dir = tasksDir(cwd);
2311
+ const now = Date.now();
2312
+ for (const name of fs.readdirSync(dir)) {
2313
+ if (!/^t\d+\.json$/.test(name)) continue;
2314
+ const metaPath = path.join(dir, name);
2315
+ let meta = null;
2316
+ try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8')); } catch (_) { continue; }
2317
+ if (!meta || meta.status !== 'running') continue;
2318
+ const stale = !pidAliveLocal(meta.pid) || (meta.deadline_ms && now > meta.deadline_ms);
2319
+ if (!stale) continue;
2320
+ if (pidAliveLocal(meta.pid)) {
2321
+ try {
2322
+ if (process.platform === 'win32') {
2323
+ spawnSync('taskkill', ['/F', '/T', '/PID', String(meta.pid)], { stdio: 'ignore', windowsHide: true, timeout: 3000 });
2324
+ } else {
2325
+ try { process.kill(-meta.pid, 'SIGKILL'); } catch (_) { try { process.kill(meta.pid, 'SIGKILL'); } catch (_) {} }
2326
+ }
2327
+ } catch (_) {}
2328
+ }
2329
+ meta.status = 'reaped-on-boot';
2330
+ meta.ended_ms = now;
2331
+ try { fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2)); } catch (_) {}
2332
+ swept += 1;
2333
+ }
2334
+ } catch (_) {}
2335
+ if (swept > 0) logEvent('plugkit', 'task.bootSweepReaped', { cwd: cwd || process.cwd(), count: swept });
2336
+ return swept;
2337
+ }
2338
+
2264
2339
  function hostTaskProc(action, params) {
2265
2340
  switch (action) {
2266
2341
  case 'spawn': return spawnTask(params);
@@ -3394,6 +3469,8 @@ async function runSpoolWatcher(instance, spoolDir) {
3394
3469
  process.exit(0);
3395
3470
  }
3396
3471
 
3472
+ try { sweepOrphanedTaskMetaOnBoot(process.cwd()); } catch (_) {}
3473
+
3397
3474
  setInterval(() => {
3398
3475
  try { reapTimedOutTasks(); } catch (_) {}
3399
3476
  }, 5000);
@@ -3744,7 +3821,7 @@ async function runSpoolWatcher(instance, spoolDir) {
3744
3821
  }
3745
3822
  }, 60_000);
3746
3823
 
3747
- const BROWSER_IDLE_LIMIT_MS = parseInt(process.env.PLUGKIT_BROWSER_IDLE_LIMIT_MS, 10) || 15 * 60 * 1000;
3824
+ const BROWSER_IDLE_LIMIT_MS = parseInt(process.env.PLUGKIT_BROWSER_IDLE_LIMIT_MS, 10) || 5 * 60 * 1000;
3748
3825
  setInterval(() => {
3749
3826
  try {
3750
3827
  const portsFile = browserPortsFile(process.cwd());
@@ -194,14 +194,13 @@ function spawnWatcher(bootReason) {
194
194
  }
195
195
 
196
196
  const isNodeExe = (p) => /(^|[\\/])node(\.exe)?$/i.test(String(p || ''));
197
- const resolveNode = () => {
197
+ const resolveRuntime = () => {
198
198
  const candidates = [];
199
- if (isNodeExe(process.env.PLUGKIT_RUNTIME)) candidates.push(process.env.PLUGKIT_RUNTIME);
200
- if (isNodeExe(process.execPath)) candidates.push(process.execPath);
201
- if (process.env.GM_NODE_PATH) candidates.push(process.env.GM_NODE_PATH);
199
+ if (process.env.PLUGKIT_RUNTIME) candidates.push(process.env.PLUGKIT_RUNTIME);
200
+ candidates.push('bun');
202
201
  try {
203
202
  const which = process.platform === 'win32' ? 'where' : 'which';
204
- const out = spawnSync(which, ['node'], { encoding: 'utf8', windowsHide: true });
203
+ const out = spawnSync(which, ['bun'], { encoding: 'utf8', windowsHide: true });
205
204
  if (out && out.stdout) {
206
205
  const first = out.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
207
206
  if (first) candidates.push(first);
@@ -210,9 +209,23 @@ function spawnWatcher(bootReason) {
210
209
  for (const c of candidates) {
211
210
  try { const r = spawnSync(c, ['--version'], { stdio: 'ignore', windowsHide: true }); if (r && r.status === 0) return c; } catch (_) {}
212
211
  }
212
+ const nodeCandidates = [];
213
+ if (isNodeExe(process.execPath)) nodeCandidates.push(process.execPath);
214
+ if (process.env.GM_NODE_PATH) nodeCandidates.push(process.env.GM_NODE_PATH);
215
+ try {
216
+ const which = process.platform === 'win32' ? 'where' : 'which';
217
+ const out = spawnSync(which, ['node'], { encoding: 'utf8', windowsHide: true });
218
+ if (out && out.stdout) {
219
+ const first = out.stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
220
+ if (first) nodeCandidates.push(first);
221
+ }
222
+ } catch (_) {}
223
+ for (const c of nodeCandidates) {
224
+ try { const r = spawnSync(c, ['--version'], { stdio: 'ignore', windowsHide: true }); if (r && r.status === 0) return c; } catch (_) {}
225
+ }
213
226
  return process.execPath;
214
227
  };
215
- let cmd = resolveNode();
228
+ let cmd = resolveRuntime();
216
229
  let args = [wrapper, 'spool'];
217
230
 
218
231
  let logFd = null;
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1849",
3
+ "version": "2.0.1851",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1849",
3
+ "version": "2.0.1851",
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",