gm-skill 2.0.1596 → 2.0.1597

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.
@@ -5,6 +5,7 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
  const cp = require('child_process');
7
7
  const os = require('os');
8
+ const { pathToFileURL } = require('url');
8
9
 
9
10
  const ROOT = process.cwd();
10
11
  const WITNESS_DIR = path.join(ROOT, '.gm', 'witness');
@@ -30,7 +31,7 @@ async function renderPreview() {
30
31
  const renderScript = `
31
32
  import { writeFileSync } from 'fs';
32
33
  import { resolve } from 'path';
33
- const mod = await import('file:///C:/dev/gm/site/theme.mjs');
34
+ const mod = await import(${JSON.stringify(pathToFileURL(path.join(ROOT, 'site', 'theme.mjs')).href)});
34
35
  const ctx = {
35
36
  readGlobal: (k) => {
36
37
  if (k === 'site') return { title: 'gm', tagline: "more coushin' for the pushin'", description: 'local browser OS surface', glyph: 'g', accent_from: '#7ee787', accent_to: '#56d364' };
@@ -53,11 +54,20 @@ async function renderPreview() {
53
54
  const server = cp.spawn('python', ['-m', 'http.server', '4210', '--directory', preview], { cwd: ROOT, detached: true, stdio: 'ignore', windowsHide: true });
54
55
  server.unref();
55
56
  await sleep(1500);
56
- return { preview, port: 4210 };
57
+ return { preview, port: 4210, serverPid: server.pid };
58
+ }
59
+
60
+ function killServer(pid) {
61
+ if (!pid) return;
62
+ try {
63
+ if (process.platform === 'win32') cp.execFileSync('taskkill', ['/F', '/T', '/PID', String(pid)], { stdio: 'ignore', windowsHide: true });
64
+ else process.kill(pid, 'SIGTERM');
65
+ } catch (_) {}
57
66
  }
58
67
 
59
68
  async function main() {
60
- const { preview, port } = await renderPreview();
69
+ const { preview, port, serverPid } = await renderPreview();
70
+ try {
61
71
  const witness = path.join(os.tmpdir(), `gm-shell-witness-${Date.now()}.js`);
62
72
  const witnessOut = path.join(WITNESS_DIR, `gm-shell-${Date.now()}.json`);
63
73
  write(witness, `
@@ -112,6 +122,9 @@ return JSON.stringify(result);
112
122
  fs.writeFileSync(witnessOut, JSON.stringify({ preview, port, output: out.trim(), parsed }, null, 2));
113
123
  try { fs.unlinkSync(witness); } catch (_) {}
114
124
  rmrf(preview);
125
+ } finally {
126
+ killServer(serverPid);
127
+ }
115
128
  }
116
129
 
