gm-codex 2.0.948 → 2.0.949

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,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-codex",
3
- "version": "2.0.948",
3
+ "version": "2.0.949",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",
package/bin/bootstrap.js CHANGED
@@ -88,6 +88,43 @@ function readVersionFile(wrapperDir) {
88
88
  return fs.readFileSync(p, 'utf8').trim();
89
89
  }
90
90
 
91
+ function readRtkVersion(wrapperDir) {
92
+ const p = path.join(wrapperDir, 'rtk.version');
93
+ if (!fs.existsSync(p)) return null;
94
+ const v = fs.readFileSync(p, 'utf8').trim();
95
+ return v || null;
96
+ }
97
+
98
+ function sha256OfFileSync(filePath) {
99
+ const h = crypto.createHash('sha256');
100
+ const fd = fs.openSync(filePath, 'r');
101
+ try {
102
+ const buf = Buffer.alloc(1024 * 1024);
103
+ for (;;) {
104
+ const n = fs.readSync(fd, buf, 0, buf.length, null);
105
+ if (n <= 0) break;
106
+ h.update(buf.subarray(0, n));
107
+ }
108
+ } finally { try { fs.closeSync(fd); } catch (_) {} }
109
+ return h.digest('hex');
110
+ }
111
+
112
+ function healIfShaMatches(binPath, expectedSha, sentinelPath, partialPath, kind) {
113
+ if (!fs.existsSync(binPath)) return false;
114
+ if (partialPath) { try { if (fs.existsSync(partialPath)) fs.unlinkSync(partialPath); } catch (_) {} }
115
+ if (!expectedSha) return false;
116
+ let got;
117
+ try { got = sha256OfFileSync(binPath); }
118
+ catch (_) { return false; }
119
+ if (got !== expectedSha) {
120
+ try { fs.unlinkSync(binPath); } catch (_) {}
121
+ return false;
122
+ }
123
+ try { fs.writeFileSync(sentinelPath, new Date().toISOString()); } catch (_) { return false; }
124
+ obsEvent('bootstrap', 'cache.heal', { path: binPath, kind });
125
+ return true;
126
+ }
127
+
91
128
  function readShaManifest(wrapperDir, manifestName) {
92
129
  const p = path.join(wrapperDir, manifestName || 'plugkit.sha256');
93
130
  if (!fs.existsSync(p)) return null;
@@ -248,12 +285,15 @@ function isLockStale(lockPath) {
248
285
  return false;
249
286
  }
250
287
 
251
- function pruneOldVersions(root, keepVersion) {
288
+ function pruneOldVersions(root, keepVersion, keepRtkVersion) {
252
289
  try {
253
290
  const entries = fs.readdirSync(root);
254
291
  for (const e of entries) {
255
- if (!e.startsWith('v')) continue;
256
- if (e === `v${keepVersion}`) continue;
292
+ const isPlugkit = e.startsWith('v') && !e.startsWith('rtk-');
293
+ const isRtk = e.startsWith('rtk-v');
294
+ if (!isPlugkit && !isRtk) continue;
295
+ if (isPlugkit && e === `v${keepVersion}`) continue;
296
+ if (isRtk && keepRtkVersion && e === `rtk-v${keepRtkVersion}`) continue;
257
297
  const dir = path.join(root, e);
258
298
  const lock = path.join(dir, '.lock');
259
299
  if (fs.existsSync(lock) && !isLockStale(lock)) continue;
@@ -283,10 +323,19 @@ async function bootstrap(opts) {
283
323
 
284
324
  const finalPath = path.join(verDir, binName);
285
325
  const okSentinel = path.join(verDir, '.ok');
326
+ const partialPath = `${finalPath}.partial`;
286
327
 
287
328
  if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
288
329
  if (!opts.silent) log(`cache hit: ${finalPath}`);
289
- pruneOldVersions(root, version);
330
+ pruneOldVersions(root, version, readRtkVersion(wrapperDir));
331
+ return finalPath;
332
+ }
333
+
334
+ if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
335
+ if (!opts.silent) log(`cache heal (sha match): ${finalPath}`);
336
+ pruneOldVersions(root, version, readRtkVersion(wrapperDir));
337
+ try { await bootstrapRtk(verDir, version, wrapperDir, opts.silent, root); }
338
+ catch (err) { log(`rtk fetch skipped: ${err.message}`); }
290
339
  return finalPath;
291
340
  }
292
341
 
@@ -294,27 +343,33 @@ async function bootstrap(opts) {
294
343
  acquireLock(lockPath);
295
344
  try {
296
345
  if (fs.existsSync(finalPath) && fs.existsSync(okSentinel)) {
297
- pruneOldVersions(root, version);
346
+ pruneOldVersions(root, version, readRtkVersion(wrapperDir));
347
+ return finalPath;
348
+ }
349
+ if (healIfShaMatches(finalPath, expectedSha, okSentinel, partialPath, 'plugkit')) {
350
+ log(`cache heal (sha match) under lock: ${finalPath}`);
351
+ pruneOldVersions(root, version, readRtkVersion(wrapperDir));
352
+ try { await bootstrapRtk(verDir, version, wrapperDir, opts.silent, root); }
353
+ catch (err) { log(`rtk fetch skipped: ${err.message}`); }
298
354
  return finalPath;
299
355
  }
300
356
 
301
- const tmpPath = `${finalPath}.partial`;
302
- if (fs.existsSync(tmpPath)) {
357
+ if (fs.existsSync(partialPath)) {
303
358
  try {
304
- const st = fs.statSync(tmpPath);
359
+ const st = fs.statSync(partialPath);
305
360
  if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
306
- fs.unlinkSync(tmpPath);
307
- log(`cleared stale partial: ${tmpPath}`);
361
+ fs.unlinkSync(partialPath);
362
+ log(`cleared stale partial: ${partialPath}`);
308
363
  }
309
364
  } catch (_) {}
310
365
  }
311
366
  const url = `https://github.com/${RELEASE_REPO}/releases/download/v${version}/${binName}`;
312
- await downloadWithRetry(url, tmpPath);
367
+ await downloadWithRetry(url, partialPath);
313
368
 
314
369
  if (expectedSha) {
315
- const got = await sha256OfFile(tmpPath);
370
+ const got = await sha256OfFile(partialPath);
316
371
  if (got !== expectedSha) {
317
- try { fs.unlinkSync(tmpPath); } catch (_) {}
372
+ try { fs.unlinkSync(partialPath); } catch (_) {}
318
373
  throw new Error(`sha256 mismatch for ${binName}: expected ${expectedSha}, got ${got}`);
319
374
  }
320
375
  log('sha256 verified');
@@ -322,11 +377,11 @@ async function bootstrap(opts) {
322
377
  log('no sha256 manifest — skipping verify');
323
378
  }
324
379
 
325
- try { fs.renameSync(tmpPath, finalPath); }
380
+ try { fs.renameSync(partialPath, finalPath); }
326
381
  catch (err) {
327
382
  if (err.code === 'EEXIST' || err.code === 'EPERM') {
328
383
  try { fs.unlinkSync(finalPath); } catch (_) {}
329
- fs.renameSync(tmpPath, finalPath);
384
+ fs.renameSync(partialPath, finalPath);
330
385
  } else throw err;
331
386
  }
332
387
 
@@ -337,10 +392,9 @@ async function bootstrap(opts) {
337
392
  fs.writeFileSync(okSentinel, new Date().toISOString());
338
393
  log(`installed ${finalPath}`);
339
394
  obsEvent('bootstrap', 'install.done', { path: finalPath, version, kind: 'plugkit' });
340
- pruneOldVersions(root, version);
395
+ pruneOldVersions(root, version, readRtkVersion(wrapperDir));
341
396
  proactiveKillForNewInstall(version);
342
- // Best-effort rtk fetch: failures here never block plugkit usage
343
- try { await bootstrapRtk(verDir, version, wrapperDir, opts.silent); }
397
+ try { await bootstrapRtk(verDir, version, wrapperDir, opts.silent, root); }
344
398
  catch (err) { log(`rtk fetch skipped: ${err.message}`); }
345
399
  return finalPath;
346
400
  } finally {
@@ -348,10 +402,19 @@ async function bootstrap(opts) {
348
402
  }
349
403
  }
350
404
 
351
- async function bootstrapRtk(verDir, version, wrapperDir, silent) {
405
+ function rtkCacheDir(root, wrapperDir, plugkitVerDir) {
406
+ const rtkVer = readRtkVersion(wrapperDir);
407
+ if (!rtkVer) return plugkitVerDir;
408
+ const dir = path.join(root, `rtk-v${rtkVer}`);
409
+ ensureDir(dir);
410
+ return dir;
411
+ }
412
+
413
+ async function bootstrapRtk(plugkitVerDir, plugkitVersion, wrapperDir, silent, root) {
352
414
  const rtkName = rtkBinaryName();
353
- const rtkPath = path.join(verDir, rtkName);
354
- const rtkOk = path.join(verDir, '.rtk-ok');
415
+ const cacheDir = rtkCacheDir(root || cacheRoot(), wrapperDir, plugkitVerDir);
416
+ const rtkPath = path.join(cacheDir, rtkName);
417
+ const rtkOk = path.join(cacheDir, '.rtk-ok');
355
418
  if (fs.existsSync(rtkPath) && fs.existsSync(rtkOk)) {
356
419
  if (!silent) log(`rtk cache hit: ${rtkPath}`);
357
420
  return rtkPath;
@@ -359,7 +422,11 @@ async function bootstrapRtk(verDir, version, wrapperDir, silent) {
359
422
  const rtkSha = readShaManifest(wrapperDir, 'rtk.sha256');
360
423
  const expected = rtkSha ? rtkSha[rtkName] : null;
361
424
  const tmp = `${rtkPath}.partial`;
362
- const url = `https://github.com/${RELEASE_REPO}/releases/download/v${version}/${rtkName}`;
425
+ if (healIfShaMatches(rtkPath, expected, rtkOk, tmp, 'rtk')) {
426
+ if (!silent) log(`rtk cache heal (sha match): ${rtkPath}`);
427
+ return rtkPath;
428
+ }
429
+ const url = `https://github.com/${RELEASE_REPO}/releases/download/v${plugkitVersion}/${rtkName}`;
363
430
  await downloadWithRetry(url, tmp);
364
431
  if (expected) {
365
432
  const got = await sha256OfFile(tmp);
@@ -378,7 +445,7 @@ async function bootstrapRtk(verDir, version, wrapperDir, silent) {
378
445
  if (os.platform() !== 'win32') { try { fs.chmodSync(rtkPath, 0o755); } catch (_) {} }
379
446
  fs.writeFileSync(rtkOk, new Date().toISOString());
380
447
  log(`installed ${rtkPath}`);
381
- obsEvent('bootstrap', 'install.done', { path: rtkPath, version, kind: 'rtk' });
448
+ obsEvent('bootstrap', 'install.done', { path: rtkPath, plugkit_version: plugkitVersion, rtk_version: readRtkVersion(wrapperDir) || plugkitVersion, kind: 'rtk' });
382
449
  return rtkPath;
383
450
  }
384
451
 
@@ -390,9 +457,10 @@ function resolveCachedRtk(opts) {
390
457
  try { const r = cacheRoot(); ensureDir(r); return r; }
391
458
  catch (_) { const r = fallbackCacheRoot(); ensureDir(r); return r; }
392
459
  })();
393
- const verDir = path.join(root, `v${version}`);
394
- const rtkPath = path.join(verDir, rtkBinaryName());
395
- const rtkOk = path.join(verDir, '.rtk-ok');
460
+ const plugkitVerDir = path.join(root, `v${version}`);
461
+ const cacheDir = rtkCacheDir(root, wrapperDir, plugkitVerDir);
462
+ const rtkPath = path.join(cacheDir, rtkBinaryName());
463
+ const rtkOk = path.join(cacheDir, '.rtk-ok');
396
464
  if (fs.existsSync(rtkPath) && fs.existsSync(rtkOk)) return rtkPath;
397
465
  return null;
398
466
  }
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.948",
3
+ "version": "2.0.949",
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-codex",
3
- "version": "2.0.948",
3
+ "version": "2.0.949",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.948",
3
+ "version": "2.0.949",
4
4
  "description": "State machine agent with hooks, skills, and automated git enforcement",
5
5
  "author": {
6
6
  "name": "AnEntrypoint",