gm-plugkit 2.0.1159 → 2.0.1161

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/bootstrap.js CHANGED
@@ -153,6 +153,23 @@ function sha256OfFile(filePath) {
153
153
  });
154
154
  }
155
155
 
156
+ function resolveNpxJsCli() {
157
+ if (process.platform !== 'win32') return null;
158
+ const candidates = [];
159
+ if (process.env.npm_config_prefix) {
160
+ candidates.push(path.join(process.env.npm_config_prefix, 'node_modules', 'npm', 'bin', 'npx-cli.js'));
161
+ }
162
+ const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
163
+ candidates.push(path.join(programFiles, 'nodejs', 'node_modules', 'npm', 'bin', 'npx-cli.js'));
164
+ candidates.push(path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js'));
165
+ const appdata = process.env.APPDATA;
166
+ if (appdata) candidates.push(path.join(appdata, 'npm', 'node_modules', 'npm', 'bin', 'npx-cli.js'));
167
+ for (const c of candidates) {
168
+ try { if (fs.existsSync(c)) return c; } catch (_) {}
169
+ }
170
+ return null;
171
+ }
172
+
156
173
  async function extractNpmPackageWasm(destPath, version) {
157
174
  const tempDir = path.join(path.dirname(destPath), '.npm-extract-' + Date.now());
158
175
  try {
@@ -161,16 +178,28 @@ async function extractNpmPackageWasm(destPath, version) {
161
178
  log(`extracting npm package ${NPM_PACKAGE}@${version} to ${tempDir}`);
162
179
  obsEvent('bootstrap', 'npm.extract.start', { package: NPM_PACKAGE, version });
163
180
 
164
- const result = spawnSync(
165
- process.platform === 'win32' ? 'npx.cmd' : 'npx',
166
- [NPM_PACKAGE + '@' + version, '--prefix', tempDir],
167
- {
168
- stdio: ['ignore', 'pipe', 'pipe'],
169
- timeout: ATTEMPT_TIMEOUT_MS,
170
- encoding: 'utf8',
171
- windowsHide: true,
181
+ let cmd, args;
182
+ if (process.platform === 'win32') {
183
+ const npxCli = resolveNpxJsCli();
184
+ if (npxCli) {
185
+ cmd = process.execPath;
186
+ args = [npxCli, NPM_PACKAGE + '@' + version, '--prefix', tempDir];
187
+ } else {
188
+ cmd = 'npx.cmd';
189
+ args = [NPM_PACKAGE + '@' + version, '--prefix', tempDir];
172
190
  }
173
- );
191
+ } else {
192
+ cmd = 'npx';
193
+ args = [NPM_PACKAGE + '@' + version, '--prefix', tempDir];
194
+ }
195
+
196
+ const result = spawnSync(cmd, args, {
197
+ stdio: ['ignore', 'pipe', 'pipe'],
198
+ timeout: ATTEMPT_TIMEOUT_MS,
199
+ encoding: 'utf8',
200
+ windowsHide: true,
201
+ shell: process.platform === 'win32' && cmd === 'npx.cmd',
202
+ });
174
203
 
175
204
  if (result.error) throw result.error;
176
205
  if (result.status !== 0) {
@@ -192,25 +221,37 @@ async function extractNpmPackageWasm(destPath, version) {
192
221
 
193
222
  function httpGetBuffer(url, timeoutMs) {
194
223
  const https = require('https');
224
+ const idleTimeoutMs = timeoutMs || 30000;
225
+ const totalDeadlineMs = (timeoutMs || 30000) * 2;
195
226
  return new Promise((resolve, reject) => {
196
- const req = https.get(url, { timeout: timeoutMs || 30000, headers: { 'user-agent': 'gm-plugkit-bootstrap' } }, (res) => {
227
+ let bytesReceived = 0;
228
+ let settled = false;
229
+ const settleReject = (err) => { if (!settled) { settled = true; reject(err); } };
230
+ const settleResolve = (v) => { if (!settled) { settled = true; resolve(v); } };
231
+ const absTimer = setTimeout(() => {
232
+ try { req.destroy(new Error(`abs-deadline ${totalDeadlineMs}ms ${url} after ${bytesReceived} bytes`)); } catch (_) {}
233
+ settleReject(new Error(`abs-deadline ${totalDeadlineMs}ms ${url} after ${bytesReceived} bytes`));
234
+ }, totalDeadlineMs);
235
+ const req = https.get(url, { timeout: idleTimeoutMs, headers: { 'user-agent': 'gm-plugkit-bootstrap' } }, (res) => {
197
236
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
198
237
  res.resume();
199
- httpGetBuffer(res.headers.location, timeoutMs).then(resolve, reject);
238
+ clearTimeout(absTimer);
239
+ httpGetBuffer(res.headers.location, timeoutMs).then(settleResolve, settleReject);
200
240
  return;
201
241
  }
202
242
  if (res.statusCode !== 200) {
203
243
  res.resume();
204
- reject(new Error(`HTTP ${res.statusCode} ${url}`));
244
+ clearTimeout(absTimer);
245
+ settleReject(new Error(`HTTP ${res.statusCode} ${url}`));
205
246
  return;
206
247
  }
207
248
  const chunks = [];
208
- res.on('data', c => chunks.push(c));
209
- res.on('end', () => resolve(Buffer.concat(chunks)));
210
- res.on('error', reject);
249
+ res.on('data', c => { chunks.push(c); bytesReceived += c.length; });
250
+ res.on('end', () => { clearTimeout(absTimer); settleResolve(Buffer.concat(chunks)); });
251
+ res.on('error', (e) => { clearTimeout(absTimer); settleReject(e); });
211
252
  });
212
- req.on('timeout', () => req.destroy(new Error(`timeout ${url}`)));
213
- req.on('error', reject);
253
+ req.on('timeout', () => { try { req.destroy(new Error(`idle-timeout ${idleTimeoutMs}ms ${url}`)); } catch (_) {} settleReject(new Error(`idle-timeout ${idleTimeoutMs}ms ${url}`)); });
254
+ req.on('error', (e) => { clearTimeout(absTimer); settleReject(e); });
214
255
  });
215
256
  }
216
257
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-plugkit",
3
- "version": "2.0.1159",
3
+ "version": "2.0.1161",
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": {
@@ -932,6 +932,22 @@ function resolveVersion(instance) {
932
932
  return 'unknown';
933
933
  }
934
934
 
935
+ function readFileVersionOnly() {
936
+ try { return fs.readFileSync(path.join(os.homedir(), '.claude', 'gm-tools', 'plugkit.version'), 'utf8').trim(); } catch (_) { return null; }
937
+ }
938
+
939
+ function readInstanceVersion(instance) {
940
+ try {
941
+ const fn = instance && instance.exports && instance.exports.plugkit_version;
942
+ if (typeof fn !== 'function') return null;
943
+ const result = fn();
944
+ const ptr = Number(result & 0xffffffffn);
945
+ const len = Number(result >> 32n);
946
+ const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
947
+ return new TextDecoder().decode(bytes).trim();
948
+ } catch (_) { return null; }
949
+ }
950
+
935
951
  async function runSpoolWatcher(instance, spoolDir) {
936
952
  const inDir = path.join(spoolDir, 'in');
937
953
  const outDir = path.join(spoolDir, 'out');
@@ -947,8 +963,11 @@ async function runSpoolWatcher(instance, spoolDir) {
947
963
  const lockTs = parseInt(tsStr, 10);
948
964
  const age = Date.now() - lockTs;
949
965
  if (age < 15000) {
950
- console.error(`[plugkit-wasm] another watcher active (pid=${pidStr}, age=${age}ms); refusing to start`);
951
- process.exit(1);
966
+ const msg = JSON.stringify({ ok: false, reason: 'another-watcher-active', pid: pidStr, age_ms: age });
967
+ console.error(`[plugkit-wasm] ${msg}; refusing to start`);
968
+ try { fs.writeFileSync(path.join(spoolDir, '.lock-rejection.json'), msg); } catch (_) {}
969
+ try { logEvent('plugkit', 'watcher.lock-rejected', { holder_pid: pidStr, lock_age_ms: age }); } catch (_) {}
970
+ process.exit(75);
952
971
  }
953
972
  console.error(`[plugkit-wasm] stale lock (age=${age}ms); taking over`);
954
973
  }
@@ -1035,6 +1054,35 @@ async function runSpoolWatcher(instance, spoolDir) {
1035
1054
  try { reapTimedOutTasks(); } catch (_) {}
1036
1055
  }, 5000);
1037
1056
 
1057
+ const _instanceVersionAtBoot = readInstanceVersion(instance);
1058
+ setInterval(() => {
1059
+ try {
1060
+ const fileV = readFileVersionOnly();
1061
+ const instV = _instanceVersionAtBoot;
1062
+ if (!fileV || !instV || fileV === instV) return;
1063
+ logEvent('plugkit', 'version.drift', {
1064
+ instance_version: instV,
1065
+ file_version: fileV,
1066
+ action: 'exit-for-respawn',
1067
+ });
1068
+ console.error(`[plugkit-wasm] version drift detected: instance=${instV} file=${fileV} → exiting so supervisor reloads fresh wasm`);
1069
+ try {
1070
+ fs.writeFileSync(path.join(spoolDir, '.shutdown-reason.json'), JSON.stringify({
1071
+ reason: 'version-change',
1072
+ ts: Date.now(),
1073
+ pid: process.pid,
1074
+ instance_version: instV,
1075
+ file_version: fileV,
1076
+ }));
1077
+ } catch (_) {}
1078
+ try { releaseLock(); } catch (_) {}
1079
+ try { fs.unlinkSync(STATUS_PATH_FOR_TEARDOWN); } catch (_) {}
1080
+ process.exit(0);
1081
+ } catch (e) {
1082
+ console.error(`[version-drift-check] error: ${e.message}`);
1083
+ }
1084
+ }, 60_000);
1085
+
1038
1086
  setInterval(() => {
1039
1087
  try {
1040
1088
  const idleMs = Date.now() - lastActivityMs;
package/plugkit.sha256 CHANGED
@@ -1 +1 @@
1
- {"plugkit.wasm":"ffb68924055bffd275db6a174fb5d286c1fcaf5a135f968345bf221b9976544f"}
1
+ {"plugkit.wasm":"623152434c8b0f75b20cb89f48551e3a1677f132275e0fc43e261db7a69afeed"}
package/plugkit.version CHANGED
@@ -1 +1 @@
1
- 0.1.418
1
+ 0.1.419