vibeostheog 0.24.15 → 0.24.17

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/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.24.16
2
+ - fix: serialize model tiers writes
3
+ - test: add concurrent tiers write regression
4
+
5
+
1
6
  ## 0.24.15
2
7
  - feat: smooth delegation UX — conversational reasoning over error injection
3
8
  - fix: preserve local agent mode in remote control merge
package/dist/vibeOS.js CHANGED
@@ -587,7 +587,7 @@ var init_meta_controller = __esm({
587
587
  });
588
588
 
589
589
  // src/vibeOS-lib/blackbox/pivot-cache.js
590
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
590
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
591
591
  import { join as join6, dirname as dirname6 } from "node:path";
592
592
  import { homedir as homedir5 } from "node:os";
593
593
  var PivotCache;
@@ -625,7 +625,7 @@ var init_pivot_cache = __esm({
625
625
  const p = this._storePath();
626
626
  const dir = dirname6(p);
627
627
  if (!existsSync7(dir))
628
- mkdirSync6(dir, { recursive: true });
628
+ mkdirSync5(dir, { recursive: true });
629
629
  writeFileSync6(p, JSON.stringify(this.store, null, 2), "utf-8");
630
630
  } catch {
631
631
  }
@@ -796,7 +796,7 @@ __export(vibemax_exports, {
796
796
  vibemaxPipeline: () => vibemaxPipeline,
797
797
  vibemaxSelectMode: () => vibemaxSelectMode
798
798
  });
799
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
799
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
800
800
  import { resolve as resolve2, dirname as dirname7 } from "node:path";
801
801
  import { fileURLToPath as fileURLToPath4 } from "node:url";
802
802
  function fallback(sr, text) {
@@ -1078,7 +1078,7 @@ function loadVibeMaXModel() {
1078
1078
  return null;
1079
1079
  }
1080
1080
  function saveVibeMaXModel(model) {
1081
- mkdirSync7(dirname7(MODEL_PATH), { recursive: true });
1081
+ mkdirSync6(dirname7(MODEL_PATH), { recursive: true });
1082
1082
  writeFileSync7(MODEL_PATH, JSON.stringify(model, null, 2) + "\n", "utf-8");
1083
1083
  }
1084
1084
  function getVibeMaXModelMeta() {
@@ -1104,8 +1104,8 @@ var init_vibemax = __esm({
1104
1104
 
1105
1105
  // src/index.ts
1106
1106
  init_flow_enforcer();
1107
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync15, existsSync as existsSync18, mkdirSync as mkdirSync14, copyFileSync as copyFileSync5, renameSync as renameSync6 } from "node:fs";
1108
- import { join as join18, dirname as dirname13, basename as basename8 } from "node:path";
1107
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync15, existsSync as existsSync18, mkdirSync as mkdirSync13, copyFileSync as copyFileSync2, renameSync as renameSync6 } from "node:fs";
1108
+ import { join as join18, dirname as dirname13, basename as basename5 } from "node:path";
1109
1109
 
1110
1110
  // src/vibeOS-lib/session-metrics.js
1111
1111
  function formatDuration(totalSeconds) {
@@ -2354,22 +2354,22 @@ async function remoteCall(method, args, fallbackFn) {
2354
2354
  }
2355
2355
 
2356
2356
  // src/lib/pricing.js
2357
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, appendFileSync as appendFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync5, statSync as statSync5, copyFileSync as copyFileSync3, renameSync as renameSync4, openSync as openSync2, closeSync as closeSync2, rmSync as rmSync3, readdirSync as readdirSync2 } from "node:fs";
2358
- import { join as join5, dirname as dirname5, basename as basename4, resolve } from "node:path";
2357
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4, statSync as statSync5, renameSync as renameSync4, openSync as openSync2, closeSync as closeSync2, rmSync as rmSync3, readdirSync as readdirSync2 } from "node:fs";
2358
+ import { join as join5, dirname as dirname5, resolve } from "node:path";
2359
2359
  import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
2360
2360
  import { createHash as createHash2 } from "node:crypto";
2361
2361
 
2362
2362
  // src/lib/state.js
2363
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, appendFileSync as appendFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync4, readdirSync, openSync, readSync, closeSync, rmSync as rmSync2, copyFileSync as copyFileSync2, renameSync as renameSync3 } from "node:fs";
2364
- import { join as join4, dirname as dirname4, basename as basename3 } from "node:path";
2363
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, statSync as statSync4, readdirSync, openSync, readSync, closeSync, rmSync as rmSync2, copyFileSync, renameSync as renameSync3 } from "node:fs";
2364
+ import { join as join4, dirname as dirname4, basename as basename2 } from "node:path";
2365
2365
  import { spawn } from "node:child_process";
2366
2366
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
2367
2367
  import { createHash } from "node:crypto";
2368
2368
  import { AsyncLocalStorage } from "node:async_hooks";
2369
2369
 
2370
2370
  // src/lib/selection-manager.js
2371
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync3, copyFileSync, renameSync as renameSync2 } from "node:fs";
2372
- import { join as join3, basename } from "node:path";
2371
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, statSync as statSync3, renameSync as renameSync2 } from "node:fs";
2372
+ import { join as join3 } from "node:path";
2373
2373
  import { homedir as homedir2, tmpdir } from "node:os";
