vibeostheog 0.24.18 → 0.24.21

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,20 @@
1
+ ## 0.24.21
2
+ - feat: pass user_text to RF prediction engine via control-vector API (#146)
3
+ - feat: pass user_text to RF prediction engine via control-vector API
4
+ - fix: extend ci test timeout
5
+ - fix: use EMBEDDED_API_TOKEN as bootstrap token fallback instead of direct API token (#148)
6
+ - fix: repair reload recovery and fallback mode state (#147)
7
+ - test: cover cold start maintenance user flow (#144)
8
+ - chore: v0.24.20
9
+ - chore: v0.24.19 (#145)
10
+ Prune stale active jobs and stabilize report tests
11
+
12
+
13
+ ## 0.24.19
14
+ - test: cover cold start maintenance user flow (#144)
15
+ Prune stale active jobs and stabilize report tests
16
+
17
+
1
18
  ## 0.24.18
2
19
  - fix: keep Return codename through 0.24 patch releases (#143)
3
20
  - fix: preserve live metrics context in reports (#137)
package/dist/vibeOS.js CHANGED
@@ -1104,7 +1104,7 @@ 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 mkdirSync13, copyFileSync as copyFileSync2, renameSync as renameSync6 } from "node:fs";
1107
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync15, existsSync as existsSync18, mkdirSync as mkdirSync13, copyFileSync as copyFileSync2, renameSync as renameSync6, statSync as statSync9 } from "node:fs";
1108
1108
  import { join as join18, dirname as dirname13, basename as basename5 } from "node:path";
1109
1109
 
1110
1110
  // src/vibeOS-lib/session-metrics.js
@@ -2117,8 +2117,8 @@ function readBootstrapTokenFromDisk() {
2117
2117
  return "";
2118
2118
  }
2119
2119
  var VIBEOS_API_DISABLED = readApiDisabledFromDisk() || isTruthyFlag(process.env.VIBEOS_API_DISABLED);
2120
- var VIBEOS_API_TOKEN = VIBEOS_API_DISABLED ? "" : readTokenFromDisk() || normalizeApiToken(process.env.VIBEOS_API_TOKEN, "") || EMBEDDED_API_TOKEN;
2121
- var VIBEOS_API_BOOTSTRAP_TOKEN = VIBEOS_API_DISABLED ? "" : readBootstrapTokenFromDisk() || process.env.VIBEOS_API_BOOTSTRAP_TOKEN || "";
2120
+ var VIBEOS_API_TOKEN = VIBEOS_API_DISABLED ? "" : readTokenFromDisk() || normalizeApiToken(process.env.VIBEOS_API_TOKEN, "");
2121
+ var VIBEOS_API_BOOTSTRAP_TOKEN = VIBEOS_API_DISABLED ? "" : readBootstrapTokenFromDisk() || process.env.VIBEOS_API_BOOTSTRAP_TOKEN || EMBEDDED_API_TOKEN;
2122
2122
  var VIBEOS_API_ENABLED = !VIBEOS_API_DISABLED && process.env.VIBEOS_API_ENABLED !== "false" && (!!VIBEOS_API_TOKEN || !!VIBEOS_API_BOOTSTRAP_TOKEN);
2123
2123
  var _anomalyDetector = null;
2124
2124
  function getAnomalyDetector() {
@@ -2284,7 +2284,7 @@ function syncApiTokenFromDisk() {
2284
2284
  console.error("[vibeOS] API token loaded from VIBEOS_API_TOKEN env var");
2285
2285
  } else {
2286
2286
  VIBEOS_API_DISABLED = false;
2287
- VIBEOS_API_TOKEN ||= EMBEDDED_API_TOKEN;
2287
+ VIBEOS_API_BOOTSTRAP_TOKEN ||= EMBEDDED_API_TOKEN;
2288
2288
  VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && (!!VIBEOS_API_TOKEN || !!VIBEOS_API_BOOTSTRAP_TOKEN);
2289
2289
  }
2290
2290
  }
@@ -2299,7 +2299,7 @@ function getApiClient2() {
2299
2299
  }
2300
2300
  return _apiClient;
2301
2301
  }
2302
- function isApiFallback() {
2302
+ function isApiFallback2() {
2303
2303
  return _apiFallbackMode || !VIBEOS_API_ENABLED;
2304
2304
  }
2305
2305
  function isApiConnected2() {
@@ -2403,7 +2403,7 @@ function loadSelection() {
2403
2403
  return DFLT_SEL;
2404
2404
  const st = statSync3(TIERS_FILE3);
2405
2405
  if (st.size > 10485760) {
2406
- _handleStateCorruption(TIERS_FILE3);
2406
+ _handleStateCorruption2(TIERS_FILE3);
2407
2407
  return DFLT_SEL;
2408
2408
  }
2409
2409
  const j = safeJsonParse2(readFileSync3(TIERS_FILE3, "utf-8"));
@@ -2430,7 +2430,7 @@ function loadSelection() {
2430
2430
  previous_default_agent: j?.selection?.previous_default_agent || null
2431
2431
  };
2432
2432
  } catch {
2433
- _handleStateCorruption(TIERS_FILE3);
2433
+ _handleStateCorruption2(TIERS_FILE3);
2434
2434
  return DFLT_SEL;
2435
2435
  }
2436
2436
  }
@@ -3453,7 +3453,7 @@ function runStartupMaintenanceOnce() {
3453
3453
  } catch {
3454
3454
  }
3455
3455
  }
3456
- function _handleStateCorruption(path) {
3456
+ function _handleStateCorruption2(path) {
3457
3457
  const backupDir = join4(VIBEOS_HOME, ".backups");
3458
3458
  mkdirSync3(backupDir, { recursive: true });
3459
3459
  const backupPath = join4(backupDir, basename2(path) + ".corrupted." + Date.now());
@@ -3558,12 +3558,12 @@ function readJsonOrEmpty(filePath) {
3558
3558
  return {};
3559
3559
  const st = statSync4(filePath);
3560
3560
  if (st.size > 10485760) {
3561
- _handleStateCorruption(filePath);
3561
+ _handleStateCorruption2(filePath);
3562
3562
  return {};
3563
3563
  }
3564
3564
  return safeJsonParse3(readFileSync4(filePath, "utf-8"));
3565
3565
  } catch {
3566
- _handleStateCorruption(filePath);
3566
+ _handleStateCorruption2(filePath);
3567
3567
  return {};
3568
3568
  }
3569
3569
  }
@@ -3614,12 +3614,12 @@ function readFullState() {
3614
3614
  return {};
3615
3615
  const st = statSync4(delegationStateFile);
3616
3616
  if (st.size > 10485760) {
3617
- _handleStateCorruption(delegationStateFile);
3617
+ _handleStateCorruption2(delegationStateFile);
3618
3618
  return {};
3619
3619
  }
3620
3620
  return safeJsonParse3(readFileSync4(delegationStateFile, "utf-8"));
3621
3621
  } catch {
3622
- _handleStateCorruption(delegationStateFile);
3622
+ _handleStateCorruption2(delegationStateFile);
3623
3623
  return {};
3624
3624
  }
3625
3625
  }
@@ -3659,7 +3659,7 @@ function loadGlobalLearning() {
3659
3659
  return DFLT_GL;
3660
3660
  const st = statSync4(globalLearningFile);
3661
3661
  if (st.size > 10485760) {
3662
- _handleStateCorruption(globalLearningFile);
3662
+ _handleStateCorruption2(globalLearningFile);
3663
3663
  return DFLT_GL;
3664
3664
  }
3665
3665
  const j = safeJsonParse3(readFileSync4(globalLearningFile, "utf-8"));
@@ -3672,7 +3672,7 @@ function loadGlobalLearning() {
3672
3672
  j.context7_last_seen ??= null;
3673
3673
  return j;
3674
3674
  } catch {
3675
- _handleStateCorruption(globalLearningFile);
3675
+ _handleStateCorruption2(globalLearningFile);
3676
3676
  return DFLT_GL;
3677
3677
  }
3678
3678
  }
@@ -3734,7 +3734,7 @@ function loadBlackboxState() {
3734
3734
  return { enabled: true, sessions: {} };
3735
3735
  const st = statSync4(blackboxFile);
3736
3736
  if (st.size > 10485760) {
3737
- _handleStateCorruption(blackboxFile);
3737
+ _handleStateCorruption2(blackboxFile);
3738
3738
  return { enabled: false, sessions: {} };
3739
3739
  }
3740
3740
  const raw = safeJsonParse3(readFileSync4(blackboxFile, "utf-8")) || { enabled: false, sessions: {} };
@@ -3774,7 +3774,7 @@ function loadBlackboxState() {
3774
3774
  }
3775
3775
  return raw;
3776
3776
  } catch {
3777
- _handleStateCorruption(blackboxFile);
3777
+ _handleStateCorruption2(blackboxFile);
3778
3778
  return { enabled: false, sessions: {} };
3779
3779
  }
3780
3780
  }
@@ -4281,7 +4281,7 @@ function _readActiveJobsRaw() {
4281
4281
  const raw = safeJsonParse3(readFileSync4(ACTIVE_JOBS_FILE, "utf-8"));
4282
4282
  return raw && typeof raw === "object" ? raw : {};
4283
4283
  } catch {
4284
- _handleStateCorruption(ACTIVE_JOBS_FILE);
4284
+ _handleStateCorruption2(ACTIVE_JOBS_FILE);
4285
4285
  return {};
4286
4286
  }
4287
4287
  }
@@ -4337,7 +4337,7 @@ function loadActiveJobs() {
4337
4337
  const now = Date.now();
4338
4338
  for (const [key, value] of Object.entries(raw || {})) {
4339
4339
  const norm = _normalizeActiveJobRecord(value, now, true);
4340
- if (!norm.record) {
4340
+ if (!norm.record || norm.stale && norm.record.status === "completed" && norm.record.completedAt) {
4341
4341
  changed = true;
4342
4342
  continue;
4343
4343
  }
@@ -4350,7 +4350,7 @@ function loadActiveJobs() {
4350
4350
  return next;
4351
4351
  });
4352
4352
  } catch {
4353
- _handleStateCorruption(ACTIVE_JOBS_FILE);
4353
+ _handleStateCorruption2(ACTIVE_JOBS_FILE);
4354
4354
  return {};
4355
4355
  }
4356
4356
  }
@@ -5543,7 +5543,7 @@ function _loadDynamicPricingCache() {
5543
5543
  return {};
5544
5544
  const st = statSync5(PRICING_CACHE_FILE2);
5545
5545
  if (st.size > 10485760) {
5546
- _handleStateCorruption(PRICING_CACHE_FILE2);
5546
+ _handleStateCorruption2(PRICING_CACHE_FILE2);
5547
5547
  _dynamicPricingCache = {};
5548
5548
  return {};
5549
5549
  }
@@ -5551,7 +5551,7 @@ function _loadDynamicPricingCache() {
5551
5551
  const map = raw?.models && typeof raw.models === "object" ? raw.models : {};
5552
5552
  _dynamicPricingCache = map;
5553
5553
  } catch {
5554
- _handleStateCorruption(PRICING_CACHE_FILE2);
5554
+ _handleStateCorruption2(PRICING_CACHE_FILE2);
5555
5555
  _dynamicPricingCache = {};
5556
5556
  }
5557
5557
  return _dynamicPricingCache;
@@ -5646,7 +5646,7 @@ function _loadPricingOverrides() {
5646
5646
  return {};
5647
5647
  const st = statSync5(tiersFile);
5648
5648
  if (st.size > 10485760) {
5649
- _handleStateCorruption(tiersFile);
5649
+ _handleStateCorruption2(tiersFile);
5650
5650
  _pricingOverridesCache = {};
5651
5651
  return {};
5652
5652
  }
@@ -5677,7 +5677,7 @@ function _loadPricingOverrides() {
5677
5677
  }
5678
5678
  _pricingOverridesCache = out;
5679
5679
  } catch {
5680
- _handleStateCorruption(join5(home, "model-tiers.json"));
5680
+ _handleStateCorruption2(join5(home, "model-tiers.json"));
5681
5681
  _pricingOverridesCache = {};
5682
5682
  }
5683
5683
  return _pricingOverridesCache;
@@ -5813,7 +5813,7 @@ function loadSelection2() {
5813
5813
  return DFLT_SEL2;
5814
5814
  const st = statSync5(TIERS_FILE3);
5815
5815
  if (st.size > 10485760) {
5816
- _handleStateCorruption(TIERS_FILE3);
5816
+ _handleStateCorruption2(TIERS_FILE3);
5817
5817
  return DFLT_SEL2;
5818
5818
  }
5819
5819
  const j = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
@@ -5836,7 +5836,7 @@ function loadSelection2() {
5836
5836
  executed_model: j?.selection?.executed_model || null
5837
5837
  };
5838
5838
  } catch {
5839
- _handleStateCorruption(TIERS_FILE3);
5839
+ _handleStateCorruption2(TIERS_FILE3);
5840
5840
  return DFLT_SEL2;
5841
5841
  }
5842
5842
  }
@@ -6064,7 +6064,7 @@ function loadTrinitySlotsFromTiersFile() {
6064
6064
  return false;
6065
6065
  const st = statSync5(TIERS_FILE3);
6066
6066
  if (st.size > 10485760) {
6067
- _handleStateCorruption(TIERS_FILE3);
6067
+ _handleStateCorruption2(TIERS_FILE3);
6068
6068
  return false;
6069
6069
  }
6070
6070
  const tiersData = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8")) || {};
@@ -6774,7 +6774,7 @@ function classifyTurnSimple2(userText) {
6774
6774
  async function classifyTurnRemote(text) {
6775
6775
  try {
6776
6776
  const client2 = getApiClient2();
6777
- if (!client2 || isApiFallback())
6777
+ if (!client2 || isApiFallback2())
6778
6778
  return classifyTurnSimple(text);
6779
6779
  const res = await client2.classifyQuery(text);
6780
6780
  if (res && typeof res === "object" && "sub_regime" in res) {
@@ -6805,7 +6805,7 @@ function resolveOptimizationMode(subRegime, stressMultiplier, optimizationMode)
6805
6805
  const normalized = String(optimizationMode || "auto").toLowerCase();
6806
6806
  if (normalized === "auto" || normalized === "")
6807
6807
  return autoSelectMode2(subRegime || "INIT", stressMultiplier);
6808
- if (isApiFallback())
6808
+ if (isApiFallback2())
6809
6809
  return "vibelitex";
6810
6810
  if (normalized === "balanced" || normalized === "budget" || normalized === "quality" || normalized === "speed" || normalized === "longrun" || normalized === "audit" || normalized === "forensic" || normalized === "vibeultrax" || normalized === "vibeqmax" || normalized === "vibemax" || normalized === "vibelitex") {
6811
6811
  return normalized;
@@ -6817,7 +6817,7 @@ async function selectOptimizationModeRemote(subRegime, stressMultiplier, fallbac
6817
6817
  const fallback2 = resolveOptimizationMode(subRegime, stressMultiplier, fallbackMode);
6818
6818
  if (normalizedRequestedMode !== "auto" && normalizedRequestedMode !== "")
6819
6819
  return fallback2;
6820
- if (isApiFallback())
6820
+ if (isApiFallback2())
6821
6821
  return fallback2;
6822
6822
  try {
6823
6823
  const client2 = getApiClient2();
@@ -7085,7 +7085,7 @@ function resolveEnforcementMode() {
7085
7085
  async function syncOutcomeToApi(outcome) {
7086
7086
  try {
7087
7087
  const client2 = getApiClient2();
7088
- if (!client2 || isApiFallback())
7088
+ if (!client2 || isApiFallback2())
7089
7089
  return;
7090
7090
  await client2.blackboxOutcome(_OC_SID, outcome);
7091
7091
  } catch {
@@ -7094,7 +7094,7 @@ async function syncOutcomeToApi(outcome) {
7094
7094
  async function fetchBlackboxEnrichment(sessionId, localState) {
7095
7095
  try {
7096
7096
  const client2 = getApiClient2();
7097
- if (!client2 || isApiFallback())
7097
+ if (!client2 || isApiFallback2())
7098
7098
  return null;
7099
7099
  const result = await client2.blackboxAnalyze(sessionId, {
7100
7100
  userText: "",
@@ -7638,12 +7638,12 @@ function readJsonOrEmpty2(filePath) {
7638
7638
  return {};
7639
7639
  const st = statSync6(filePath);
7640
7640
  if (st.size > 10485760) {
7641
- _handleStateCorruption(filePath);
7641
+ _handleStateCorruption2(filePath);
7642
7642
  return {};
7643
7643
  }
7644
7644
  return safeJsonParse3(readFileSync10(filePath, "utf-8"));
7645
7645
  } catch {
7646
- _handleStateCorruption(filePath);
7646
+ _handleStateCorruption2(filePath);
7647
7647
  return {};
7648
7648
  }
7649
7649
  }
@@ -10565,7 +10565,11 @@ async function apiComputeControlVector(state, action, optimizationMode) {
10565
10565
  const res = await remoteCall("blackboxControlVector", [state, action, optimizationMode], null);
10566
10566
  if (res?.control_vector) {
10567
10567
  const local = computeControlVector2(state, action, optimizationMode);
10568
- return mergeRemoteControlVector(res.control_vector, local);
10568
+ const merged = mergeRemoteControlVector(res.control_vector, local);
10569
+ if (res.rf_prediction?.mode && res.rf_prediction.mode !== res.control_vector?.optimization_mode) {
10570
+ merged.optimization_mode = res.rf_prediction.mode;
10571
+ }
10572
+ return merged;
10569
10573
  }
10570
10574
  } catch {
10571
10575
  }
@@ -10749,7 +10753,8 @@ function syncControlSettings(cv, options = {}) {
10749
10753
  writeIf("thinking_level", nextThinking);
10750
10754
  }
10751
10755
  if (persistOptimizationMode && cv.optimization_mode && userOptMode !== "auto") {
10752
- if (userOptMode !== cv.optimization_mode) {
10756
+ const fallbackPinned = isApiFallback() && cv.optimization_mode === "vibelitex" && currentSel.optimization_mode !== "vibelitex";
10757
+ if (!fallbackPinned && userOptMode !== cv.optimization_mode) {
10753
10758
  writeIf("optimization_mode", cv.optimization_mode);
10754
10759
  }
10755
10760
  }
@@ -10929,6 +10934,7 @@ async function trackBlackbox(messages) {
10929
10934
  localState.latest_stress_multiplier = st;
10930
10935
  saveSessionStress(st, st > 1.5 ? "critical" : st > 0.7 ? "elevated" : st > 0.3 ? "moderate" : "none");
10931
10936
  }
10937
+ localState.user_text = latestUserIntent;
10932
10938
  const modePreview = peekBudgetFirstMode({
10933
10939
  requestedMode: loadOptimizationMode(),
10934
10940
  subRegime: localState.sub_regime || "INIT",
@@ -11083,13 +11089,15 @@ var onSystemTransform = async (_input, output) => {
11083
11089
  const st = scoreStress(latestUserIntent);
11084
11090
  _controlVector = await apiComputeControlVector({
11085
11091
  sub_regime: classifiedRegime,
11086
- latest_stress_multiplier: st || void 0
11092
+ latest_stress_multiplier: st || void 0,
11093
+ user_text: latestUserIntent
11087
11094
  }, void 0, optimizationMode);
11088
11095
  }
11089
11096
  if (!_controlVector) {
11090
11097
  _controlVector = await apiComputeControlVector({
11091
11098
  sub_regime: "INIT",
11092
- latest_stress_multiplier: latestUserIntent ? scoreStress(latestUserIntent) : void 0
11099
+ latest_stress_multiplier: latestUserIntent ? scoreStress(latestUserIntent) : void 0,
11100
+ user_text: latestUserIntent || void 0
11093
11101
  }, void 0, optimizationMode);
11094
11102
  }
11095
11103
  const system = output?.system;
@@ -13570,26 +13578,28 @@ ${argsJson}
13570
13578
  }
13571
13579
  costDetector.record(modelCost);
13572
13580
  }
13573
- if (_credit < 40 && !compatibilityMode) {
13574
- const total = recordSaving(t, "credit<40% high-tier", _estOpus, {
13581
+ const tLower = String(t || "").toLowerCase();
13582
+ const lowCreditNudge = _credit < 40 && !compatibilityMode;
13583
+ if (lowCreditNudge) {
13584
+ const total = recordSaving(t, "credit<40% high-tier", _estEdit, {
13575
13585
  firstWord: _firstWord,
13576
13586
  projectFingerprint: currentProjectFingerprint,
13577
13587
  projectName: currentProjectName || "",
13578
13588
  sessionId: getCurrentSessionId()
13579
13589
  });
13580
- const msg = `[vibeOS] Quick win: ${resolveTierIcon("cheap")} cheap lane open \xB7 switch to ${resolveTierIcon("medium")} medium to save about ~$${_estOpus.toFixed(3)}/turn.`;
13590
+ const msg = `[vibeOS] Quick win: ${resolveTierIcon("cheap")} cheap lane open \xB7 switch to ${resolveTierIcon("medium")} medium to save about ~$${_estEdit.toFixed(3)}/turn.`;
13581
13591
  if (shouldLogWarn(`${t}|credit|${_tierWord}`) && process.env.VIBEOS_DEBUG_DELEGATION === "1") {
13582
13592
  console.error(`[vibeOS] [delegation] ${msg}`);
13583
13593
  }
13584
13594
  pendingUiNote = msg;
13585
- return;
13595
+ if (!WARN_ON_DIRECT.has(tLower))
13596
+ return;
13586
13597
  }
13587
- if (WARN_ON_DIRECT.has(String(t || "").toLowerCase())) {
13598
+ if (WARN_ON_DIRECT.has(tLower)) {
13588
13599
  const argSources = _toolArgSources(input, output);
13589
13600
  if (process.env.VIBEOS_DEBUG_DELEGATION === "1")
13590
13601
  console.error(`[vibeOS] [enforce-debug] tool=${t} tier=${currentTier} enforce=${sel?.delegation_enforce} argsType=${typeof args} argsExists=${argSources.length > 0}`);
13591
- const tLower = String(t || "").toLowerCase();
13592
- if (!compatibilityMode && sel.delegation_enforce && currentTier === "high" && argSources.length > 0) {
13602
+ if (!compatibilityMode && sel.delegation_enforce && currentTier === "high") {
13593
13603
  const originalPath = argSources.flatMap((src) => [src?.filePath, src?.file_path, src?.path]).find((v) => typeof v === "string" && v.trim()) || "";
13594
13604
  const basename6 = originalPath.split("/").pop() || "blocked";
13595
13605
  const apiResult = await remoteCall("delegateCheck", [tLower, currentTier, currentModel, _prompt], () => ({
@@ -13599,32 +13609,36 @@ ${argsJson}
13599
13609
  const isBlocked = apiResult?.blocked !== false;
13600
13610
  const savings = apiResult?.savings ?? _estEdit;
13601
13611
  if (isBlocked) {
13602
- const total2 = recordSaving(t, "delegation enforced", savings, {
13603
- firstWord: _firstWord,
13604
- projectFingerprint: currentProjectFingerprint,
13605
- projectName: currentProjectName || "",
13606
- sessionId: getCurrentSessionId()
13607
- });
13612
+ if (!lowCreditNudge) {
13613
+ const total = recordSaving(t, "delegation enforced", savings, {
13614
+ firstWord: _firstWord,
13615
+ projectFingerprint: currentProjectFingerprint,
13616
+ projectName: currentProjectName || "",
13617
+ sessionId: getCurrentSessionId()
13618
+ });
13619
+ }
13608
13620
  pendingUiNote = `[delegation] This is a good candidate for a Task subagent \u2014 ${resolveTierIcon("brain")} brain handles orchestration, let cheaper tiers do the write/edit. Switch to ${resolveTierIcon("medium")} medium with \`trinity medium\` if you'd rather do it directly.`;
13609
13621
  enforcementBlocked = true;
13610
13622
  if (shouldLogWarn(`${t}|enforced|${_tierWord}`))
13611
13623
  console.error(`[vibeOS] [enforcement] BLOCKED direct ${t} on high tier \u2192 delegate via Task`);
13612
13624
  return;
13613
13625
  }
13614
- }
13615
- const total = recordSaving(t, "direct edit", _estEdit, {
13616
- firstWord: _firstWord,
13617
- projectFingerprint: currentProjectFingerprint,
13618
- projectName: currentProjectName || "",
13619
- sessionId: getCurrentSessionId()
13620
- });
13621
- if (!compatibilityMode) {
13622
- const msg = `[vibeOS] ${resolveTierIcon("cheap")} cheap lane \xB7 save about ~$${_estEdit.toFixed(3)} by delegating to Task. Try ${resolveTierIcon("medium")} medium.`;
13623
- if (shouldLogWarn(`${t}|direct|${_tierWord}`) && process.env.VIBEOS_DEBUG_DELEGATION === "1") {
13624
- console.error(`[vibeOS] [delegation] ${msg}`);
13626
+ if (!lowCreditNudge) {
13627
+ const total = recordSaving(t, "direct edit", _estEdit, {
13628
+ firstWord: _firstWord,
13629
+ projectFingerprint: currentProjectFingerprint,
13630
+ projectName: currentProjectName || "",
13631
+ sessionId: getCurrentSessionId()
13632
+ });
13633
+ }
13634
+ if (!compatibilityMode) {
13635
+ const msg = `[vibeOS] ${resolveTierIcon("cheap")} cheap lane \xB7 save about ~$${_estEdit.toFixed(3)} by delegating to Task. Try ${resolveTierIcon("medium")} medium.`;
13636
+ if (shouldLogWarn(`${t}|direct|${_tierWord}`) && process.env.VIBEOS_DEBUG_DELEGATION === "1") {
13637
+ console.error(`[vibeOS] [delegation] ${msg}`);
13638
+ }
13639
+ pendingUiNote = msg;
13640
+ return;
13625
13641
  }
13626
- pendingUiNote = msg;
13627
- return;
13628
13642
  }
13629
13643
  }
13630
13644
  if (SOFT_QUOTA.has(t)) {
@@ -14260,9 +14274,30 @@ function _loadActiveJobForProject(directory3, fp2 = "") {
14260
14274
  }
14261
14275
  return getActiveJobForProject(fp2);
14262
14276
  }
14263
- async function _seedModelTiersIfMissing(directory3) {
14277
+ function _tiersNeedRepair(tiers) {
14278
+ const slots = ["brain", "medium", "cheap"];
14279
+ if (!tiers || typeof tiers !== "object") return true;
14280
+ return slots.some((slot) => {
14281
+ const oc = String(tiers?.trinity?.[slot]?.oc || "").trim();
14282
+ return !oc || PLACEHOLDER_RE.test(oc);
14283
+ });
14284
+ }
14285
+ async function _seedOrRepairModelTiers(directory3) {
14264
14286
  const TIERS_FILE3 = getTiersFile();
14265
- if (existsSync18(TIERS_FILE3))
14287
+ let existing = null;
14288
+ if (existsSync18(TIERS_FILE3)) {
14289
+ try {
14290
+ const st = statSync9(TIERS_FILE3);
14291
+ if (st.size > 10485760) {
14292
+ _handleStateCorruption(TIERS_FILE3);
14293
+ return false;
14294
+ }
14295
+ existing = safeJsonParse3(readFileSync17(TIERS_FILE3, "utf-8")) || {};
14296
+ } catch {
14297
+ existing = null;
14298
+ }
14299
+ }
14300
+ if (existing && !_tiersNeedRepair(existing))
14266
14301
  return false;
14267
14302
  const providers = _loadOpenCodeProviders(directory3);
14268
14303
  const auth = typeof _readAuth === "function" ? _readAuth() : {};
@@ -14285,25 +14320,31 @@ async function _seedModelTiersIfMissing(directory3) {
14285
14320
  cheap = "deepseek/deepseek-chat";
14286
14321
  console.error("[vibeOS] no providers detected \u2014 using default model tiers (brain=v4-pro, medium=v4-flash, cheap=v4-chat)");
14287
14322
  }
14323
+ const existingSelection = existing?.selection && typeof existing.selection === "object" ? existing.selection : {};
14324
+ const existingTrinity = existing?.trinity && typeof existing.trinity === "object" ? existing.trinity : {};
14325
+ const nextTrinity = {
14326
+ brain: existingTrinity.brain?.manual === true && String(existingTrinity.brain?.oc || "").trim() && !PLACEHOLDER_RE.test(String(existingTrinity.brain?.oc || "")) ? { ...existingTrinity.brain, cc: existingTrinity.brain?.cc || modelToCcAlias(String(existingTrinity.brain?.oc || "")) } : { oc: brain, cc: modelToCcAlias(brain) },
14327
+ medium: existingTrinity.medium?.manual === true && String(existingTrinity.medium?.oc || "").trim() && !PLACEHOLDER_RE.test(String(existingTrinity.medium?.oc || "")) ? { ...existingTrinity.medium, cc: existingTrinity.medium?.cc || modelToCcAlias(String(existingTrinity.medium?.oc || "")) } : { oc: medium, cc: modelToCcAlias(medium) },
14328
+ cheap: existingTrinity.cheap?.manual === true && String(existingTrinity.cheap?.oc || "").trim() && !PLACEHOLDER_RE.test(String(existingTrinity.cheap?.oc || "")) ? { ...existingTrinity.cheap, cc: existingTrinity.cheap?.cc || modelToCcAlias(String(existingTrinity.cheap?.oc || "")) } : { oc: cheap, cc: modelToCcAlias(cheap) }
14329
+ };
14330
+ const activeSlot = ["brain", "medium", "cheap"].includes(String(existingSelection.active_slot || "").trim()) ? String(existingSelection.active_slot) : "brain";
14288
14331
  const tiers = {
14332
+ ...existing,
14289
14333
  selection: {
14290
- enabled: true,
14291
- active_slot: "brain",
14292
- thinking_level: "off",
14293
- flow_enabled: false,
14294
- flow_enforce: false,
14295
- tdd_enforce: false,
14296
- tdd_strict: false,
14297
- tdd_quality: false,
14298
- delegation_enforce: true,
14299
- onboarding_mode: "assist",
14300
- setup_completed_at: (/* @__PURE__ */ new Date()).toISOString()
14334
+ ...existingSelection,
14335
+ enabled: existingSelection.enabled !== false,
14336
+ active_slot: activeSlot,
14337
+ thinking_level: existingSelection.thinking_level || "off",
14338
+ delegation_enforce: existingSelection.delegation_enforce !== false,
14339
+ flow_enabled: existingSelection.flow_enabled === true,
14340
+ flow_enforce: existingSelection.flow_enforce === true,
14341
+ tdd_enforce: existingSelection.tdd_enforce === true,
14342
+ tdd_strict: existingSelection.tdd_strict === true,
14343
+ tdd_quality: existingSelection.tdd_quality !== false,
14344
+ onboarding_mode: existingSelection.onboarding_mode || "assist",
14345
+ setup_completed_at: existingSelection.setup_completed_at || (/* @__PURE__ */ new Date()).toISOString()
14301
14346
  },
14302
- trinity: {
14303
- brain: { oc: brain, cc: modelToCcAlias(brain) },
14304
- medium: { oc: medium, cc: modelToCcAlias(medium) },
14305
- cheap: { oc: cheap, cc: modelToCcAlias(cheap) }
14306
- }
14347
+ trinity: nextTrinity
14307
14348
  };
14308
14349
  mkdirSync13(dirname13(TIERS_FILE3), { recursive: true });
14309
14350
  writeFileSync15(TIERS_FILE3, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
@@ -14435,7 +14476,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14435
14476
  if (!existsSync18(getTiersFile())) {
14436
14477
  console.error(`[vibeOS] model-tiers.json missing at load; will seed on first hook`);
14437
14478
  }
14438
- await _seedModelTiersIfMissing(directory3);
14479
+ await _seedOrRepairModelTiers(directory3);
14439
14480
  loadTrinitySlotsFromTiersFile();
14440
14481
  } catch {
14441
14482
  }
@@ -14589,7 +14630,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14589
14630
  setBlackboxEnabled,
14590
14631
  loadBlackboxState,
14591
14632
  saveBlackboxState,
14592
- isApiFallback: () => isApiFallback(),
14633
+ isApiFallback: () => isApiFallback2(),
14593
14634
  get _apiFallbackSince() {
14594
14635
  return _apiFallbackSince2;
14595
14636
  },
@@ -14843,7 +14884,7 @@ ${report.narrative}`);
14843
14884
  fallbackThinking: thinkingLevel(loadCredit()),
14844
14885
  backendConnected: isApiConnected2(),
14845
14886
  backendHealthUrl: `${VIBEOS_API_URL}/health`,
14846
- apiFallbackMode: isApiFallback(),
14887
+ apiFallbackMode: isApiFallback2(),
14847
14888
  apiFallbackSince: _apiFallbackSince2,
14848
14889
  modelLocked: _modelLocked,
14849
14890
  lockedSlot: _lockedSlot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.24.18",
3
+ "version": "0.24.21",
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",