117
130
  main().catch((e) => {
@@ -189,7 +189,7 @@ async function validateEmbed() {
189
189
  }
190
190
  const lats = v.calls.map(c => c.latency_ms).sort((a, b) => a - b);
191
191
  v.p50_ms = lats[Math.floor(lats.length * 0.5)] || 0;
192
- v.p95_ms = lats[Math.max(0, lats.length - 1)] || 0;
192
+ v.p95_ms = lats[Math.max(0, Math.ceil(lats.length * 0.95) - 1)] || 0;
193
193
 
194
194
  const r = await dispatch('recall', { query: 'gm-validate witness', namespace: 'validate', limit: 3 }, 60000);
195
195
  const rd = (r.response && (r.response.data || r.response)) || {};
@@ -238,6 +238,7 @@ async function validateBrowserEmbed() {
238
238
  serveProc.unref();
239
239
  } catch (e) { v.errors.push('serve spawn: ' + e.message); return v; }
240
240
 
241
+ try {
241
242
  const t0 = Date.now();
242
243
  let ready = false;
243
244
  while (Date.now() - t0 < 20000) {
@@ -278,7 +279,7 @@ return out;
278
279
  try {
279
280
  const tmpScript = path.join(os.tmpdir(), 'gm-validate-' + Date.now() + '.js');
280
281
  fs.writeFileSync(tmpScript, script);
281
- const out = cp.execSync('playwriter -s ' + sessionId + ' --timeout 60000 -e "' + script.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ') + '"', { encoding: 'utf8', windowsHide: true, maxBuffer: 4 * 1024 * 1024 });
282
+ const out = cp.execFileSync(pw, ['-s', sessionId, '--timeout', '60000', '-f', tmpScript], { encoding: 'utf8', windowsHide: true, maxBuffer: 4 * 1024 * 1024 });
282
283
  try { fs.unlinkSync(tmpScript); } catch (_) {}
283
284
  const m = out.match(/\{[\s\S]*\}\s*$/);
284
285
  if (m) { try { res = JSON.parse(m[0]); } catch (_) {} }
@@ -290,7 +291,7 @@ return out;
290
291
  v.calls = res.mems;
291
292
  const lats = res.mems.map(c => c.latency_ms).sort((a, b) => a - b);
292
293
  v.p50_ms = Math.round(lats[Math.floor(lats.length * 0.5)] || 0);
293
- v.p95_ms = Math.round(lats[Math.max(0, lats.length - 1)] || 0);
294
+ v.p95_ms = Math.round(lats[Math.max(0, Math.ceil(lats.length * 0.95) - 1)] || 0);
294
295
  const rows = (res.recall && (res.recall.rows || res.recall.hits)) || [];
295
296
  v.recall_top_text = (rows[0] && (rows[0].text || rows[0].content)) || '';
296
297
  const allOk = v.calls.every(c => c.ok);
@@ -298,6 +299,14 @@ return out;
298
299
  if (!allOk) v.errors.push('not all browser memorize calls ok');
299
300
  }
300
301
  return v;
302
+ } finally {
303
+ if (serveProc && serveProc.pid) {
304
+ try {
305
+ if (process.platform === 'win32') cp.execFileSync('taskkill', ['/F', '/T', '/PID', String(serveProc.pid)], { stdio: 'ignore', windowsHide: true });
306
+ else process.kill(serveProc.pid, 'SIGTERM');
307
+ } catch (_) {}
308
+ }
309
+ }
301
310
  }
302
311
 
303
312
  (async () => {
package/gm-plugkit/cli.js CHANGED
@@ -101,7 +101,7 @@ function killStaleWatchers() {
101
101
  for (const s of stale) {
102
102
  try {
103
103
  if (process.platform === 'win32') {
104
- cp.execFileSync('taskkill', ['/F', '/PID', String(s.pid)], { stdio: 'ignore', windowsHide: true });
104
+ cp.execFileSync('taskkill', ['/F', '/T', '/PID', String(s.pid)], { stdio: 'ignore', windowsHide: true });
105
105
  } else {
106
106
  process.kill(s.pid, 'SIGTERM');
107
107
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1596",
3
+ "version": "2.0.1597",
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": {
@@ -836,7 +836,7 @@ function isPortAliveSync(port) {
836
836
  }
837
837
 
838
838
  function sleepSync(ms) {
839
- spawnSync(process.execPath, ['-e', `setTimeout(()=>{}, ${ms})`], { timeout: ms + 2000 });
839
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.max(0, ms | 0));
840
840
  }
841
841
 
842
842
  function playwriterHomeFor(cwd, claudeSessionId) {
@@ -1049,7 +1049,7 @@ function purgeProfileLockFiles(profileDir) {
1049
1049
 
1050
1050
  function sleepSyncMs(ms) {
1051
1051
  if (ms <= 0) return;
1052
- spawnSync(process.execPath, ['-e', `setTimeout(()=>process.exit(0),${ms})`], { timeout: ms + 500, windowsHide: true });
1052
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms | 0);
1053
1053
  }
1054
1054
 
1055
1055
  function gracefulCloseBrowser(entry, reason) {
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1596",
3
+ "version": "2.0.1597",
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/lang/ssh.js CHANGED
@@ -75,19 +75,23 @@ function runSsh(target, cmd, onData) {
75
75
  return new Promise((resolve, reject) => {
76
76
  const { Client } = resolveSsh2();
77
77
  const ssh = new Client();
78
- let out = '';
78
+ let stdout = '';
79
+ let stderr = '';
79
80
  let done = false;
80
81
 
81
- const finish = (text) => {
82
- if (!done) { done = true; ssh.end(); resolve(text != null ? text : out.trimEnd()); }
82
+ const finish = (extra) => {
83
+ if (!done) {
84
+ done = true;
85
+ ssh.end();
86
+ resolve({ stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), timedOut: false, ...extra });
87
+ }
83
88
  };
84
89
 
85
90
  const timeout = setTimeout(() => {
86
91
  if (!done) {
87
92
  done = true;
88
93
  try { ssh.end(); } catch (_) {}
89
- const partial = out.trimEnd();
90
- resolve(partial ? `${partial}\n[ssh timed out after 55s; output above is partial]` : '[ssh timed out after 55s; no output]');
94
+ resolve({ stdout: stdout.trimEnd(), stderr: (stderr + '\n[ssh timed out after 55s; output is partial]').trimEnd(), timedOut: true });
91
95
  }
92
96
  }, 55000);
93
97
 
@@ -96,12 +100,12 @@ function runSsh(target, cmd, onData) {
96
100
  if (err) { clearTimeout(timeout); ssh.end(); reject(err); return; }
97
101
  stream.on('data', d => {
98
102
  const s = d.toString();
99
- out += s;
103
+ stdout += s;
100
104
  if (onData) onData(s, 'stdout');
101
105
  });
102
106
  stream.stderr.on('data', d => {
103
107
  const s = d.toString();
104
- out += s;
108
+ stderr += s;
105
109
  if (onData) onData(s, 'stderr');
106
110
  });
107
111
  stream.on('close', () => { clearTimeout(timeout); finish(); });
@@ -133,8 +137,8 @@ async function runBackground(target, cmd) {
133
137
  rpcCall(port, 'appendOutput', { taskId, type, data }).catch(() => {});
134
138
  };
135
139
 
136
- runSsh(target, cmd, onData).then(out => {
137
- rpcCall(port, 'completeTask', { taskId, result: { success: true, exitCode: 0, stdout: out, stderr: '', error: null } }).catch(() => {});
140
+ runSsh(target, cmd, onData).then(r => {
141
+ rpcCall(port, 'completeTask', { taskId, result: { success: !r.timedOut, exitCode: r.timedOut ? 124 : 0, stdout: r.stdout, stderr: r.stderr, error: r.timedOut ? 'ssh timed out after 55s' : null } }).catch(() => {});
138
142
  }).catch(err => {
139
143
  rpcCall(port, 'completeTask', { taskId, result: { success: false, exitCode: 1, stdout: '', stderr: err.message, error: err.message } }).catch(() => {});
140
144
  });
@@ -163,7 +167,9 @@ module.exports = {
163
167
  }
164
168
  }
165
169
 
166
- return await runSsh(target, cmd, null);
170
+ const r = await runSsh(target, cmd, null);
171
+ const combined = [r.stdout, r.stderr].filter(Boolean).join('\n');
172
+ return combined || (r.timedOut ? '[ssh timed out after 55s; no output]' : '');
167
173
  }
168
174
  },
169
175
  context: `=== exec:ssh ===
@@ -423,7 +423,7 @@ function killExistingPlugkit() {
423
423
  for (const pid of pids) {
424
424
  try {
425
425
  if (process.platform === 'win32') {
426
- execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
426
+ execSync(`taskkill /F /T /PID ${pid}`, { stdio: 'ignore' });
427
427
  } else {
428
428
  execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
429
429
  }
package/lib/spool.js CHANGED
@@ -7,8 +7,9 @@ function getSpoolBaseDir() {
7
7
  return path.join(cwd, '.gm', 'exec-spool');
8
8
  }
9
9
 
10
+ let taskIdSeq = 0;
10
11
  function generateTaskId() {
11
- return `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
12
+ return `${process.pid}-${Date.now()}-${(taskIdSeq++).toString(36)}`;
12
13
  }
13
14
 
14
15
  function validateLang(lang) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1596",
3
+ "version": "2.0.1597",
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",