2374
2374
  var USER_HOME = (() => {
2375
2375
  try {
@@ -2381,20 +2381,6 @@ var USER_HOME = (() => {
2381
2381
  function getVibeOSHome2() {
2382
2382
  return process.env.VIBEOS_HOME || join3(process.env.HOME || homedir2(), ".claude");
2383
2383
  }
2384
- function _handleStateCorruption(path) {
2385
- const backupDir = join3(getVibeOSHome2(), ".backups");
2386
- mkdirSync3(backupDir, { recursive: true });
2387
- const backupPath = join3(backupDir, basename(path) + ".corrupted." + Date.now());
2388
- try {
2389
- copyFileSync(path, backupPath);
2390
- } catch {
2391
- }
2392
- const logPath = join3(getVibeOSHome2(), ".state-corruption-log.jsonl");
2393
- try {
2394
- appendFileSync2(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
2395
- } catch {
2396
- }
2397
- }
2398
2384
  function safeJsonParse2(raw) {
2399
2385
  if (raw == null || raw === "")
2400
2386
  return null;
@@ -2451,14 +2437,16 @@ function loadSelection() {
2451
2437
  function writeSelection(key, value) {
2452
2438
  const TIERS_FILE3 = join3(getVibeOSHome2(), "model-tiers.json");
2453
2439
  try {
2454
- const j = safeJsonParse2(readFileSync3(TIERS_FILE3, "utf-8"));
2455
- if (!j.selection)
2456
- j.selection = {};
2457
- j.selection[key] = value;
2458
- const tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
2459
- writeFileSync3(tmp, JSON.stringify(j, null, 2) + "\n");
2460
- renameSync2(tmp, TIERS_FILE3);
2461
- return true;
2440
+ return withFileLock(TIERS_FILE3, () => {
2441
+ const j = safeJsonParse2(readFileSync3(TIERS_FILE3, "utf-8"));
2442
+ if (!j.selection)
2443
+ j.selection = {};
2444
+ j.selection[key] = value;
2445
+ const tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
2446
+ writeFileSync3(tmp, JSON.stringify(j, null, 2) + "\n");
2447
+ renameSync2(tmp, TIERS_FILE3);
2448
+ return true;
2449
+ });
2462
2450
  } catch (err) {
2463
2451
  console.error(`[vibeOS] writeSelection failed: ${err.message}`);
2464
2452
  return false;
@@ -2535,7 +2523,7 @@ function writeSessionOptMode(sid, mode) {
2535
2523
  }
2536
2524
 
2537
2525
  // src/lib/pattern-helpers.js
2538
- import { relative, basename as basename2 } from "node:path";
2526
+ import { relative, basename } from "node:path";
2539
2527
  function normalizeObservedPath(filePath, directory3) {
2540
2528
  if (!filePath || typeof filePath !== "string")
2541
2529
  return "unknown";
@@ -2556,7 +2544,7 @@ function normalizeObservedPath(filePath, directory3) {
2556
2544
  return `src/*.${m[1].toLowerCase()}`;
2557
2545
  if (p.startsWith("tests/") && m)
2558
2546
  return `tests/*.${m[1].toLowerCase()}`;
2559
- return basename2(p) || "unknown";
2547
+ return basename(p) || "unknown";
2560
2548
  }
2561
2549
  function commandFamily(command) {
2562
2550
  const c = String(command || "").trim().toLowerCase();
@@ -3289,6 +3277,12 @@ var MAX_SCRATCHPAD_FILES = 1e3;
3289
3277
  var MAX_SCRATCHPAD_BYTES = 10 * 1024 * 1024;
3290
3278
  var MAX_SESSION_SCRATCHPAD_FILES = 200;
3291
3279
  var MAX_SESSION_SCRATCHPAD_BYTES = 2 * 1024 * 1024;
3280
+ var CORRUPTION_BACKUP_MAX = 5;
3281
+ var CORRUPTION_BACKUP_TTL_MS = 24 * 60 * 60 * 1e3;
3282
+ var LEDGER_ROTATE_MAX_BYTES = 1 * 1024 * 1024;
3283
+ var LEDGER_ROTATE_MAX_LINES = 5e4;
3284
+ var LEDGER_ROTATE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
3285
+ var ACTIVE_JOBS_STALE_MS = 72 * 60 * 60 * 1e3;
3292
3286
  var MAX_PTR_CANDIDATES = 50;
3293
3287
  var SUMMARY_HEAD_TRUNCATE = 500;
3294
3288
  function getVibeOSHome3() {
@@ -3419,19 +3413,48 @@ var tool = Object.assign((def) => def, {
3419
3413
  enum: (values) => _zType({ kind: "enum", values })
3420
3414
  }
3421
3415
  });
3422
- function _handleStateCorruption2(path) {
3416
+ function _pruneCorruptionBackups(backupDir) {
3417
+ try {
3418
+ if (!existsSync5(backupDir))
3419
+ return;
3420
+ const now = Date.now();
3421
+ const backups = readdirSync(backupDir).map((name) => {
3422
+ const path = join4(backupDir, name);
3423
+ try {
3424
+ const st = statSync4(path);
3425
+ return { name, path, mtimeMs: st.mtimeMs };
3426
+ } catch {
3427
+ return null;
3428
+ }
3429
+ }).filter((entry) => !!entry && entry.name.includes(".corrupted.")).sort((a, b) => b.mtimeMs - a.mtimeMs);
3430
+ const keep = new Set(backups.slice(0, CORRUPTION_BACKUP_MAX).map((b) => b.path));
3431
+ for (const backup of backups) {
3432
+ const isExpired = now - backup.mtimeMs > CORRUPTION_BACKUP_TTL_MS;
3433
+ if (isExpired || !keep.has(backup.path)) {
3434
+ try {
3435
+ rmSync2(backup.path, { force: true });
3436
+ } catch {
3437
+ }
3438
+ }
3439
+ }
3440
+ } catch {
3441
+ }
3442
+ }
3443
+ function _handleStateCorruption(path) {
3423
3444
  const backupDir = join4(VIBEOS_HOME, ".backups");
3424
- mkdirSync4(backupDir, { recursive: true });
3425
- const backupPath = join4(backupDir, basename3(path) + ".corrupted." + Date.now());
3445
+ mkdirSync3(backupDir, { recursive: true });
3446
+ const backupPath = join4(backupDir, basename2(path) + ".corrupted." + Date.now());
3426
3447
  try {
3427
- copyFileSync2(path, backupPath);
3448
+ copyFileSync(path, backupPath);
3428
3449
  } catch {
3429
3450
  }
3430
3451
  const logPath = join4(VIBEOS_HOME, ".state-corruption-log.jsonl");
3431
3452
  try {
3432
- appendFileSync3(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
3453
+ appendFileSync2(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
3433
3454
  } catch {
3434
3455
  }
3456
+ _pruneCorruptionBackups(backupDir);
3457
+ return backupPath;
3435
3458
  }
3436
3459
  function _lockPathFor(filePath) {
3437
3460
  const hash = createHash("sha1").update(String(filePath || "")).digest("hex");
@@ -3444,7 +3467,7 @@ function withFileLock(filePath, fn, opts = {}) {
3444
3467
  const start = Date.now();
3445
3468
  while (Date.now() - start < timeoutMs) {
3446
3469
  try {
3447
- mkdirSync4(FILE_LOCK_DIR, { recursive: true });
3470
+ mkdirSync3(FILE_LOCK_DIR, { recursive: true });
3448
3471
  const fd = openSync(lockPath, "wx");
3449
3472
  try {
3450
3473
  writeFileSync4(fd, `${process.pid}
@@ -3522,12 +3545,12 @@ function readJsonOrEmpty(filePath) {
3522
3545
  return {};
3523
3546
  const st = statSync4(filePath);
3524
3547
  if (st.size > 10485760) {
3525
- _handleStateCorruption2(filePath);
3548
+ _handleStateCorruption(filePath);
3526
3549
  return {};
3527
3550
  }
3528
3551
  return safeJsonParse3(readFileSync4(filePath, "utf-8"));
3529
3552
  } catch {
3530
- _handleStateCorruption2(filePath);
3553
+ _handleStateCorruption(filePath);
3531
3554
  return {};
3532
3555
  }
3533
3556
  }
@@ -3552,7 +3575,7 @@ function updateState(mutator) {
3552
3575
  state._gen = preGen + 1;
3553
3576
  const next = mutator(state) ?? state;
3554
3577
  validateState(next, delegationStateFile);
3555
- mkdirSync4(dirname4(delegationStateFile), { recursive: true });
3578
+ mkdirSync3(dirname4(delegationStateFile), { recursive: true });
3556
3579
  const tmp = delegationStateFile + ".tmp";
3557
3580
  writeFileSync4(tmp, JSON.stringify(next, null, 2) + "\n");
3558
3581
  renameSync3(tmp, delegationStateFile);
@@ -3578,12 +3601,12 @@ function readFullState() {
3578
3601
  return {};
3579
3602
  const st = statSync4(delegationStateFile);
3580
3603
  if (st.size > 10485760) {
3581
- _handleStateCorruption2(delegationStateFile);
3604
+ _handleStateCorruption(delegationStateFile);
3582
3605
  return {};
3583
3606
  }
3584
3607
  return safeJsonParse3(readFileSync4(delegationStateFile, "utf-8"));
3585
3608
  } catch {
3586
- _handleStateCorruption2(delegationStateFile);
3609
+ _handleStateCorruption(delegationStateFile);
3587
3610
  return {};
3588
3611
  }
3589
3612
  }
@@ -3623,7 +3646,7 @@ function loadGlobalLearning() {
3623
3646
  return DFLT_GL;
3624
3647
  const st = statSync4(globalLearningFile);
3625
3648
  if (st.size > 10485760) {
3626
- _handleStateCorruption2(globalLearningFile);
3649
+ _handleStateCorruption(globalLearningFile);
3627
3650
  return DFLT_GL;
3628
3651
  }
3629
3652
  const j = safeJsonParse3(readFileSync4(globalLearningFile, "utf-8"));
@@ -3636,7 +3659,7 @@ function loadGlobalLearning() {
3636
3659
  j.context7_last_seen ??= null;
3637
3660
  return j;
3638
3661
  } catch {
3639
- _handleStateCorruption2(globalLearningFile);
3662
+ _handleStateCorruption(globalLearningFile);
3640
3663
  return DFLT_GL;
3641
3664
  }
3642
3665
  }
@@ -3646,7 +3669,7 @@ function updateGlobalLearning(mutator) {
3646
3669
  const s = loadGlobalLearning();
3647
3670
  const next = mutator(s) ?? s;
3648
3671
  next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3649
- mkdirSync4(dirname4(globalLearningFile), { recursive: true });
3672
+ mkdirSync3(dirname4(globalLearningFile), { recursive: true });
3650
3673
  const tmp = globalLearningFile + ".tmp";
3651
3674
  writeFileSync4(tmp, JSON.stringify(next, null, 2));
3652
3675
  renameSync3(tmp, globalLearningFile);
@@ -3698,19 +3721,19 @@ function loadBlackboxState() {
3698
3721
  return { enabled: true, sessions: {} };
3699
3722
  const st = statSync4(blackboxFile);
3700
3723
  if (st.size > 10485760) {
3701
- _handleStateCorruption2(blackboxFile);
3724
+ _handleStateCorruption(blackboxFile);
3702
3725
  return { enabled: false, sessions: {} };
3703
3726
  }
3704
3727
  return safeJsonParse3(readFileSync4(blackboxFile, "utf-8")) || { enabled: false, sessions: {} };
3705
3728
  } catch {
3706
- _handleStateCorruption2(blackboxFile);
3729
+ _handleStateCorruption(blackboxFile);
3707
3730
  return { enabled: false, sessions: {} };
3708
3731
  }
3709
3732
  }
3710
3733
  function saveBlackboxState(state) {
3711
3734
  const blackboxFile = join4(getVibeOSHome3(), "blackbox-state.json");
3712
3735
  try {
3713
- mkdirSync4(dirname4(blackboxFile), { recursive: true });
3736
+ mkdirSync3(dirname4(blackboxFile), { recursive: true });
3714
3737
  const tmp = blackboxFile + ".tmp";
3715
3738
  writeFileSync4(tmp, JSON.stringify(state, null, 2) + "\n");
3716
3739
  renameSync3(tmp, blackboxFile);
@@ -3732,7 +3755,7 @@ function getGlobalIndexPath() {
3732
3755
  }
3733
3756
  function ensureSessionScratchpadDirs() {
3734
3757
  try {
3735
- mkdirSync4(getSessionScratchpadDir(), { recursive: true });
3758
+ mkdirSync3(getSessionScratchpadDir(), { recursive: true });
3736
3759
  return true;
3737
3760
  } catch {
3738
3761
  return false;
@@ -3776,7 +3799,8 @@ function _flushLedgerBuffer() {
3776
3799
  const lines = batch.map((e) => typeof e === "string" ? e.trimEnd() : String(e).trimEnd());
3777
3800
  const joined = lines.filter(Boolean).map((l) => l + "\n").join("");
3778
3801
  try {
3779
- appendFileSync3(SAVINGS_LEDGER_FILE, joined);
3802
+ appendFileSync2(SAVINGS_LEDGER_FILE, joined);
3803
+ _compactSavingsLedgerIfNeeded();
3780
3804
  } catch {
3781
3805
  }
3782
3806
  }
@@ -3923,10 +3947,10 @@ function indexAppend(hash, tool2, size, extra) {
3923
3947
  const entry = JSON.stringify(entryObj) + "\n";
3924
3948
  const globalIndex = getGlobalIndexPath();
3925
3949
  const sessionIndex = getSessionIndexPath();
3926
- mkdirSync4(dirname4(globalIndex), { recursive: true });
3927
- mkdirSync4(dirname4(sessionIndex), { recursive: true });
3928
- appendFileSync3(globalIndex, entry);
3929
- appendFileSync3(sessionIndex, entry);
3950
+ mkdirSync3(dirname4(globalIndex), { recursive: true });
3951
+ mkdirSync3(dirname4(sessionIndex), { recursive: true });
3952
+ appendFileSync2(globalIndex, entry);
3953
+ appendFileSync2(sessionIndex, entry);
3930
3954
  } catch (err) {
3931
3955
  console.error(`[vibeOS] index write failed: ${err.message}`);
3932
3956
  }
@@ -4179,21 +4203,83 @@ function pruneScratchpadOnce() {
4179
4203
  }
4180
4204
  cleanupStaleSessionScratchpads();
4181
4205
  }
4182
- function loadActiveJobs() {
4206
+ function _readActiveJobsRaw() {
4183
4207
  try {
4184
4208
  if (!existsSync5(ACTIVE_JOBS_FILE))
4185
4209
  return {};
4186
- const st = statSync4(ACTIVE_JOBS_FILE);
4187
- if (st.size > 10485760) {
4188
- _handleStateCorruption2(ACTIVE_JOBS_FILE);
4189
- return {};
4190
- }
4191
4210
  const raw = safeJsonParse3(readFileSync4(ACTIVE_JOBS_FILE, "utf-8"));
4192
- if (!raw || typeof raw !== "object")
4193
- return {};
4194
- return raw;
4211
+ return raw && typeof raw === "object" ? raw : {};
4212
+ } catch {
4213
+ _handleStateCorruption(ACTIVE_JOBS_FILE);
4214
+ return {};
4215
+ }
4216
+ }
4217
+ function _writeActiveJobsRaw(jobs) {
4218
+ try {
4219
+ mkdirSync3(dirname4(ACTIVE_JOBS_FILE), { recursive: true });
4220
+ const tmp = ACTIVE_JOBS_FILE + ".tmp";
4221
+ writeFileSync4(tmp, JSON.stringify(jobs, null, 2) + "\n");
4222
+ renameSync3(tmp, ACTIVE_JOBS_FILE);
4223
+ } catch {
4224
+ }
4225
+ }
4226
+ function _normalizeActiveJobRecord(record, now = Date.now(), strict = false) {
4227
+ if (!record || typeof record !== "object")
4228
+ return { record: null, changed: false, stale: false };
4229
+ const next = { ...record };
4230
+ let changed = false;
4231
+ const updatedAtRaw = typeof next.updatedAt === "string" ? next.updatedAt : "";
4232
+ const createdAtRaw = typeof next.createdAt === "string" ? next.createdAt : "";
4233
+ const updatedAtMs = Date.parse(updatedAtRaw);
4234
+ const createdAtMs = Date.parse(createdAtRaw);
4235
+ const anchorMs = Number.isFinite(updatedAtMs) ? updatedAtMs : createdAtMs;
4236
+ const stale = Number.isFinite(anchorMs) && now - anchorMs > ACTIVE_JOBS_STALE_MS;
4237
+ if (strict && (!next.status || typeof next.status !== "string" || !next.status.trim()))
4238
+ return { record: null, changed: false, stale };
4239
+ if (strict && !Number.isFinite(createdAtMs))
4240
+ return { record: null, changed: false, stale };
4241
+ if (!Number.isFinite(createdAtMs)) {
4242
+ next.createdAt = Number.isFinite(anchorMs) ? new Date(anchorMs).toISOString() : new Date(now).toISOString();
4243
+ changed = true;
4244
+ }
4245
+ if (!Number.isFinite(updatedAtMs)) {
4246
+ next.updatedAt = next.createdAt || new Date(now).toISOString();
4247
+ changed = true;
4248
+ }
4249
+ if (typeof next.status !== "string" || !next.status.trim()) {
4250
+ next.status = "active";
4251
+ changed = true;
4252
+ }
4253
+ if (stale && next.status !== "completed") {
4254
+ next.status = "completed";
4255
+ next.completedAt = new Date(now).toISOString();
4256
+ changed = true;
4257
+ }
4258
+ return { record: next, changed, stale };
4259
+ }
4260
+ function loadActiveJobs() {
4261
+ try {
4262
+ return withFileLock(ACTIVE_JOBS_FILE, () => {
4263
+ const raw = _readActiveJobsRaw();
4264
+ const next = {};
4265
+ let changed = false;
4266
+ const now = Date.now();
4267
+ for (const [key, value] of Object.entries(raw || {})) {
4268
+ const norm = _normalizeActiveJobRecord(value, now, true);
4269
+ if (!norm.record) {
4270
+ changed = true;
4271
+ continue;
4272
+ }
4273
+ next[key] = norm.record;
4274
+ if (norm.changed)
4275
+ changed = true;
4276
+ }
4277
+ if (changed)
4278
+ _writeActiveJobsRaw(next);
4279
+ return next;
4280
+ });
4195
4281
  } catch {
4196
- _handleStateCorruption2(ACTIVE_JOBS_FILE);
4282
+ _handleStateCorruption(ACTIVE_JOBS_FILE);
4197
4283
  return {};
4198
4284
  }
4199
4285
  }
@@ -4210,15 +4296,19 @@ function saveActiveJobForProject(job, fp2 = currentProjectFingerprint) {
4210
4296
  if (!fp2 || !job || typeof job !== "object")
4211
4297
  return;
4212
4298
  try {
4213
- const jobs = loadActiveJobs();
4214
- jobs[fp2] = job;
4215
- mkdirSync4(dirname4(ACTIVE_JOBS_FILE), { recursive: true });
4216
- const tmp = ACTIVE_JOBS_FILE + ".tmp";
4217
- writeFileSync4(tmp, JSON.stringify(jobs, null, 2));
4218
- renameSync3(tmp, ACTIVE_JOBS_FILE);
4299
+ withFileLock(ACTIVE_JOBS_FILE, () => {
4300
+ const jobs = _readActiveJobsRaw();
4301
+ const norm = _normalizeActiveJobRecord(job);
4302
+ jobs[fp2] = norm.record || job;
4303
+ _writeActiveJobsRaw(jobs);
4304
+ });
4219
4305
  } catch {
4220
4306
  }
4221
4307
  }
4308
+ try {
4309
+ loadActiveJobs();
4310
+ } catch {
4311
+ }
4222
4312
  function projectFingerprint(dir) {
4223
4313
  if (!dir)
4224
4314
  return "unknown";
@@ -4240,7 +4330,7 @@ function saveProjectState(state) {
4240
4330
  const projectStateFile = join4(getVibeOSHome3(), "project-states.json");
4241
4331
  try {
4242
4332
  withFileLock(projectStateFile, () => {
4243
- mkdirSync4(dirname4(projectStateFile), { recursive: true });
4333
+ mkdirSync3(dirname4(projectStateFile), { recursive: true });
4244
4334
  const _tmp = projectStateFile + ".tmp." + Date.now();
4245
4335
  writeFileSync4(_tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
4246
4336
  renameSync3(_tmp, projectStateFile);
@@ -4477,7 +4567,7 @@ function loadTodos() {
4477
4567
  }
4478
4568
  function saveTodos(todos) {
4479
4569
  try {
4480
- mkdirSync4(dirname4(TODOS_FILE), { recursive: true });
4570
+ mkdirSync3(dirname4(TODOS_FILE), { recursive: true });
4481
4571
  const tmp = TODOS_FILE + ".tmp." + Date.now();
4482
4572
  writeFileSync4(tmp, JSON.stringify(todos, null, 2), "utf-8");
4483
4573
  renameSync3(tmp, TODOS_FILE);
@@ -4516,6 +4606,57 @@ function markTodoDone(id2) {
4516
4606
  function getTodos() {
4517
4607
  return loadTodos();
4518
4608
  }
4609
+ function _compactSavingsLedgerIfNeeded() {
4610
+ try {
4611
+ if (!existsSync5(SAVINGS_LEDGER_FILE))
4612
+ return;
4613
+ const st = statSync4(SAVINGS_LEDGER_FILE);
4614
+ if (st.size <= LEDGER_ROTATE_MAX_BYTES)
4615
+ return;
4616
+ withFileLock(SAVINGS_LEDGER_FILE, () => {
4617
+ if (!existsSync5(SAVINGS_LEDGER_FILE))
4618
+ return;
4619
+ const lockedStat = statSync4(SAVINGS_LEDGER_FILE);
4620
+ if (lockedStat.size <= LEDGER_ROTATE_MAX_BYTES)
4621
+ return;
4622
+ const raw = readFileSync4(SAVINGS_LEDGER_FILE, "utf-8");
4623
+ if (!raw.trim())
4624
+ return;
4625
+ const now = Date.now();
4626
+ const rows = raw.split("\n").filter(Boolean).map((line) => {
4627
+ let rec = null;
4628
+ try {
4629
+ rec = JSON.parse(line);
4630
+ } catch {
4631
+ rec = null;
4632
+ }
4633
+ const atRaw = rec && typeof rec === "object" ? String(rec.at || rec.ts || "") : "";
4634
+ const atMs = Date.parse(atRaw);
4635
+ return { raw: line.trim(), atMs: Number.isFinite(atMs) ? atMs : null };
4636
+ }).filter((row) => row.raw);
4637
+ const recent = rows.filter((row) => row.atMs != null && now - Number(row.atMs) <= LEDGER_ROTATE_MAX_AGE_MS);
4638
+ const pool = recent.length > 0 ? recent : rows;
4639
+ const capped = pool.length > LEDGER_ROTATE_MAX_LINES ? pool.slice(-LEDGER_ROTATE_MAX_LINES) : pool;
4640
+ let size = 0;
4641
+ const kept = [];
4642
+ for (let i = capped.length - 1; i >= 0; i--) {
4643
+ const line = capped[i].raw;
4644
+ const lineBytes = Buffer.byteLength(line + "\n", "utf-8");
4645
+ if (kept.length > 0 && size + lineBytes > LEDGER_ROTATE_MAX_BYTES)
4646
+ break;
4647
+ kept.push(line);
4648
+ size += lineBytes;
4649
+ }
4650
+ const compacted = kept.reverse().join("\n") + "\n";
4651
+ if (compacted.trim() && compacted !== raw) {
4652
+ const tmp = SAVINGS_LEDGER_FILE + ".tmp." + Date.now();
4653
+ writeFileSync4(tmp, compacted, "utf-8");
4654
+ renameSync3(tmp, SAVINGS_LEDGER_FILE);
4655
+ }
4656
+ }, { timeoutMs: 4e3 });
4657
+ } catch {
4658
+ }
4659
+ }
4519
4660
  function readLedgerTotals() {
4520
4661
  const empty = { delegation: 0, cache: 0, context7: 0, total: 0, entries: 0 };
4521
4662
  try {
@@ -4524,15 +4665,19 @@ function readLedgerTotals() {
4524
4665
  return empty;
4525
4666
  }
4526
4667
  const st = statSync4(SAVINGS_LEDGER_FILE);
4527
- if (st.size > 10485760) {
4528
- _handleStateCorruption2(SAVINGS_LEDGER_FILE);
4529
- return empty;
4530
- }
4531
4668
  if (st.size === 0) {
4532
4669
  _ledgerTotalsCache = { mtime: st.mtimeMs, size: 0, delegation: 0, cache: 0, context7: 0, entries: 0 };
4533
4670
  return empty;
4534
4671
  }
4535
- if (_ledgerTotalsCache.mtime === st.mtimeMs && _ledgerTotalsCache.size === st.size) {
4672
+ if (st.size > LEDGER_ROTATE_MAX_BYTES) {
4673
+ _compactSavingsLedgerIfNeeded();
4674
+ }
4675
+ const currentStat = statSync4(SAVINGS_LEDGER_FILE);
4676
+ if (currentStat.size === 0) {
4677
+ _ledgerTotalsCache = { mtime: currentStat.mtimeMs, size: 0, delegation: 0, cache: 0, context7: 0, entries: 0 };
4678
+ return empty;
4679
+ }
4680
+ if (_ledgerTotalsCache.mtime === currentStat.mtimeMs && _ledgerTotalsCache.size === currentStat.size) {
4536
4681
  return {
4537
4682
  delegation: Math.round(_ledgerTotalsCache.delegation * 1e3) / 1e3,
4538
4683
  cache: Math.round(_ledgerTotalsCache.cache * 1e3) / 1e3,
@@ -4546,9 +4691,9 @@ function readLedgerTotals() {
4546
4691
  let context7 = 0;
4547
4692
  let entries = 0;
4548
4693
  let raw = "";
4549
- let incremental = _ledgerTotalsCache.size > 0 && st.size >= _ledgerTotalsCache.size && _ledgerTotalsCache.mtime > 0;
4694
+ let incremental = _ledgerTotalsCache.size > 0 && currentStat.size >= _ledgerTotalsCache.size && _ledgerTotalsCache.mtime > 0;
4550
4695
  if (incremental) {
4551
- const deltaSize = st.size - _ledgerTotalsCache.size;
4696
+ const deltaSize = currentStat.size - _ledgerTotalsCache.size;
4552
4697
  if (deltaSize > 0) {
4553
4698
  const fd = openSync(SAVINGS_LEDGER_FILE, "r");
4554
4699
  try {
@@ -4574,8 +4719,8 @@ function readLedgerTotals() {
4574
4719
  }
4575
4720
  if (!raw.trim()) {
4576
4721
  _ledgerTotalsCache = {
4577
- mtime: st.mtimeMs,
4578
- size: st.size,
4722
+ mtime: currentStat.mtimeMs,
4723
+ size: currentStat.size,
4579
4724
  delegation,
4580
4725
  cache,
4581
4726
  context7,
@@ -4618,7 +4763,7 @@ function readLedgerTotals() {
4618
4763
  else
4619
4764
  delegation += amt;
4620
4765
  }
4621
- _ledgerTotalsCache = { mtime: st.mtimeMs, size: st.size, delegation, cache, context7, entries };
4766
+ _ledgerTotalsCache = { mtime: currentStat.mtimeMs, size: currentStat.size, delegation, cache, context7, entries };
4622
4767
  const total = delegation + cache;
4623
4768
  return {
4624
4769
  delegation: Math.round(delegation * 1e3) / 1e3,
@@ -4703,7 +4848,7 @@ function saveSessionCheckpoint() {
4703
4848
  model: session.model || ""
4704
4849
  };
4705
4850
  const cpPath = join4(getSessionRoot(), "checkpoint.json");
4706
- mkdirSync4(dirname4(cpPath), { recursive: true });
4851
+ mkdirSync3(dirname4(cpPath), { recursive: true });
4707
4852
  const tmp = cpPath + ".tmp";
4708
4853
  writeFileSync4(tmp, JSON.stringify(cp, null, 2) + "\n");
4709
4854
  renameSync3(tmp, cpPath);
@@ -4744,20 +4889,6 @@ function getOpenCodeDesktopHome() {
4744
4889
  return process.env.VIBEOS_OPENCODE_DESKTOP_HOME || join5(process.env.HOME || homedir4(), "Library", "Application Support", "ai.opencode.desktop");
4745
4890
  }
4746
4891
  var TIERS_FILE2 = join5(getVibeOSHome4(), "model-tiers.json");
4747
- function _handleStateCorruption3(path) {
4748
- const backupDir = join5(getVibeOSHome4(), ".backups");
4749
- mkdirSync5(backupDir, { recursive: true });
4750
- const backupPath = join5(backupDir, basename4(path) + ".corrupted." + Date.now());
4751
- try {
4752
- copyFileSync3(path, backupPath);
4753
- } catch {
4754
- }
4755
- const logPath = join5(getVibeOSHome4(), ".state-corruption-log.jsonl");
4756
- try {
4757
- appendFileSync4(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
4758
- } catch {
4759
- }
4760
- }
4761
4892
  function _lockPathFor2(filePath) {
4762
4893
  const hash = createHash2("sha1").update(String(filePath || "")).digest("hex");
4763
4894
  return join5(getVibeOSHome4(), ".vibeOS-locks", `${hash}.lock`);
@@ -4769,7 +4900,7 @@ function withFileLock2(filePath, fn, opts = {}) {
4769
4900
  const start = Date.now();
4770
4901
  while (Date.now() - start < timeoutMs) {
4771
4902
  try {
4772
- mkdirSync5(join5(getVibeOSHome4(), ".vibeOS-locks"), { recursive: true });
4903
+ mkdirSync4(join5(getVibeOSHome4(), ".vibeOS-locks"), { recursive: true });
4773
4904
  const fd = openSync2(lockPath, "wx");
4774
4905
  try {
4775
4906
  writeFileSync5(fd, `${process.pid}
@@ -5287,7 +5418,7 @@ function _loadDynamicPricingCache() {
5287
5418
  return {};
5288
5419
  const st = statSync5(PRICING_CACHE_FILE2);
5289
5420
  if (st.size > 10485760) {
5290
- _handleStateCorruption3(PRICING_CACHE_FILE2);
5421
+ _handleStateCorruption(PRICING_CACHE_FILE2);
5291
5422
  _dynamicPricingCache = {};
5292
5423
  return {};
5293
5424
  }
@@ -5295,7 +5426,7 @@ function _loadDynamicPricingCache() {
5295
5426
  const map = raw?.models && typeof raw.models === "object" ? raw.models : {};
5296
5427
  _dynamicPricingCache = map;
5297
5428
  } catch {
5298
- _handleStateCorruption3(PRICING_CACHE_FILE2);
5429
+ _handleStateCorruption(PRICING_CACHE_FILE2);
5299
5430
  _dynamicPricingCache = {};
5300
5431
  }
5301
5432
  return _dynamicPricingCache;
@@ -5332,7 +5463,7 @@ function _writeDynamicPricingCache(modelsMap) {
5332
5463
  const PRICING_CACHE_FILE2 = join5(getVibeOSHome4(), "model-pricing-cache.json");
5333
5464
  try {
5334
5465
  withFileLock2(PRICING_CACHE_FILE2, () => {
5335
- mkdirSync5(dirname5(PRICING_CACHE_FILE2), { recursive: true });
5466
+ mkdirSync4(dirname5(PRICING_CACHE_FILE2), { recursive: true });
5336
5467
  let merged = {};
5337
5468
  try {
5338
5469
  if (existsSync6(PRICING_CACHE_FILE2)) {
@@ -5390,7 +5521,7 @@ function _loadPricingOverrides() {
5390
5521
  return {};
5391
5522
  const st = statSync5(tiersFile);
5392
5523
  if (st.size > 10485760) {
5393
- _handleStateCorruption3(tiersFile);
5524
+ _handleStateCorruption(tiersFile);
5394
5525
  _pricingOverridesCache = {};
5395
5526
  return {};
5396
5527
  }
@@ -5421,7 +5552,7 @@ function _loadPricingOverrides() {
5421
5552
  }
5422
5553
  _pricingOverridesCache = out;
5423
5554
  } catch {
5424
- _handleStateCorruption3(join5(home, "model-tiers.json"));
5555
+ _handleStateCorruption(join5(home, "model-tiers.json"));
5425
5556
  _pricingOverridesCache = {};
5426
5557
  }
5427
5558
  return _pricingOverridesCache;
@@ -5557,7 +5688,7 @@ function loadSelection2() {
5557
5688
  return DFLT_SEL2;
5558
5689
  const st = statSync5(TIERS_FILE3);
5559
5690
  if (st.size > 10485760) {
5560
- _handleStateCorruption3(TIERS_FILE3);
5691
+ _handleStateCorruption(TIERS_FILE3);
5561
5692
  return DFLT_SEL2;
5562
5693
  }
5563
5694
  const j = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
@@ -5580,7 +5711,7 @@ function loadSelection2() {
5580
5711
  executed_model: j?.selection?.executed_model || null
5581
5712
  };
5582
5713
  } catch {
5583
- _handleStateCorruption3(TIERS_FILE3);
5714
+ _handleStateCorruption(TIERS_FILE3);
5584
5715
  return DFLT_SEL2;
5585
5716
  }
5586
5717
  }
@@ -5808,7 +5939,7 @@ function loadTrinitySlotsFromTiersFile() {
5808
5939
  return false;
5809
5940
  const st = statSync5(TIERS_FILE3);
5810
5941
  if (st.size > 10485760) {
5811
- _handleStateCorruption3(TIERS_FILE3);
5942
+ _handleStateCorruption(TIERS_FILE3);
5812
5943
  return false;
5813
5944
  }
5814
5945
  const tiersData = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8")) || {};
@@ -5874,18 +6005,20 @@ function _refreshModel(directory3) {
5874
6005
  console.error(`[vibeOS] model refresh (config): ${oldModel}(${oldTier}) \u2192 ${currentModel}(${currentTier})`);
5875
6006
  try {
5876
6007
  if (existsSync6(TIERS_FILE3)) {
5877
- const t = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
5878
- for (const s of getTrinitySlotOrder(t)) {
5879
- if (t?.trinity?.[s]?.oc === cfgModel) {
5880
- t.selection.active_slot = s;
5881
- const _tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
5882
- writeFileSync5(_tmp, JSON.stringify(t, null, 2) + "\n", "utf-8");
5883
- renameSync4(_tmp, TIERS_FILE3);
5884
- if (DEBUG_INTERNALS)
5885
- console.error(`[vibeOS] model refresh (config): synced active_slot \u2192 ${s}`);
5886
- break;
6008
+ withFileLock2(TIERS_FILE3, () => {
6009
+ const t = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
6010
+ for (const s of getTrinitySlotOrder(t)) {
6011
+ if (t?.trinity?.[s]?.oc === cfgModel) {
6012
+ t.selection.active_slot = s;
6013
+ const _tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
6014
+ writeFileSync5(_tmp, JSON.stringify(t, null, 2) + "\n", "utf-8");
6015
+ renameSync4(_tmp, TIERS_FILE3);
6016
+ if (DEBUG_INTERNALS)
6017
+ console.error(`[vibeOS] model refresh (config): synced active_slot \u2192 ${s}`);
6018
+ break;
6019
+ }
5887
6020
  }
5888
- }
6021
+ });
5889
6022
  }
5890
6023
  } catch {
5891
6024
  }
@@ -5897,32 +6030,34 @@ function _refreshModel(directory3) {
5897
6030
  function applySlot2(slot, projectDir = "") {
5898
6031
  try {
5899
6032
  const TIERS_FILE3 = join5(getVibeOSHome4(), "model-tiers.json");
5900
- const j = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
5901
- const ocModel = j?.trinity?.[slot]?.oc;
5902
- if (!ocModel)
5903
- return { ok: false, reason: `slot '${slot}' has no oc model` };
5904
- j.selection.active_slot = slot;
5905
- const _tmp = TIERS_FILE3 + ".tmp." + Date.now();
5906
- writeFileSync5(_tmp, JSON.stringify(j, null, 2) + "\n", "utf-8");
5907
- renameSync4(_tmp, TIERS_FILE3);
5908
- const dir = projectDir || process.cwd();
5909
- const localOcConfig = join5(dir, "opencode.json");
5910
- const ocConfig = existsSync6(localOcConfig) ? localOcConfig : join5(getOpenCodeHome(), "opencode.json");
5911
- if (existsSync6(ocConfig)) {
5912
- const oc = safeJsonParse3(readFileSync5(ocConfig, "utf-8"));
5913
- oc.model = ocModel;
5914
- writeFileSync5(ocConfig, JSON.stringify(oc, null, 2) + "\n");
5915
- }
5916
- clearWorkspaceFollowupPauseForSession(getCurrentSessionId());
5917
- _refreshModel(dir);
5918
- return { ok: true, ocModel };
6033
+ return withFileLock2(TIERS_FILE3, () => {
6034
+ const j = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
6035
+ const ocModel = j?.trinity?.[slot]?.oc;
6036
+ if (!ocModel)
6037
+ return { ok: false, reason: `slot '${slot}' has no oc model` };
6038
+ j.selection.active_slot = slot;
6039
+ const _tmp = TIERS_FILE3 + ".tmp." + Date.now();
6040
+ writeFileSync5(_tmp, JSON.stringify(j, null, 2) + "\n", "utf-8");
6041
+ renameSync4(_tmp, TIERS_FILE3);
6042
+ const dir = projectDir || process.cwd();
6043
+ const localOcConfig = join5(dir, "opencode.json");
6044
+ const ocConfig = existsSync6(localOcConfig) ? localOcConfig : join5(getOpenCodeHome(), "opencode.json");
6045
+ if (existsSync6(ocConfig)) {
6046
+ const oc = safeJsonParse3(readFileSync5(ocConfig, "utf-8"));
6047
+ oc.model = ocModel;
6048
+ writeFileSync5(ocConfig, JSON.stringify(oc, null, 2) + "\n");
6049
+ }
6050
+ clearWorkspaceFollowupPauseForSession(getCurrentSessionId());
6051
+ _refreshModel(dir);
6052
+ return { ok: true, ocModel };
6053
+ });
5919
6054
  } catch (err) {
5920
6055
  return { ok: false, reason: err.message };
5921
6056
  }
5922
6057
  }
5923
6058
 
5924
6059
  // src/lib/turn-classify.js
5925
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync8, renameSync as renameSync5 } from "node:fs";
6060
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync7, renameSync as renameSync5 } from "node:fs";
5926
6061
  import { join as join7, dirname as dirname8 } from "node:path";
5927
6062
 
5928
6063
  // src/vibeOS-lib/blackbox/resolution-tracker.js
@@ -7356,8 +7491,8 @@ function projectStructuredFromText(raw, selection, creditPercent = 0) {
7356
7491
  }
7357
7492
 
7358
7493
  // src/lib/reporting.js
7359
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync9, statSync as statSync6, copyFileSync as copyFileSync4, rmSync as rmSync4 } from "node:fs";
7360
- import { join as join9, basename as basename5 } from "node:path";
7494
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync8, statSync as statSync6, rmSync as rmSync4 } from "node:fs";
7495
+ import { join as join9 } from "node:path";
7361
7496
  function getVibeOSHome7() {
7362
7497
  return process.env.VIBEOS_HOME || join9(process.env.HOME || "", ".claude");
7363
7498
  }
@@ -7372,27 +7507,18 @@ var REPORTS_INDEX = getReportsIndexPath();
7372
7507
  var currentProjectFingerprint2 = "";
7373
7508
  var currentProjectName2 = "";
7374
7509
  var currentSessionId2 = "";
7375
- function _handleStateCorruption4(path) {
7376
- const backupDir = join9(getVibeOSHome7(), ".backups");
7377
- mkdirSync9(backupDir, { recursive: true });
7378
- const backupPath = join9(backupDir, basename5(path) + ".corrupted." + Date.now());
7379
- try {
7380
- copyFileSync4(path, backupPath);
7381
- } catch {
7382
- }
7383
- }
7384
7510
  function readJsonOrEmpty2(filePath) {
7385
7511
  try {
7386
7512
  if (!existsSync11(filePath))
7387
7513
  return {};
7388
7514
  const st = statSync6(filePath);
7389
7515
  if (st.size > 10485760) {
7390
- _handleStateCorruption4(filePath);
7516
+ _handleStateCorruption(filePath);
7391
7517
  return {};
7392
7518
  }
7393
7519
  return safeJsonParse3(readFileSync10(filePath, "utf-8"));
7394
7520
  } catch {
7395
- _handleStateCorruption4(filePath);
7521
+ _handleStateCorruption(filePath);
7396
7522
  return {};
7397
7523
  }
7398
7524
  }
@@ -7407,7 +7533,7 @@ function saveReportsIndex(idx) {
7407
7533
  const reportsIndexPath = getReportsIndexPath();
7408
7534
  const reportsDir = getReportsDir();
7409
7535
  withFileLock(reportsIndexPath, () => {
7410
- mkdirSync9(reportsDir, { recursive: true });
7536
+ mkdirSync8(reportsDir, { recursive: true });
7411
7537
  writeFileSync9(reportsIndexPath, JSON.stringify(idx, null, 2) + "\n");
7412
7538
  });
7413
7539
  } catch (err) {
@@ -7525,7 +7651,7 @@ function saveReport({ type = "manual", summary = "", findings = null, metrics =
7525
7651
  const reportsIndexPath = getReportsIndexPath();
7526
7652
  const reportsDir = getReportsDir();
7527
7653
  withFileLock(reportsIndexPath, () => {
7528
- mkdirSync9(reportsDir, { recursive: true });
7654
+ mkdirSync8(reportsDir, { recursive: true });
7529
7655
  writeFileSync9(join9(reportsDir, `${id2}.json`), JSON.stringify(report, null, 2) + "\n");
7530
7656
  const idx = reportsIndex();
7531
7657
  const _sum = (summary || "").slice(0, 80);
@@ -7888,6 +8014,14 @@ var RAW_MODE = {
7888
8014
  desc: "Pure v4 Pro baseline. No vibeOS overhead."
7889
8015
  };
7890
8016
  var ALL_MODES = [...BRANDED_MODES, ...RUNTIME_MODES, RAW_MODE];
8017
+ function resolveCascadeSlot(pipeline = []) {
8018
+ const normalized = Array.isArray(pipeline) ? pipeline.map((t) => String(t || "").toLowerCase()) : [];
8019
+ if (normalized.includes("brain"))
8020
+ return "brain";
8021
+ if (normalized.includes("medium"))
8022
+ return "medium";
8023
+ return "cheap";
8024
+ }
7891
8025
 
7892
8026
  // src/lib/trinity-tool.js
7893
8027
  var MIN_TOOL_BREAKDOWN_THRESHOLD = 5e-3;
@@ -8171,8 +8305,7 @@ function createTrinityTool(deps) {
8171
8305
  const allEntries = [...BRANDED_MODES, ...RUNTIME_MODES];
8172
8306
  const modeEntry = allEntries.find((e) => e.id === slot);
8173
8307
  if (modeEntry) {
8174
- const rawTier = modeEntry.pipeline[0] || "cheap";
8175
- const tierSlot = (/* @__PURE__ */ new Set(["brain", "medium", "cheap"])).has(rawTier) ? rawTier : "cheap";
8308
+ const tierSlot = resolveCascadeSlot(modeEntry.pipeline);
8176
8309
  deps.writeSessionSlot(deps._OC_SID, tierSlot);
8177
8310
  deps.writeSelection("slot_locked", resolvedSlot !== "auto");
8178
8311
  deps.writeSelection("active_slot", tierSlot);
@@ -9505,12 +9638,12 @@ async function probeModel(modelId, auth, providers = null) {
9505
9638
  }
9506
9639
 
9507
9640
  // src/lib/hooks/footer.js
9508
- import { readFileSync as readFileSync14, appendFileSync as appendFileSync6, mkdirSync as mkdirSync11 } from "node:fs";
9641
+ import { readFileSync as readFileSync14, appendFileSync as appendFileSync4, mkdirSync as mkdirSync10 } from "node:fs";
9509
9642
  import { join as join15 } from "node:path";
9510
9643
 
9511
9644
  // src/lib/hooks/chat-transform.js
9512
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, appendFileSync as appendFileSync5, existsSync as existsSync14, mkdirSync as mkdirSync10, rmSync as rmSync5, readdirSync as readdirSync3, statSync as statSync7 } from "node:fs";
9513
- import { join as join14, dirname as dirname10, basename as basename6 } from "node:path";
9645
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, appendFileSync as appendFileSync3, existsSync as existsSync14, mkdirSync as mkdirSync9, rmSync as rmSync5, readdirSync as readdirSync3, statSync as statSync7 } from "node:fs";
9646
+ import { join as join14, dirname as dirname10, basename as basename3 } from "node:path";
9514
9647
  import { createHash as createHash3 } from "node:crypto";
9515
9648
 
9516
9649
  // src/lib/mode-policy.js
@@ -10297,14 +10430,14 @@ function observeUserCorrection(text) {
10297
10430
  }
10298
10431
  }
10299
10432
  function buildProjectBriefing(directory3) {
10300
- const label = currentProjectName || (directory3 ? basename6(directory3) : "");
10433
+ const label = currentProjectName || (directory3 ? basename3(directory3) : "");
10301
10434
  if (!label)
10302
10435
  return null;
10303
10436
  return `[project memory] Active project: ${label}. Stay focused on the current repository and prefer the existing workflow.`;
10304
10437
  }
10305
10438
  function ensureProjectSkill(dir, fp2) {
10306
10439
  const skillsDir = join14(dir, ".opencode", "skills");
10307
- const projectName = basename6(dir);
10440
+ const projectName = basename3(dir);
10308
10441
  const skillDir = join14(skillsDir, projectName);
10309
10442
  const skillPath = join14(skillDir, "SKILL.md");
10310
10443
  if (existsSync14(skillPath)) {
@@ -10369,7 +10502,7 @@ function ensureProjectSkill(dir, fp2) {
10369
10502
  content += "\n";
10370
10503
  }
10371
10504
  try {
10372
- mkdirSync10(skillDir, { recursive: true });
10505
+ mkdirSync9(skillDir, { recursive: true });
10373
10506
  writeFileSync12(skillPath, content, "utf-8");
10374
10507
  console.error(`[vibeOS] Project Guard: created .opencode/skills/${projectName}/SKILL.md`);
10375
10508
  return { created: true, path: skillPath, skipped: false };
@@ -10528,7 +10661,7 @@ ${raw}
10528
10661
  const sessPath = join14(getSessionScratchpadDir(), `${hash}.txt`);
10529
10662
  const globalPath = join14(globalDir, `${hash}.txt`);
10530
10663
  try {
10531
- mkdirSync10(globalDir, { recursive: true });
10664
+ mkdirSync9(globalDir, { recursive: true });
10532
10665
  ensureSessionScratchpadDirs();
10533
10666
  if (!existsSync14(globalPath)) {
10534
10667
  writeFileSync12(globalPath, raw);
@@ -10941,8 +11074,8 @@ var onSystemTransform = async (_input, output) => {
10941
11074
  fp: currentProjectFingerprint || ""
10942
11075
  }) + "\n";
10943
11076
  try {
10944
- mkdirSync10(calDir, { recursive: true });
10945
- appendFileSync5(calFile, calRecord);
11077
+ mkdirSync9(calDir, { recursive: true });
11078
+ appendFileSync3(calFile, calRecord);
10946
11079
  } catch {
10947
11080
  }
10948
11081
  if (!oneShot("vibeos_dashboard_instruct")) {
@@ -11358,8 +11491,8 @@ ${vibeLine}`;
11358
11491
  tracker.recordOutcome(finalOutcome);
11359
11492
  syncOutcomeToApi(finalOutcome);
11360
11493
  try {
11361
- mkdirSync11(getVibeOSHome10(), { recursive: true });
11362
- appendFileSync6(join15(getVibeOSHome10(), "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: getSessionId(), outcome: finalOutcome }) + "\n");
11494
+ mkdirSync10(getVibeOSHome10(), { recursive: true });
11495
+ appendFileSync4(join15(getVibeOSHome10(), "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: getSessionId(), outcome: finalOutcome }) + "\n");
11363
11496
  } catch {
11364
11497
  }
11365
11498
  }
@@ -11385,8 +11518,8 @@ ${vibeLine} \u2014`);
11385
11518
  }
11386
11519
 
11387
11520
  // src/lib/hooks/tool-execute.js
11388
- import { writeFileSync as writeFileSync14, appendFileSync as appendFileSync8, existsSync as existsSync16, mkdirSync as mkdirSync13 } from "node:fs";
11389
- import { join as join17, dirname as dirname12, basename as basename7 } from "node:path";
11521
+ import { writeFileSync as writeFileSync14, appendFileSync as appendFileSync6, existsSync as existsSync16, mkdirSync as mkdirSync12 } from "node:fs";
11522
+ import { join as join17, dirname as dirname12, basename as basename4 } from "node:path";
11390
11523
  import { createHash as createHash5 } from "node:crypto";
11391
11524
 
11392
11525
  // src/lib/cost-anomaly.js
@@ -11450,7 +11583,7 @@ function getCostAnomalyDetector() {
11450
11583
  init_flow_enforcer();
11451
11584
 
11452
11585
  // src/lib/tdd-enforcer.js
11453
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync13, appendFileSync as appendFileSync7, existsSync as existsSync15, mkdirSync as mkdirSync12, statSync as statSync8, readdirSync as readdirSync4, rmSync as rmSync6, openSync as openSync3 } from "node:fs";
11586
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync13, appendFileSync as appendFileSync5, existsSync as existsSync15, mkdirSync as mkdirSync11, statSync as statSync8, readdirSync as readdirSync4, rmSync as rmSync6, openSync as openSync3 } from "node:fs";
11454
11587
  import { join as join16, dirname as dirname11 } from "node:path";
11455
11588
  import { createHash as createHash4 } from "node:crypto";
11456
11589
 
@@ -12553,7 +12686,7 @@ var COOLDOWN_MS = 6e4;
12553
12686
  var _enforcementCooldown = /* @__PURE__ */ new Set();
12554
12687
  function _acquireLock(testPath) {
12555
12688
  try {
12556
- mkdirSync12(ENFORCEMENT_LOCK_DIR, { recursive: true });
12689
+ mkdirSync11(ENFORCEMENT_LOCK_DIR, { recursive: true });
12557
12690
  const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
12558
12691
  const lockPath = join16(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
12559
12692
  try {
@@ -12610,10 +12743,10 @@ function _isInCooldown(testPath) {
12610
12743
  }
12611
12744
  function _recordCooldown(testPath) {
12612
12745
  try {
12613
- mkdirSync12(dirname11(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
12746
+ mkdirSync11(dirname11(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
12614
12747
  const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
12615
12748
  const entry = JSON.stringify({ h: hash, ts: Date.now() }) + "\n";
12616
- appendFileSync7(ENFORCEMENT_COOLDOWN_FILE2, entry);
12749
+ appendFileSync5(ENFORCEMENT_COOLDOWN_FILE2, entry);
12617
12750
  const lines = readFileSync15(ENFORCEMENT_COOLDOWN_FILE2, "utf-8").trim().split("\n").filter(Boolean);
12618
12751
  if (lines.length > 500) {
12619
12752
  writeFileSync13(ENFORCEMENT_COOLDOWN_FILE2, lines.slice(-200).join("\n") + "\n");
@@ -12702,7 +12835,7 @@ function enforceTestFile(filePath) {
12702
12835
  if (!_acquireLock(skeleton.path))
12703
12836
  return null;
12704
12837
  try {
12705
- mkdirSync12(skeleton.dir, { recursive: true });
12838
+ mkdirSync11(skeleton.dir, { recursive: true });
12706
12839
  writeFileSync13(skeleton.path, skeleton.content);
12707
12840
  _enforcementCooldown.add(skeleton.path);
12708
12841
  _recordCooldown(skeleton.path);
@@ -12928,7 +13061,7 @@ function _isProtectedToolPath(pathValue) {
12928
13061
  }
12929
13062
  function _mutateBlockedToolArgs(toolName, sources, blockedPath, outputObj) {
12930
13063
  const tLower = String(toolName || "").toLowerCase();
12931
- const blockedBase = basename7(blockedPath || "") || "blocked";
13064
+ const blockedBase = basename4(blockedPath || "") || "blocked";
12932
13065
  for (const src of sources) {
12933
13066
  if (!src || typeof src !== "object")
12934
13067
  continue;
@@ -13237,7 +13370,7 @@ ${argsJson}
13237
13370
  _mutateBlockedToolArgs(t, argSources, checkPath, output);
13238
13371
  if (shouldLogWarn(`${t}|protect|${checkPath}`))
13239
13372
  console.error(`[vibeOS] [protection] BLOCKED direct ${t} in self-protected directory: ${checkPath}`);
13240
- pendingUiNote = `[LOCK] Self-modification paused: ${basename7(checkPath)} is in a protected project tree. Use a manual git workflow.`;
13373
+ pendingUiNote = `[LOCK] Self-modification paused: ${basename4(checkPath)} is in a protected project tree. Use a manual git workflow.`;
13241
13374
  enforcementBlocked = true;
13242
13375
  return;
13243
13376
  }
@@ -13275,7 +13408,7 @@ ${argsJson}
13275
13408
  const tLower = String(t || "").toLowerCase();
13276
13409
  if (!compatibilityMode && sel.delegation_enforce && currentTier === "high" && argSources.length > 0) {
13277
13410
  const originalPath = argSources.flatMap((src) => [src?.filePath, src?.file_path, src?.path]).find((v) => typeof v === "string" && v.trim()) || "";
13278
- const basename9 = originalPath.split("/").pop() || "blocked";
13411
+ const basename6 = originalPath.split("/").pop() || "blocked";
13279
13412
  const apiResult = await remoteCall("delegateCheck", [tLower, currentTier, currentModel, _prompt], () => ({
13280
13413
  blocked: true,
13281
13414
  savings: _estEdit
@@ -13315,7 +13448,7 @@ ${argsJson}
13315
13448
  const missed = recordMissedContext7(_estC7);
13316
13449
  if (!existsSync16(CONTEXT7_INSTALL_FLAG)) {
13317
13450
  try {
13318
- mkdirSync13(dirname12(CONTEXT7_INSTALL_FLAG), { recursive: true });
13451
+ mkdirSync12(dirname12(CONTEXT7_INSTALL_FLAG), { recursive: true });
13319
13452
  writeFileSync14(CONTEXT7_INSTALL_FLAG, "");
13320
13453
  } catch {
13321
13454
  }
@@ -13501,7 +13634,7 @@ var onToolExecuteAfter = async (input, output) => {
13501
13634
  const taskPrompt = input?.args?.prompt || input?.args?.description || "";
13502
13635
  const quality = scoreTaskQuality(taskOutput, taskPrompt);
13503
13636
  try {
13504
- appendFileSync8(SAVINGS_LEDGER_FILE, JSON.stringify({
13637
+ appendFileSync6(SAVINGS_LEDGER_FILE, JSON.stringify({
13505
13638
  at: (/* @__PURE__ */ new Date()).toISOString(),
13506
13639
  kind: "quality",
13507
13640
  score: quality,
@@ -13667,7 +13800,7 @@ ${pendingUiNote}`;
13667
13800
  if (guardRe.test(fp3)) {
13668
13801
  const guardIcons = { flag: "!", warn: "!!", hint: "_" };
13669
13802
  const guardIcon = guardIcons.flag || "!";
13670
- const fn = basename7(fp3);
13803
+ const fn = basename4(fp3);
13671
13804
  console.error(`[flow-enforcer] ${guardIcon} [guard] ${fn}: protected project doc modified \u2014 verify user intent`);
13672
13805
  }
13673
13806
  }
@@ -13975,7 +14108,7 @@ async function _seedModelTiersIfMissing(directory3) {
13975
14108
  cheap: { oc: cheap, cc: modelToCcAlias(cheap) }
13976
14109
  }
13977
14110
  };
13978
- mkdirSync14(dirname13(TIERS_FILE3), { recursive: true });
14111
+ mkdirSync13(dirname13(TIERS_FILE3), { recursive: true });
13979
14112
  writeFileSync15(TIERS_FILE3, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
13980
14113
  return true;
13981
14114
  }
@@ -14044,7 +14177,7 @@ function persistMcpPort(port) {
14044
14177
  tiers.selection.mcp_port = port;
14045
14178
  if ("mcp_port" in tiers)
14046
14179
  delete tiers.mcp_port;
14047
- mkdirSync14(dirname13(getTiersFile()), { recursive: true });
14180
+ mkdirSync13(dirname13(getTiersFile()), { recursive: true });
14048
14181
  const tmp = getTiersFile() + ".tmp." + Date.now();
14049
14182
  writeFileSync15(tmp, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
14050
14183
  renameSync6(tmp, getTiersFile());
@@ -14135,7 +14268,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14135
14268
  };
14136
14269
  const saveProjectStateStable = (state) => {
14137
14270
  try {
14138
- mkdirSync14(dirname13(hookProjectStateFile), { recursive: true });
14271
+ mkdirSync13(dirname13(hookProjectStateFile), { recursive: true });
14139
14272
  const tmp = hookProjectStateFile + ".tmp";
14140
14273
  writeFileSync15(tmp, JSON.stringify(state, null, 2) + "\n");
14141
14274
  renameSync6(tmp, hookProjectStateFile);
@@ -14154,7 +14287,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14154
14287
  };
14155
14288
  const saveReportsIndexStable = (idx) => {
14156
14289
  try {
14157
- mkdirSync14(hookReportsDir, { recursive: true });
14290
+ mkdirSync13(hookReportsDir, { recursive: true });
14158
14291
  writeFileSync15(hookReportsIndex, JSON.stringify(idx, null, 2) + "\n");
14159
14292
  } catch {
14160
14293
  }
@@ -14164,9 +14297,9 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14164
14297
  if (!existsSync18(path))
14165
14298
  return null;
14166
14299
  const bkDir = join18(hookVibeHome, ".backups");
14167
- mkdirSync14(bkDir, { recursive: true });
14168
- const bk = join18(bkDir, `${basename8(path)}.${label}.${Date.now()}.bak`);
14169
- copyFileSync5(path, bk);
14300
+ mkdirSync13(bkDir, { recursive: true });
14301
+ const bk = join18(bkDir, `${basename5(path)}.${label}.${Date.now()}.bak`);
14302
+ copyFileSync2(path, bk);
14170
14303
  return bk;
14171
14304
  } catch {
14172
14305
  return null;
@@ -14204,7 +14337,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14204
14337
  writeFileSync: writeFileSync15,
14205
14338
  existsSync: existsSync18,
14206
14339
  renameSync: renameSync6,
14207
- mkdirSync: mkdirSync14,
14340
+ mkdirSync: mkdirSync13,
14208
14341
  get TIERS_FILE() {
14209
14342
  return hookTiersFile;
14210
14343
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.24.15",
3
+ "version": "0.24.17",
4
4
  "description": "Cost-aware delegation enforcer for OpenCode. Tracks model usage, routes Task subagents to cheaper tiers, surfaces cumulative savings in chat. Includes research audit, reporting framework, project memory, progressive scratchpad decadence, and trinity CLI for brain/medium/cheap slot switching.",
5
5
  "scripts": {
6
6
  "release": "node scripts/release.mjs",