vibeostheog 0.24.24 → 0.24.26

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,7 @@
1
+ ## 0.24.25
2
+ - fix: heal stale vibelitex recovery across cascade
3
+
4
+
1
5
  ## 0.24.24
2
6
  - fix: restore persisted slot lock on reload (#151)
3
7
  - chore: v0.24.23 (#150)
package/dist/vibeOS.js CHANGED
@@ -2348,22 +2348,31 @@ async function remoteCall(method, args, fallbackFn) {
2348
2348
  return null;
2349
2349
  }
2350
2350
  const result = await client2[method](...args);
2351
+ if (_apiFallbackMode) {
2352
+ _apiFallbackMode = false;
2353
+ _apiFallbackSince = null;
2354
+ console.warn(`[vibeOS] API reconnected \u2014 ${method} OK`);
2355
+ }
2351
2356
  _apiFallbackMode = false;
2352
2357
  _apiFallbackSince = null;
2353
2358
  markApiConnected();
2354
2359
  return result;
2355
2360
  } catch (err) {
2361
+ const status = err?.statusCode || err?.status || 0;
2362
+ const body = err?.response?.body || err?.body || "";
2363
+ const bodyPreview = typeof body === "string" ? body.substring(0, 120) : String(body).substring(0, 120);
2364
+ const detail = status ? `status=${status} body=${bodyPreview}` : `message=${err?.message || err}`;
2356
2365
  if (!_apiFallbackMode) {
2357
2366
  _apiFallbackMode = true;
2358
2367
  _apiFallbackSince = (/* @__PURE__ */ new Date()).toISOString();
2359
- console.error(`[vibeOS] API fallback activated: ${err.message}`);
2368
+ console.error(`[vibeOS] API fallback activated (${method}): ${detail}`);
2360
2369
  }
2361
2370
  markApiDisconnected();
2362
2371
  if (fallbackFn) {
2363
2372
  try {
2364
2373
  return fallbackFn();
2365
2374
  } catch (fe) {
2366
- console.error(`[vibeOS] fallback also failed: ${fe.message}`);
2375
+ console.error(`[vibeOS] fallback also failed: ${fe?.message || fe}`);
2367
2376
  }
2368
2377
  }
2369
2378
  return null;
@@ -3480,9 +3489,30 @@ function runStartupMaintenanceOnce() {
3480
3489
  } catch {
3481
3490
  }
3482
3491
  }
3492
+ function _ensureVibeOSHomeDir() {
3493
+ try {
3494
+ if (!existsSync5(VIBEOS_HOME)) {
3495
+ mkdirSync3(VIBEOS_HOME, { recursive: true });
3496
+ return VIBEOS_HOME;
3497
+ }
3498
+ const st = statSync4(VIBEOS_HOME);
3499
+ if (!st.isDirectory()) {
3500
+ const backup = VIBEOS_HOME + ".backup." + Date.now();
3501
+ renameSync3(VIBEOS_HOME, backup);
3502
+ mkdirSync3(VIBEOS_HOME, { recursive: true });
3503
+ }
3504
+ return VIBEOS_HOME;
3505
+ } catch {
3506
+ return VIBEOS_HOME;
3507
+ }
3508
+ }
3483
3509
  function _handleStateCorruption2(path) {
3510
+ _ensureVibeOSHomeDir();
3484
3511
  const backupDir = join4(VIBEOS_HOME, ".backups");
3485
- mkdirSync3(backupDir, { recursive: true });
3512
+ try {
3513
+ mkdirSync3(backupDir, { recursive: true });
3514
+ } catch {
3515
+ }
3486
3516
  const backupPath = join4(backupDir, basename2(path) + ".corrupted." + Date.now());
3487
3517
  try {
3488
3518
  copyFileSync(path, backupPath);
@@ -4469,8 +4499,9 @@ function touchProjectBucket(state, fp2, meta = {}) {
4469
4499
  if (!bucket.sessions.includes(meta.sessionId)) {
4470
4500
  bucket.sessions.push(meta.sessionId);
4471
4501
  bucket.sessions = bucket.sessions.slice(-30);
4502
+ bucket.totalSessions = Number(bucket.totalSessions || 0) + 1;
4472
4503
  }
4473
- bucket.totalSessions = Math.max(Number(bucket.totalSessions || 0), bucket.sessions.length);
4504
+ bucket.totalSessions = Math.max(Number(bucket.totalSessions || 0), bucket.sessions.length, 1);
4474
4505
  }
4475
4506
  if (typeof meta.reportId === "string" && meta.reportId.trim()) {
4476
4507
  bucket.reports ??= [];
@@ -6147,32 +6178,35 @@ function _refreshModel(directory3) {
6147
6178
  }
6148
6179
  if (!(_modelLocked || sel.slot_locked === true)) {
6149
6180
  const activeIsManual = tiersData?.trinity?.[activeSlot]?.manual === true;
6150
- const cfgModel = activeIsManual ? "" : readConfig(directory3) || readConfig(getOpenCodeHome()) || "";
6151
- if (cfgModel && cfgModel.includes("/") && cfgModel !== currentModel) {
6152
- const oldModel = currentModel;
6153
- const oldTier = currentTier;
6154
- setCurrentModel(cfgModel);
6155
- setCurrentTier(classify(cfgModel));
6156
- if (DEBUG_INTERNALS)
6157
- console.error(`[vibeOS] model refresh (config): ${oldModel}(${oldTier}) \u2192 ${currentModel}(${currentTier})`);
6158
- try {
6159
- if (existsSync6(TIERS_FILE3)) {
6160
- withFileLock2(TIERS_FILE3, () => {
6161
- const t = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
6162
- for (const s of getTrinitySlotOrder(t)) {
6163
- if (t?.trinity?.[s]?.oc === cfgModel) {
6164
- t.selection.active_slot = s;
6165
- const _tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
6166
- writeFileSync5(_tmp, JSON.stringify(t, null, 2) + "\n", "utf-8");
6167
- renameSync4(_tmp, TIERS_FILE3);
6168
- if (DEBUG_INTERNALS)
6169
- console.error(`[vibeOS] model refresh (config): synced active_slot \u2192 ${s}`);
6170
- break;
6181
+ const currentSlotModel = activeIsManual ? "" : slotOcModel;
6182
+ if (!currentSlotModel) {
6183
+ const cfgModel = readConfig(directory3) || readConfig(getOpenCodeHome()) || "";
6184
+ if (cfgModel && cfgModel.includes("/") && cfgModel !== currentModel) {
6185
+ const oldModel = currentModel;
6186
+ const oldTier = currentTier;
6187
+ setCurrentModel(cfgModel);
6188
+ setCurrentTier(classify(cfgModel));
6189
+ if (DEBUG_INTERNALS)
6190
+ console.error(`[vibeOS] model refresh (config fallback): ${oldModel}(${oldTier}) \u2192 ${currentModel}(${currentTier})`);
6191
+ try {
6192
+ if (existsSync6(TIERS_FILE3)) {
6193
+ withFileLock2(TIERS_FILE3, () => {
6194
+ const t = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
6195
+ for (const s of getTrinitySlotOrder(t)) {
6196
+ if (t?.trinity?.[s]?.oc === cfgModel) {
6197
+ t.selection.active_slot = s;
6198
+ const _tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
6199
+ writeFileSync5(_tmp, JSON.stringify(t, null, 2) + "\n", "utf-8");
6200
+ renameSync4(_tmp, TIERS_FILE3);
6201
+ if (DEBUG_INTERNALS)
6202
+ console.error(`[vibeOS] model refresh (config fallback): synced active_slot \u2192 ${s}`);
6203
+ break;
6204
+ }
6171
6205
  }
6172
- }
6173
- });
6206
+ });
6207
+ }
6208
+ } catch {
6174
6209
  }
6175
- } catch {
6176
6210
  }
6177
6211
  }
6178
6212
  }
@@ -7283,15 +7317,33 @@ function recoverOptimizationModeFromSelection(sel) {
7283
7317
  return "budget";
7284
7318
  return "budget";
7285
7319
  }
7320
+ function recoverOptimizationModeFromLiveState(sel) {
7321
+ const liveTier = String(currentTier || "").toLowerCase();
7322
+ if (liveTier === "high")
7323
+ return "quality";
7324
+ if (liveTier === "mid")
7325
+ return "vibemax";
7326
+ if (liveTier === "cheap" || liveTier === "budget")
7327
+ return "budget";
7328
+ return recoverOptimizationModeFromSelection(sel);
7329
+ }
7286
7330
  function loadOptimizationMode() {
7287
7331
  try {
7288
7332
  const sel = loadSelection();
7289
7333
  const persistedMode = sel.optimization_mode || null;
7290
- if (persistedMode === "vibelitex") {
7291
- const prevKey = `${_OC_SID}_prev_opt`;
7292
- const sessionMode = loadSessionOptMode(_OC_SID);
7293
- const globalMode = loadGlobalOptMode();
7294
- const recoveryMode = sel.previous_optimization_mode || loadSessionOptMode(prevKey) || (sessionMode && sessionMode !== "vibelitex" ? sessionMode : "") || (globalMode && globalMode !== "vibelitex" ? globalMode : "") || recoverOptimizationModeFromSelection(sel);
7334
+ const prevKey = `${_OC_SID}_prev_opt`;
7335
+ const sessionMode = loadSessionOptMode(_OC_SID);
7336
+ const globalMode = loadGlobalOptMode();
7337
+ const liveRecovery = recoverOptimizationModeFromLiveState(sel);
7338
+ const storedModes = [
7339
+ persistedMode,
7340
+ sel.previous_optimization_mode,
7341
+ loadSessionOptMode(prevKey),
7342
+ sessionMode,
7343
+ globalMode
7344
+ ].map((mode) => String(mode || "").toLowerCase());
7345
+ if (storedModes.includes("vibelitex")) {
7346
+ const recoveryMode = (sel.previous_optimization_mode && sel.previous_optimization_mode !== "vibelitex" ? sel.previous_optimization_mode : "") || loadSessionOptMode(prevKey) || (sessionMode && sessionMode !== "vibelitex" ? sessionMode : "") || (globalMode && globalMode !== "vibelitex" ? globalMode : "") || liveRecovery;
7295
7347
  if (recoveryMode && recoveryMode !== "vibelitex") {
7296
7348
  try {
7297
7349
  writeSelection("optimization_mode", recoveryMode);
@@ -7312,12 +7364,10 @@ function loadOptimizationMode() {
7312
7364
  return recoveryMode;
7313
7365
  }
7314
7366
  }
7315
- const mode = loadSessionOptMode(_OC_SID);
7316
- if (mode && mode !== "auto")
7317
- return mode;
7318
- const global = loadGlobalOptMode();
7319
- if (global && global !== "auto")
7320
- return global;
7367
+ if (sessionMode && sessionMode !== "auto")
7368
+ return sessionMode;
7369
+ if (globalMode && globalMode !== "auto")
7370
+ return globalMode;
7321
7371
  return DFLT_OPTIMIZATION_MODE;
7322
7372
  } catch {
7323
7373
  return DFLT_OPTIMIZATION_MODE;
@@ -7735,10 +7785,10 @@ function generateReportId(type, fp2) {
7735
7785
  return `${ts}-${(fp2 || "unknown").slice(0, 6)}-${type}-${rnd}`;
7736
7786
  }
7737
7787
  var _reportDedupWindow = /* @__PURE__ */ new Map();
7738
- function _wouldBeDuplicate(type, summary) {
7788
+ function _wouldBeDuplicate(type, summary, scope) {
7739
7789
  if (typeof summary !== "string")
7740
7790
  return false;
7741
- const key = `${getVibeOSHome7()}::${type || ""}::${summary}`;
7791
+ const key = `${getVibeOSHome7()}::${type || ""}::${String(scope || "unknown")}::${summary}`;
7742
7792
  const last = _reportDedupWindow.get(key);
7743
7793
  if (last && Date.now() - last < 5 * 60 * 1e3)
7744
7794
  return true;
@@ -7821,7 +7871,8 @@ function saveReport({ type = "manual", summary = "", findings = null, metrics =
7821
7871
  const metricsSessionId = typeof metricsObject.sessionId === "string" && metricsObject.sessionId.trim() ? metricsObject.sessionId.trim() : "";
7822
7872
  const metricsProjectName = typeof metricsObject.projectName === "string" && metricsObject.projectName.trim() ? metricsObject.projectName.trim() : "";
7823
7873
  const metricsProjectFingerprint = typeof metricsObject.projectFingerprint === "string" && metricsObject.projectFingerprint.trim() ? metricsObject.projectFingerprint.trim() : "";
7824
- if (_wouldBeDuplicate(type, summary))
7874
+ const dedupScope = fingerprint || metricsProjectFingerprint || currentProjectFingerprint || currentProjectFingerprint2 || metricsProjectName || currentProjectName || currentProjectName2 || "unknown";
7875
+ if (_wouldBeDuplicate(type, summary, dedupScope))
7825
7876
  return null;
7826
7877
  if (!currentProjectFingerprint2 && metricsProjectFingerprint)
7827
7878
  currentProjectFingerprint2 = metricsProjectFingerprint;
@@ -8263,6 +8314,13 @@ function createTrinityTool(deps) {
8263
8314
  slot = action;
8264
8315
  action = "set";
8265
8316
  }
8317
+ const keepExistingTrinitySlot = (existingSlot, nextModel) => {
8318
+ const currentOc = String(existingSlot?.oc || "").trim();
8319
+ if (currentOc && !/placeholder/i.test(currentOc) && !/^[^/]+\/[a-z-]+-model$/i.test(currentOc)) {
8320
+ return { ...existingSlot, cc: existingSlot?.cc || deps.modelToCcAlias(currentOc) };
8321
+ }
8322
+ return { oc: nextModel, cc: deps.modelToCcAlias(nextModel) };
8323
+ };
8266
8324
  const _brandedModeIds = ["vibeultrax", "vibeqmax", "vibemax", "vibelitex"];
8267
8325
  const _builtInModeIds = ["budget", "quality", "speed", "longrun", "auto", "balanced", "audit", "forensic"];
8268
8326
  if (!action || action === "status") {
@@ -8300,7 +8358,7 @@ function createTrinityTool(deps) {
8300
8358
  const fallbackModelGuard = currentProvider === "opencode" && selectedProvider !== "opencode";
8301
8359
  if (deps.currentModel && sel.selected_model && deps.currentModel !== sel.selected_model && !apiFallbackActive && !fallbackModelGuard) {
8302
8360
  try {
8303
- const providers = deps._loadOpenCodeProviders();
8361
+ const providers = typeof deps._loadOpenCodeProviders === "function" ? deps._loadOpenCodeProviders(deps.directory) : {};
8304
8362
  const auth = deps._readAuth();
8305
8363
  const models = await deps.discoverAvailableModels(providers, auth);
8306
8364
  const trinity = buildDeterministicTrinity(models, { selectedModelId: deps.currentModel });
@@ -8318,13 +8376,7 @@ function createTrinityTool(deps) {
8318
8376
  const slots = ["brain", "medium", "cheap"];
8319
8377
  for (const s of slots) {
8320
8378
  const autoModel = probed[s].id;
8321
- const oldModel = oldTiers[s]?.oc || "";
8322
- const oldModelProvider = oldModel.includes("/") ? oldModel.split("/")[0] : "";
8323
- if (oldModelProvider && oldModelProvider !== oldProvider && oldModelProvider !== newProvider) {
8324
- tiersData.trinity[s] = oldTiers[s];
8325
- } else {
8326
- tiersData.trinity[s] = { oc: autoModel, cc: deps.modelToCcAlias(autoModel) };
8327
- }
8379
+ tiersData.trinity[s] = keepExistingTrinitySlot(oldTiers[s], autoModel);
8328
8380
  }
8329
8381
  tiersData.selection ??= {};
8330
8382
  tiersData.selection.selected_provider = trinity.provider || resolveExecutionIdentity(deps.currentModel, deps.directory)?.provider || "";
@@ -8672,7 +8724,7 @@ Lock is per-session (resets on restart).`;
8672
8724
  if (action === "setup") {
8673
8725
  const now = (/* @__PURE__ */ new Date()).toISOString();
8674
8726
  const existing = deps.existsSync(deps.TIERS_FILE) ? deps.safeJsonParse(deps.readFileSync(deps.TIERS_FILE, "utf-8")) || {} : {};
8675
- const providers = typeof deps._loadOpenCodeProviders === "function" ? deps._loadOpenCodeProviders() : {};
8727
+ const providers = typeof deps._loadOpenCodeProviders === "function" ? deps._loadOpenCodeProviders(deps.directory) : {};
8676
8728
  const auth = typeof deps._readAuth === "function" ? deps._readAuth() : {};
8677
8729
  let discovered = [];
8678
8730
  try {
@@ -8706,12 +8758,12 @@ Lock is per-session (resets on restart).`;
8706
8758
  tiers.selection.executed_provider = tiers.selection.selected_provider;
8707
8759
  tiers.selection.executed_quality_tier = tiers.selection.selected_quality_tier;
8708
8760
  tiers.selection.executed_model = tiers.selection.selected_model;
8709
- if (brain && existing?.trinity?.brain?.manual !== true)
8710
- tiers.trinity.brain = { oc: brain, cc: deps.modelToCcAlias(brain) };
8711
- if (medium && existing?.trinity?.medium?.manual !== true)
8712
- tiers.trinity.medium = { oc: medium, cc: deps.modelToCcAlias(medium) };
8713
- if (cheap && existing?.trinity?.cheap?.manual !== true)
8714
- tiers.trinity.cheap = { oc: cheap, cc: deps.modelToCcAlias(cheap) };
8761
+ if (brain)
8762
+ tiers.trinity.brain = keepExistingTrinitySlot(existing?.trinity?.brain, brain);
8763
+ if (medium)
8764
+ tiers.trinity.medium = keepExistingTrinitySlot(existing?.trinity?.medium, medium);
8765
+ if (cheap)
8766
+ tiers.trinity.cheap = keepExistingTrinitySlot(existing?.trinity?.cheap, cheap);
8715
8767
  deps.mkdirSync(dirname9(deps.TIERS_FILE), { recursive: true });
8716
8768
  deps.writeFileSync(deps.TIERS_FILE, JSON.stringify(tiers, null, 2) + "\n");
8717
8769
  if (typeof deps._refreshModel === "function")
@@ -9063,7 +9115,7 @@ ${L.repeat(40)}`);
9063
9115
  return "[vibeOS] Alpha bootstrap token saved. Remote API will retry the exchange on the next call.";
9064
9116
  }
9065
9117
  if (action === "rebuild") {
9066
- const providers = deps._loadOpenCodeProviders();
9118
+ const providers = typeof deps._loadOpenCodeProviders === "function" ? deps._loadOpenCodeProviders(deps.directory) : {};
9067
9119
  const auth = deps._readAuth();
9068
9120
  const models = await deps.discoverAvailableModels(providers, auth);
9069
9121
  const selectedModel = deps.currentModel || deps.loadSelection?.().selected_model || deps.loadSelection?.().executed_model || "";
@@ -9092,9 +9144,9 @@ ${L.repeat(40)}`);
9092
9144
  const tiers = deps.safeJsonParse(deps.readFileSync(deps.TIERS_FILE, "utf-8"));
9093
9145
  const existing = tiers.trinity || {};
9094
9146
  tiers.trinity = {
9095
- brain: existing.brain?.manual === true ? { ...existing.brain } : { oc: probed.brain.id, cc: deps.modelToCcAlias(probed.brain.id) },
9096
- medium: existing.medium?.manual === true ? { ...existing.medium } : { oc: probed.medium.id, cc: deps.modelToCcAlias(probed.medium.id) },
9097
- cheap: existing.cheap?.manual === true ? { ...existing.cheap } : { oc: probed.cheap.id, cc: deps.modelToCcAlias(probed.cheap.id) }
9147
+ brain: keepExistingTrinitySlot(existing.brain, probed.brain.id),
9148
+ medium: keepExistingTrinitySlot(existing.medium, probed.medium.id),
9149
+ cheap: keepExistingTrinitySlot(existing.cheap, probed.cheap.id)
9098
9150
  };
9099
9151
  tiers.selection ??= {};
9100
9152
  tiers.selection.selected_provider = trinity.provider || resolveExecutionIdentity(selectedModel, deps.directory)?.provider || "";
@@ -9178,7 +9230,7 @@ ${L.repeat(40)}`);
9178
9230
  } else if (deps.currentModel || !deps.existsSync(deps.TIERS_FILE)) {
9179
9231
  try {
9180
9232
  const auth = deps._readAuth();
9181
- const ok = await deps.probeModel(deps.currentModel, auth, deps._loadOpenCodeProviders());
9233
+ const ok = await deps.probeModel(deps.currentModel, auth, typeof deps._loadOpenCodeProviders === "function" ? deps._loadOpenCodeProviders(deps.directory) : {});
9182
9234
  results.push({
9183
9235
  ok,
9184
9236
  okLabel: ok ? "\u2705" : "\u274C",
@@ -10826,9 +10878,10 @@ function syncControlSettings(cv, options = {}) {
10826
10878
  const restoreMode = sessionPreviousOptMode || previousOptMode2 || inferredRecoveryMode;
10827
10879
  const canRestorePrevious = !!restoreMode && cv.optimization_mode !== "vibelitex" && (previousOptMode2 !== null || sessionPreviousOptMode !== null);
10828
10880
  if (fallbackPinned) {
10829
- if (currentSel.optimization_mode !== "vibelitex") {
10830
- writeIf("previous_optimization_mode", currentSel.optimization_mode);
10831
- writeSessionOptMode(prevSessionKey2, currentSel.optimization_mode || "");
10881
+ const snapshotMode = currentSel.optimization_mode && currentSel.optimization_mode !== "vibelitex" ? currentSel.optimization_mode : previousOptMode2 || sessionPreviousOptMode || inferredRecoveryMode;
10882
+ if (snapshotMode && snapshotMode !== "vibelitex") {
10883
+ writeIf("previous_optimization_mode", snapshotMode);
10884
+ writeSessionOptMode(prevSessionKey2, snapshotMode);
10832
10885
  }
10833
10886
  } else if (canRestorePrevious) {
10834
10887
  writeIf("optimization_mode", restoreMode);
@@ -13715,6 +13768,7 @@ ${argsJson}
13715
13768
  }
13716
13769
  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.`;
13717
13770
  enforcementBlocked = true;
13771
+ _mutateBlockedToolArgs(t, argSources, originalPath, output);
13718
13772
  if (shouldLogWarn(`${t}|enforced|${_tierWord}`))
13719
13773
  console.error(`[vibeOS] [enforcement] BLOCKED direct ${t} on high tier \u2192 delegate via Task`);
13720
13774
  return;
@@ -14418,10 +14472,17 @@ async function _seedOrRepairModelTiers(directory3) {
14418
14472
  }
14419
14473
  const existingSelection = existing?.selection && typeof existing.selection === "object" ? existing.selection : {};
14420
14474
  const existingTrinity = existing?.trinity && typeof existing.trinity === "object" ? existing.trinity : {};
14475
+ const keepExistingSlot = (slotRow, fallbackModel) => {
14476
+ const currentOc = String(slotRow?.oc || "").trim();
14477
+ if (currentOc && !PLACEHOLDER_RE.test(currentOc) && !/placeholder/i.test(currentOc)) {
14478
+ return { ...slotRow, cc: slotRow?.cc || modelToCcAlias(currentOc) };
14479
+ }
14480
+ return { oc: fallbackModel, cc: modelToCcAlias(fallbackModel) };
14481
+ };
14421
14482
  const nextTrinity = {
14422
- 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) },
14423
- 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) },
14424
- 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) }
14483
+ brain: keepExistingSlot(existingTrinity.brain, brain),
14484
+ medium: keepExistingSlot(existingTrinity.medium, medium),
14485
+ cheap: keepExistingSlot(existingTrinity.cheap, cheap)
14425
14486
  };
14426
14487
  const activeSlot = ["brain", "medium", "cheap"].includes(String(existingSelection.active_slot || "").trim()) ? String(existingSelection.active_slot) : "brain";
14427
14488
  const tiers = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.24.24",
3
+ "version": "0.24.26",
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",