gm-qwen 2.0.882 → 2.0.884

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/bin/bootstrap.js CHANGED
@@ -9,11 +9,16 @@ const crypto = require('crypto');
9
9
  const { URL } = require('url');
10
10
 
11
11
  const RELEASE_REPO = 'AnEntrypoint/plugkit-bin';
12
- const ATTEMPT_TIMEOUT_MS = 60 * 1000;
13
- const STALL_TIMEOUT_MS = 20 * 1000;
12
+ const ATTEMPT_TIMEOUT_MS = 5 * 60 * 1000;
13
+ const STALL_TIMEOUT_MS = 60 * 1000;
14
14
  const MAX_ATTEMPTS = 5;
15
15
  const BACKOFF_MS = [2000, 5000, 15000, 30000];
16
- const LOCK_STALE_MS = 5 * 60 * 1000;
16
+ // Worst case: a slow link downloading 140MB at 1MB/s = ~140s. Allow 30 minutes
17
+ // before another bootstrap process treats this lock as abandoned. Below this,
18
+ // concurrent bootstrap calls would wipe an in-progress download mid-stream
19
+ // (see the v0.1.294 incident where a race between two wrappers blew away the
20
+ // .partial during a 10-minute fetch).
21
+ const LOCK_STALE_MS = 30 * 60 * 1000;
17
22
 
18
23
  function log(msg) {
19
24
  try { process.stderr.write(`[plugkit-bootstrap] ${msg}\n`); } catch (_) {}
@@ -170,6 +175,10 @@ function fetchToFile(url, destPath, expectedTotal) {
170
175
  return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
171
176
  }
172
177
  const append = res.statusCode === 206 && existing > 0;
178
+ // Ensure parent dir exists — a concurrent prune may have removed it
179
+ // between lock-acquire and now. Recreating is cheap and avoids a
180
+ // confusing ENOENT later.
181
+ try { ensureDir(path.dirname(destPath)); } catch (_) {}
173
182
  const out = fs.createWriteStream(destPath, { flags: append ? 'a' : 'w' });
174
183
  let bytes = append ? existing : 0;
175
184
  let lastStderr = Date.now();
@@ -402,7 +411,65 @@ function resolveCachedBinary(opts) {
402
411
  return null;
403
412
  }
404
413
 
405
- module.exports = { bootstrap, resolveCachedBinary, resolveCachedRtk, platformKey, binaryName, rtkBinaryName, cacheRoot, obsEvent };
414
+ // ---------------------------------------------------------------------------
415
+ // Daemon kill on version change.
416
+ //
417
+ // The plugin tarball pins `plugkit.version`. When that pin advances and we
418
+ // install a newer cached binary, any long-running daemon (the runner) holds
419
+ // stale code and serves stale RPCs until killed. We track which version the
420
+ // daemon was last started under via `.daemon-version`; on every wrapper
421
+ // invocation, if the wrapper-pinned version differs, we kill the daemon so
422
+ // the next exec spawns it fresh under the new binary.
423
+ // ---------------------------------------------------------------------------
424
+
425
+ function daemonVersionSentinel() {
426
+ const root = (() => {
427
+ try { const r = cacheRoot(); ensureDir(r); return r; }
428
+ catch (_) { const r = fallbackCacheRoot(); ensureDir(r); return r; }
429
+ })();
430
+ return path.join(root, '.daemon-version');
431
+ }
432
+
433
+ function readDaemonVersion() {
434
+ try { return fs.readFileSync(daemonVersionSentinel(), 'utf8').trim(); }
435
+ catch (_) { return null; }
436
+ }
437
+
438
+ function writeDaemonVersion(v) {
439
+ try { fs.writeFileSync(daemonVersionSentinel(), String(v)); } catch (_) {}
440
+ }
441
+
442
+ function killRunningDaemons(reason) {
443
+ const tmp = os.tmpdir();
444
+ let killed = 0;
445
+ for (const pidFile of ['glootie-runner.pid', 'plugkit-runner.pid']) {
446
+ const pidPath = path.join(tmp, pidFile);
447
+ if (!fs.existsSync(pidPath)) continue;
448
+ try {
449
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
450
+ if (Number.isFinite(pid) && pid !== process.pid && pidAlive(pid)) {
451
+ try { process.kill(pid, 'SIGTERM'); killed++; }
452
+ catch (_) { try { process.kill(pid); killed++; } catch (_) {} }
453
+ obsEvent('bootstrap', 'daemon.killed', { pid, pidFile, reason });
454
+ }
455
+ try { fs.unlinkSync(pidPath); } catch (_) {}
456
+ } catch (_) {}
457
+ }
458
+ return killed;
459
+ }
460
+
461
+ // Compare wrapper-pinned version against last-recorded daemon version. If
462
+ // they differ, kill the daemon so it respawns under the new binary.
463
+ function killStaleDaemonIfVersionChanged(wrapperDir) {
464
+ let currentVersion;
465
+ try { currentVersion = readVersionFile(wrapperDir); } catch (_) { return; }
466
+ const recorded = readDaemonVersion();
467
+ if (recorded === currentVersion) return;
468
+ if (recorded) killRunningDaemons(`version_change:${recorded}->${currentVersion}`);
469
+ writeDaemonVersion(currentVersion);
470
+ }
471
+
472
+ module.exports = { bootstrap, resolveCachedBinary, resolveCachedRtk, platformKey, binaryName, rtkBinaryName, cacheRoot, obsEvent, killRunningDaemons, killStaleDaemonIfVersionChanged };
406
473
 
407
474
  if (require.main === module) {
408
475
  bootstrap({ silent: false })
package/bin/plugkit.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const { spawn, spawnSync } = require('child_process');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const { bootstrap, resolveCachedBinary, resolveCachedRtk, obsEvent } = require('./bootstrap');
6
+ const { bootstrap, resolveCachedBinary, resolveCachedRtk, obsEvent, killStaleDaemonIfVersionChanged } = require('./bootstrap');
7
7
 
8
8
  const dir = __dirname;
9
9
 
@@ -26,21 +26,31 @@ async function main() {
26
26
  const isHook = args[0] === 'hook';
27
27
  const startedAt = Date.now();
28
28
  obsEvent('plugkit_wrapper', 'invoke', { argv: args.slice(0, 4), is_hook: isHook });
29
+ // If the plugin tarball updated `plugkit.version` since the runner daemon
30
+ // was last started, kill the daemon so the next `runner start` picks up
31
+ // the freshly-installed binary instead of serving stale RPCs.
32
+ try { killStaleDaemonIfVersionChanged(dir); } catch (_) {}
29
33
  let bin;
30
34
  try {
31
- if (isHook) {
35
+ const hookSubcmd = isHook ? (args[1] || '') : '';
36
+ // session-start ALWAYS bootstraps: this is the once-per-session moment
37
+ // where we guarantee the cached binary matches the wrapper-pinned version.
38
+ // If the bootstrap fails (offline) we fall through to whatever the cache
39
+ // currently has — the hook itself isn't blocking, just refreshing.
40
+ if (isHook && hookSubcmd === 'session-start') {
41
+ obsEvent('plugkit_wrapper', 'hook_bootstrap_session_start', { argv: args.slice(0, 4) });
42
+ try {
43
+ bin = await bootstrap({ wrapperDir: dir, silent: true });
44
+ } catch (e) {
45
+ process.stderr.write(`[plugkit] session-start bootstrap failed: ${e.message}\n`);
46
+ bin = resolveCachedBinary({ wrapperDir: dir }) || legacyFallback();
47
+ }
48
+ // session-start hook itself runs in the freshly-bootstrapped binary
49
+ // below — fall through to the spawn path so the actual handler runs.
50
+ if (!bin) process.exit(0);
51
+ } else if (isHook) {
32
52
  bin = resolveCachedBinary({ wrapperDir: dir }) || legacyFallback();
33
53
  if (!bin) {
34
- const hookSubcmd = args[1] || '';
35
- if (hookSubcmd === 'session-start') {
36
- obsEvent('plugkit_wrapper', 'hook_bootstrap_session_start', { argv: args.slice(0, 4) });
37
- try {
38
- await bootstrap({ wrapperDir: dir });
39
- } catch (e) {
40
- process.stderr.write(`[plugkit] session-start bootstrap failed: ${e.message}\n`);
41
- }
42
- process.exit(0);
43
- }
44
54
  process.stderr.write(`[plugkit] hook ${hookSubcmd} skipped: binary not yet installed. Bootstrap will run on session-start.\n`);
45
55
  obsEvent('plugkit_wrapper', 'hook_skip_uncached', { argv: args.slice(0, 4), dur_ms: Date.now() - startedAt });
46
56
  process.exit(0);
@@ -76,16 +86,18 @@ async function main() {
76
86
  }
77
87
  }
78
88
 
89
+ // legacyFallback only returns a binary that lives next to the wrapper. We
90
+ // never reach across to ~/.claude/gm-tools/plugkit.exe or other ambient
91
+ // install dirs — those have proven to mask bootstrap failures by serving a
92
+ // stale version whose hooks silently mismatch the active wrapper code (see
93
+ // the v0.1.292-vs-v0.1.294 incident).
79
94
  function legacyFallback() {
80
95
  const os = require('os');
81
96
  const p = os.platform();
82
97
  const a = os.arch();
83
98
  let candidates = [];
84
99
  if (p === 'win32') {
85
- candidates = [
86
- path.join(dir, a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe'),
87
- path.join(dir, 'plugkit.exe'),
88
- ];
100
+ candidates = [path.join(dir, a === 'arm64' ? 'plugkit-win32-arm64.exe' : 'plugkit-win32-x64.exe')];
89
101
  } else if (p === 'darwin') {
90
102
  candidates = [path.join(dir, a === 'arm64' ? 'plugkit-darwin-arm64' : 'plugkit-darwin-x64')];
91
103
  } else {
@@ -1,6 +1,6 @@
1
- d4a4df030be84d1863a5230b2657e69765c16f69faf65d62bc56dfda68686faf plugkit-win32-x64.exe
2
- 2c1c978d7fd89898bd8f35ebbcf6d36cee533751113d9d45ad72fce95e9921ed plugkit-win32-arm64.exe
3
- 6a11f3f4fc819dde5993eb4621f056c18ad07ee7b08cf77f6f59a07fd5c1a7c7 plugkit-darwin-x64
4
- fb33de6f0823825989eae54e683904f618daa14a0c3c2ee91acd9b2bd297d699 plugkit-darwin-arm64
5
- eed7f62527acd4a8c9f02daf20150c4e5c62a9922abf377cec3b78bc5e845641 plugkit-linux-x64
6
- fbe7d87c3c6569b4b461d89998cafce836c7107dcb2f8cdd38740920c8e5bd13 plugkit-linux-arm64
1
+ f0a217d24261d6fee31b3f09347da19e2eda76f9b558adf607240cefcc131d01 plugkit-win32-x64.exe
2
+ 24bfc958f0b0682c56f5531a8e8c42f047f228bf9592405ff6802a0371620379 plugkit-win32-arm64.exe
3
+ 004d0ea64c0d20e0fc15005be2934995a9f21b5faf2d6f3e46815f1f56a3ea2f plugkit-darwin-x64
4
+ 8281cc89e37d683cb798de88ede8214946b4d87d08209a06f5af3d00aff3ab37 plugkit-darwin-arm64
5
+ 60c0f7d606bdb7fc38c8d62919204eacc736782223d5775a3329c3a788e50a67 plugkit-linux-x64
6
+ 07bfac610e89a4923aa43479ce14e2d060bf95762d2144a32787ee3698aa902d plugkit-linux-arm64
package/bin/rtk.sha256 CHANGED
@@ -1,5 +1,5 @@
1
- fce18ddef3a71ecfd3870f3f96d92c385dc7734ba7ea20f76cb2c4ba8537f3c8 rtk-win32-x64.exe
2
- cd367f741b8bb9806420eda69dddd7b056769a32f1f2fb738e4b0826cf68746c rtk-win32-arm64.exe
1
+ 45001a4384331c752b20937d2918867717655e1f836a7da626cfd813abd6b828 rtk-win32-x64.exe
2
+ 043a0438b9b28e50db56187d0e2e8e1833be674f45db24398cf3892f48f26004 rtk-win32-arm64.exe
3
3
  e89fdf402c28796b510587a8b0fe046438b5b24d49533d1a2339a48aecae35e9 rtk-darwin-x64
4
4
  2b203fd380f5782b5489eb016e34e3dbf848272a7fadf36b39bce6cfd9a3005c rtk-darwin-arm64
5
5
  0da9950b859c7a2693aaf6c169f05f9b8965508ba1f23f1547e63d5fa988749e rtk-linux-x64
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.882",
3
+ "version": "2.0.884",
4
4
  "description": "State machine agent with hooks, 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-qwen",
3
- "version": "2.0.882",
3
+ "version": "2.0.884",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",