vibeostheog 0.24.13 → 0.24.14

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,5 +1,7 @@
1
- ## 0.24.13
1
+ ## 0.24.14
2
2
  - feat: smooth delegation UX — conversational reasoning over error injection
3
+ - fix: preserve audit and forensic modes
4
+ - fix: harden live regression paths
3
5
  - fix: write npm auth for github release publish
4
6
  - fix: reset api fallback on token refresh (#138)
5
7
  - fix: _prevBlackboxState -> _latestBlackboxState, update pattern key test
@@ -7,9 +9,11 @@
7
9
  - fix: forensic anti-lying + quality enforcement pipeline
8
10
  - docs: add cross-project index, DEV ONLY markers, ESLint cleanup
9
11
  - test: add 13 cascade integration tests for forensic quality pipeline
12
+ - chore: sync package version to v0.24.13
10
13
  - chore: sync package version to v0.24.12
11
14
  - chore: sync package version to latest github release
12
15
  - chore: sync package version to latest release
16
+ Fix live regressions and add integration coverage (#139)
13
17
  Merge pull request #136 from DrunkkToys/release/v0.24.8-merge
14
18
 
15
19
 
package/dist/vibeOS.js CHANGED
@@ -2409,7 +2409,7 @@ function safeJsonParse2(raw) {
2409
2409
  throw e;
2410
2410
  }
2411
2411
  }
2412
- var DFLT_SEL = { enabled: true, active_slot: null, thinking_level: "off", flow_enabled: true, tdd_enforce: false, tdd_strict: false, tdd_quality: true, flow_enforce: true, delegation_enforce: true, onboarding_mode: null, selected_provider: null, selected_quality_tier: null, selected_model: null, executed_provider: null, executed_quality_tier: null, executed_model: null, previous_default_agent: null };
2412
+ var DFLT_SEL = { enabled: true, active_slot: null, slot_locked: false, thinking_level: "off", flow_enabled: true, tdd_enforce: false, tdd_strict: false, tdd_quality: true, flow_enforce: true, delegation_enforce: true, onboarding_mode: null, selected_provider: null, selected_quality_tier: null, selected_model: null, executed_provider: null, executed_quality_tier: null, executed_model: null, previous_default_agent: null };
2413
2413
  function loadSelection() {
2414
2414
  const TIERS_FILE3 = join3(getVibeOSHome2(), "model-tiers.json");
2415
2415
  try {
@@ -2424,6 +2424,7 @@ function loadSelection() {
2424
2424
  return {
2425
2425
  enabled: j?.selection?.enabled !== false,
2426
2426
  active_slot: j?.selection?.active_slot || null,
2427
+ slot_locked: j?.selection?.slot_locked === true,
2427
2428
  active_pipeline: j?.selection?.active_pipeline || null,
2428
2429
  optimization_mode: j?.selection?.optimization_mode || null,
2429
2430
  thinking_level: j?.selection?.thinking_level || "off",
@@ -5546,6 +5547,7 @@ function loadSelection2() {
5546
5547
  return {
5547
5548
  enabled: j?.selection?.enabled !== false,
5548
5549
  active_slot: j?.selection?.active_slot || null,
5550
+ slot_locked: j?.selection?.slot_locked === true,
5549
5551
  thinking_level: j?.selection?.thinking_level || "off",
5550
5552
  flow_enabled: j?.selection?.flow_enabled === true,
5551
5553
  tdd_enforce: j?.selection?.tdd_enforce === true,
@@ -5565,7 +5567,7 @@ function loadSelection2() {
5565
5567
  return DFLT_SEL2;
5566
5568
  }
5567
5569
  }
5568
- var DFLT_SEL2 = { enabled: true, active_slot: null, thinking_level: "off", flow_enabled: true, tdd_enforce: false, tdd_strict: false, tdd_quality: true, flow_enforce: true, delegation_enforce: true, selected_provider: null, selected_quality_tier: null, selected_model: null, executed_provider: null, executed_quality_tier: null, executed_model: null };
5570
+ var DFLT_SEL2 = { enabled: true, active_slot: null, slot_locked: false, thinking_level: "off", flow_enabled: true, tdd_enforce: false, tdd_strict: false, tdd_quality: true, flow_enforce: true, delegation_enforce: true, selected_provider: null, selected_quality_tier: null, selected_model: null, executed_provider: null, executed_quality_tier: null, executed_model: null };
5569
5571
  function readConfig(dir) {
5570
5572
  try {
5571
5573
  const configs = [];
@@ -6327,42 +6329,42 @@ function scoreStress(text) {
6327
6329
  for (const w of aggressive) {
6328
6330
  const re = new RegExp("\\b" + w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\b", "gi");
6329
6331
  const hits = (t.match(re) || []).length;
6330
- score += hits * 0.14;
6332
+ score += hits * 0.18;
6331
6333
  }
6332
- const urgency = ["fix", "now", "fast", "urgent", "important", "critical", "hurry", "immediately", "asap"];
6334
+ const urgency = ["fix", "now", "fast", "urgent", "important", "critical", "hurry", "immediately", "asap", "stressed", "stress", "frustrated", "overwhelmed", "panic", "panicked", "anxious"];
6333
6335
  for (const w of urgency) {
6334
6336
  const re = new RegExp("\\b" + w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\b", "gi");
6335
6337
  const hits = (t.match(re) || []).length;
6336
- score += hits * 0.06;
6338
+ score += hits * 0.16;
6337
6339
  }
6338
6340
  const negative = ["no", "not", "don't", "can't", "won't", "doesn't", "isn't", "shouldn't", "never", "stop"];
6339
6341
  for (const w of negative) {
6340
6342
  const re = new RegExp("\\b" + w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\b", "gi");
6341
6343
  const hits = (t.match(re) || []).length;
6342
- score += hits * 0.04;
6344
+ score += hits * 0.06;
6343
6345
  }
6344
6346
  const capsAcronyms = /* @__PURE__ */ new Set(["ai", "ui", "api", "cli", "ssh", "dns", "http", "url", "json", "xml", "css", "html", "sql", "csv", "yaml", "ide", "tdd", "pr", "ci", "cd", "env", "os", "sdk", "gui", "crud", "rest", "crlf", "utf", "ascii"]);
6345
6347
  const words = text.split(/\s+/);
6346
6348
  for (const w of words) {
6347
6349
  if (w.length >= 3 && /^[A-Z]+$/.test(w) && !capsAcronyms.has(w.toLowerCase())) {
6348
- score += 0.02;
6350
+ score += 0.05;
6349
6351
  }
6350
6352
  }
6351
6353
  const exclamParts = text.match(/!{2,}/g);
6352
6354
  if (exclamParts)
6353
- score += exclamParts.length * 0.03;
6355
+ score += exclamParts.length * 0.08;
6354
6356
  const qmarkParts = text.match(/\?{2,}/g);
6355
6357
  if (qmarkParts)
6356
- score += qmarkParts.length * 0.02;
6358
+ score += qmarkParts.length * 0.05;
6357
6359
  const qeCombos = text.match(/\?!|!\?/g);
6358
6360
  if (qeCombos)
6359
- score += qeCombos.length * 0.05;
6361
+ score += qeCombos.length * 0.1;
6360
6362
  if (text.length < 30)
6361
- score += 0.05;
6363
+ score += 0.06;
6362
6364
  else if (text.length < 80)
6363
- score += 0.03;
6365
+ score += 0.05;
6364
6366
  else if (text.length < 150)
6365
- score += 0.01;
6367
+ score += 0.03;
6366
6368
  return Math.min(score, 0.95);
6367
6369
  }
6368
6370
  function estimateContextBudget(_input, output) {
@@ -7022,6 +7024,9 @@ function incrementTurnCounter() {
7022
7024
  return 0;
7023
7025
  }
7024
7026
  }
7027
+ function resetBlackboxTracker() {
7028
+ _blackboxTracker = null;
7029
+ }
7025
7030
 
7026
7031
  // src/lib/research-audit.js
7027
7032
  import { readFileSync as readFileSync9, existsSync as existsSync10 } from "node:fs";
@@ -7142,7 +7147,7 @@ function researchAudit({ hours = 24, session: sessionFilter } = {}) {
7142
7147
  function normalizeTrend(trend) {
7143
7148
  return trend === "up" || trend === "down" ? trend : "flat";
7144
7149
  }
7145
- function buildStatusPayload({ selection, tiersData, currentModel: currentModel3, creditPercent, version, todos, backendConnected, backendHealthUrl, modelLocked, lockedSlot, lockedModel }) {
7150
+ function buildStatusPayload({ selection, tiersData, currentModel: currentModel3, creditPercent, version, todos, backendConnected, backendHealthUrl, apiFallbackMode, apiFallbackSince, modelLocked, lockedSlot, lockedModel }) {
7146
7151
  const activeSlot = selection?.active_slot || "brain";
7147
7152
  const todoList = Array.isArray(todos) ? todos : [];
7148
7153
  const pendingTodos = todoList.filter((t) => t?.status === "pending").length;
@@ -7169,6 +7174,8 @@ function buildStatusPayload({ selection, tiersData, currentModel: currentModel3,
7169
7174
  todos: { total: totalTodos, pending: pendingTodos },
7170
7175
  backend_connected: Boolean(backendConnected),
7171
7176
  backend_health_url: backendHealthUrl || null,
7177
+ api_fallback: Boolean(apiFallbackMode),
7178
+ api_fallback_since: apiFallbackSince || null,
7172
7179
  model_locked: lockActive,
7173
7180
  locked_slot: resolvedLockedSlot,
7174
7181
  locked_model: resolvedLockedModel,
@@ -7274,6 +7281,7 @@ function diagnoseStructuredFromText(raw, creditPercent = 0) {
7274
7281
  const lines = text.split("\n");
7275
7282
  const files = [];
7276
7283
  const model_probes = [];
7284
+ let apiFallback = { active: false, since: null };
7277
7285
  const suggestions = [];
7278
7286
  let credit = { percent: Number(creditPercent || 0), ok: true, fix: null };
7279
7287
  for (const line of lines) {
@@ -7288,6 +7296,12 @@ function diagnoseStructuredFromText(raw, creditPercent = 0) {
7288
7296
  if (/model-tiers\.json|opencode\.json|delegation-state\.json|auth\.json/i.test(trimmed)) {
7289
7297
  files.push({ path: trimmed, exists: trimmed.includes("\u2705"), ok: trimmed.includes("\u2705"), fix: trimmed.includes("\u2192") ? trimmed.split("\u2192")[1].trim() : void 0 });
7290
7298
  }
7299
+ if (/api fallback/i.test(trimmed)) {
7300
+ apiFallback = {
7301
+ active: /\b(on|active|true)\b/i.test(trimmed) && !/\boff\b/i.test(trimmed),
7302
+ since: trimmed.includes("since") ? trimmed.split(/since/i)[1].trim() : null
7303
+ };
7304
+ }
7291
7305
  if (/credit/i.test(trimmed)) {
7292
7306
  const m = trimmed.match(/(\d+)%/);
7293
7307
  if (m)
@@ -7301,6 +7315,7 @@ function diagnoseStructuredFromText(raw, creditPercent = 0) {
7301
7315
  files,
7302
7316
  model_probes,
7303
7317
  credit,
7318
+ api_fallback: apiFallback,
7304
7319
  locks_clean: true,
7305
7320
  suggestions
7306
7321
  };
@@ -7337,9 +7352,9 @@ function getReportsIndexPath() {
7337
7352
  }
7338
7353
  var REPORTS_DIR2 = getReportsDir();
7339
7354
  var REPORTS_INDEX = getReportsIndexPath();
7340
- var _OC_SID3 = "opencode-" + (process.pid || "x") + "-" + Date.now();
7341
7355
  var currentProjectFingerprint2 = "";
7342
7356
  var currentProjectName2 = "";
7357
+ var currentSessionId2 = "";
7343
7358
  function _handleStateCorruption4(path) {
7344
7359
  const backupDir = join9(getVibeOSHome7(), ".backups");
7345
7360
  mkdirSync9(backupDir, { recursive: true });
@@ -7472,10 +7487,14 @@ function saveReport({ type = "manual", summary = "", findings = null, metrics =
7472
7487
  const parsedMetrics = _parseMetrics(metrics);
7473
7488
  if (_wouldBeDuplicate(type, summary))
7474
7489
  return null;
7475
- const fp2 = fingerprint || currentProjectFingerprint2 || "unknown";
7490
+ const metricProjectFingerprint = typeof parsedMetrics?.projectFingerprint === "string" ? parsedMetrics.projectFingerprint : "";
7491
+ const metricProjectName = typeof parsedMetrics?.projectName === "string" ? parsedMetrics.projectName : "";
7492
+ const fp2 = fingerprint || currentProjectFingerprint2 || currentProjectFingerprint || metricProjectFingerprint || "unknown";
7493
+ const projectName = currentProjectName2 || currentProjectName || metricProjectName || "unknown";
7494
+ const sessionId = currentSessionId2 || getCurrentSessionId() || "unknown";
7476
7495
  const id2 = generateReportId(type, fp2);
7477
7496
  const report = {
7478
- meta: { id: id2, project: currentProjectName2 || "unknown", fingerprint: fp2, type, created: (/* @__PURE__ */ new Date()).toISOString(), sessionId: _OC_SID3 },
7497
+ meta: { id: id2, project: projectName, fingerprint: fp2, type, created: (/* @__PURE__ */ new Date()).toISOString(), sessionId },
7479
7498
  summary,
7480
7499
  findings: parsedFindings,
7481
7500
  metrics: parsedMetrics,
@@ -7910,10 +7929,14 @@ function createTrinityTool(deps) {
7910
7929
  tiers = deps.safeJsonParse(deps.readFileSync(deps.TIERS_FILE, "utf-8")).trinity || {};
7911
7930
  } catch {
7912
7931
  }
7913
- let cheapModel2 = "(unset)";
7932
+ let cheapModel = "(unset)";
7914
7933
  const credit = deps.loadCredit();
7915
7934
  const effectiveLevel = sel.thinking_level || deps.thinkingLevel(credit);
7916
- if (deps.currentModel && sel.selected_model && deps.currentModel !== sel.selected_model) {
7935
+ const apiFallbackActive = typeof deps.isApiFallback === "function" ? deps.isApiFallback() : false;
7936
+ const currentProvider = String(deps.currentModel || "").split("/")[0] || "";
7937
+ const selectedProvider = String(sel.selected_provider || "").split("/")[0] || "";
7938
+ const fallbackModelGuard = currentProvider === "opencode" && selectedProvider !== "opencode";
7939
+ if (deps.currentModel && sel.selected_model && deps.currentModel !== sel.selected_model && !apiFallbackActive && !fallbackModelGuard) {
7917
7940
  try {
7918
7941
  const providers = deps._loadOpenCodeProviders();
7919
7942
  const auth = deps._readAuth();
@@ -7969,7 +7992,7 @@ function createTrinityTool(deps) {
7969
7992
  const topTools = Object.entries(toolBreakdown).filter(([, v]) => v > MIN_TOOL_BREAKDOWN_THRESHOLD).sort((a, b) => b[1] - a[1]).slice(0, 5);
7970
7993
  const brainModel = tiers?.brain?.oc || "(unset)";
7971
7994
  const mediumModel = tiers?.medium?.oc || "(unset)";
7972
- cheapModel2 = tiers?.cheap?.oc || cheapModel2;
7995
+ cheapModel = tiers?.cheap?.oc || cheapModel;
7973
7996
  const activeSlot = sel.active_slot || "brain";
7974
7997
  const lockedSlot = deps._lockedSlot || null;
7975
7998
  const lockedModel = deps._lockedModel || null;
@@ -8011,9 +8034,9 @@ function createTrinityTool(deps) {
8011
8034
  `Stress: ${stressBar} (${stressLabel})`,
8012
8035
  `|`,
8013
8036
  `Guards:`,
8014
- ` Flow: ${sel.flow_enabled !== false ? "ON" : "OFF"}${sel.flow_enforce ? " (extract)" : ""}`,
8037
+ ` Flow: ${sel.flow_enabled !== false ? "ON" : "OFF"}${sel.flow_enabled !== false && sel.flow_enforce ? " (extract)" : ""}`,
8015
8038
  ` TDD: ${sel.tdd_enforce ? "ON" : "OFF"}${sel.tdd_strict !== false ? " strict" : ""}${sel.tdd_quality !== false ? " quality" : ""}`,
8016
- ` Enforce: ${sel.delegation_enforce ? "ON" : "OFF"}${sel.onboarding_mode === "assist" ? " (compatibility)" : " (mandatory)"}`,
8039
+ ` Enforce: ${sel.delegation_enforce ? "ON (mandatory)" : "OFF (compatibility)"}`,
8017
8040
  ` Lock: ${deps._modelLocked ? `LOCK ON${lockedSlot ? ` (${lockedSlot})` : ""}${lockedModel ? ` ${lockedModel}` : ""}` : "LOCK OFF"}`,
8018
8041
  ` Compatibility: ${onboardingMode === "assist" ? "ASSIST (soft defaults, progressive activation)" : "STRICT (full guardrails)"}`,
8019
8042
  `|`,
@@ -8032,7 +8055,7 @@ function createTrinityTool(deps) {
8032
8055
  `Tiers:`,
8033
8056
  ` brain: ${brainModel}${activeSlot === "brain" ? " *" : ""}`,
8034
8057
  ` medium: ${mediumModel}${activeSlot === "medium" ? " *" : ""}`,
8035
- ` cheap: ${cheapModel2}${activeSlot === "cheap" ? " *" : ""}`,
8058
+ ` cheap: ${cheapModel}${activeSlot === "cheap" ? " *" : ""}`,
8036
8059
  ` Labels: ${(LABEL_MODES || []).join(", ")}`
8037
8060
  ];
8038
8061
  return lines.join("\n");
@@ -8094,6 +8117,7 @@ function createTrinityTool(deps) {
8094
8117
  console.error("[vibeOS] WARN: probe error for " + targetModel + ": " + e.message + " - switching anyway");
8095
8118
  }
8096
8119
  deps.writeSessionSlot(deps._OC_SID, slot);
8120
+ deps.writeSelection("slot_locked", true);
8097
8121
  const result = deps.applySlot(slot, deps.directory);
8098
8122
  if (!result.ok)
8099
8123
  return `\u274C Failed to set slot: ${result.reason}`;
@@ -8113,7 +8137,7 @@ function createTrinityTool(deps) {
8113
8137
  return `\u2705 Switched to ${slot} slot (${result.ocModel}). Active now (no restart needed).`;
8114
8138
  }
8115
8139
  if (action === "mode") {
8116
- const builtInIds = ["budget", "quality", "speed", "longrun"];
8140
+ const builtInIds = ["balanced", "budget", "quality", "speed", "longrun", "audit", "forensic"];
8117
8141
  const brandedIds = BRANDED_MODES.map((m) => m.id);
8118
8142
  const allModeIds = [...builtInIds, "auto", ...brandedIds];
8119
8143
  if (!slot)
@@ -8132,6 +8156,8 @@ function createTrinityTool(deps) {
8132
8156
  if (modeEntry) {
8133
8157
  const rawTier = modeEntry.pipeline[0] || "cheap";
8134
8158
  const tierSlot = (/* @__PURE__ */ new Set(["brain", "medium", "cheap"])).has(rawTier) ? rawTier : "cheap";
8159
+ deps.writeSessionSlot(deps._OC_SID, tierSlot);
8160
+ deps.writeSelection("slot_locked", resolvedSlot !== "auto");
8135
8161
  deps.writeSelection("active_slot", tierSlot);
8136
8162
  deps.writeSelection("active_pipeline", modeEntry.pipeline);
8137
8163
  deps.writeSelection("onboarding_mode", modeEntry.tdd === "quality" || modeEntry.enforcement === "strict" ? "strict" : "assist");
@@ -8143,6 +8169,9 @@ function createTrinityTool(deps) {
8143
8169
  const pipelineStr = modeEntry.pipeline.join(" \u2192 ");
8144
8170
  return `Mode set to ${slot.toUpperCase()}. Tier: ${tierSlot}. Pipeline: ${pipelineStr}`;
8145
8171
  }
8172
+ if (resolvedSlot === "auto") {
8173
+ deps.writeSelection("slot_locked", false);
8174
+ }
8146
8175
  return `Mode set to ${slot.toUpperCase()}.`;
8147
8176
  }
8148
8177
  if (action === "thinking") {
@@ -8163,6 +8192,8 @@ function createTrinityTool(deps) {
8163
8192
  if (action === "flow") {
8164
8193
  if (slot === "on" || slot === "off") {
8165
8194
  const ok = deps.writeSelection("flow_enabled", slot === "on");
8195
+ if (ok)
8196
+ deps.writeSelection("flow_enforce", slot === "on");
8166
8197
  if (ok && slot === "on")
8167
8198
  deps.writeSelection("onboarding_mode", "strict");
8168
8199
  return ok ? `\u2705 Flow enforcer ${slot === "on" ? "ENABLED" : "DISABLED"}` : `\u274C Failed to write model-tiers.json`;
@@ -8742,6 +8773,7 @@ ${L.repeat(40)}`);
8742
8773
  if (action === "diagnose") {
8743
8774
  const results = [];
8744
8775
  const ocConfig = join11(deps.OPENCODE_HOME, "opencode.json");
8776
+ const apiFallbackActive = typeof deps.isApiFallback === "function" ? deps.isApiFallback() : false;
8745
8777
  const checks = [
8746
8778
  { path: deps.TIERS_FILE, label: "model-tiers.json" },
8747
8779
  { path: ocConfig, label: "opencode.json" },
@@ -8774,7 +8806,15 @@ ${L.repeat(40)}`);
8774
8806
  results.push({ ok: false, okLabel: "\u274C", label: `${s} slot`, detail: "cannot read model-tiers.json", fix: "run `trinity rebuild` to create it" });
8775
8807
  }
8776
8808
  }
8777
- if (deps.currentModel || !deps.existsSync(deps.TIERS_FILE)) {
8809
+ if (apiFallbackActive) {
8810
+ results.push({
8811
+ ok: false,
8812
+ okLabel: "\u26A0",
8813
+ label: "model probe",
8814
+ detail: "API fallback active",
8815
+ fix: "re-enter `trinity api-token <token>` to retry the remote API"
8816
+ });
8817
+ } else if (deps.currentModel || !deps.existsSync(deps.TIERS_FILE)) {
8778
8818
  try {
8779
8819
  const auth = deps._readAuth();
8780
8820
  const ok = await deps.probeModel(deps.currentModel, auth, deps._loadOpenCodeProviders());
@@ -8793,6 +8833,7 @@ ${L.repeat(40)}`);
8793
8833
  const credit = deps.loadCredit();
8794
8834
  let budget = DIAGNOSE_BUDGET_LINES;
8795
8835
  let totalBal = 0;
8836
+ let cheapModel = "";
8796
8837
  try {
8797
8838
  const j = deps.safeJsonParse(deps.readFileSync(deps.TIERS_FILE, "utf-8"));
8798
8839
  cheapModel = j?.trinity?.cheap?.oc || cheapModel;
@@ -8806,8 +8847,17 @@ ${L.repeat(40)}`);
8806
8847
  totalBal = cache.total;
8807
8848
  } catch {
8808
8849
  }
8850
+ const apiFallbackSince = deps._apiFallbackSince || null;
8851
+ results.push({
8852
+ ok: !apiFallbackActive,
8853
+ okLabel: !apiFallbackActive ? "\u2705" : "\u26A0",
8854
+ label: "api fallback",
8855
+ detail: apiFallbackActive ? `active${apiFallbackSince ? ` since ${apiFallbackSince}` : ""}` : "off",
8856
+ fix: apiFallbackActive ? "re-enter `trinity api-token <token>` to retry the remote API" : null
8857
+ });
8809
8858
  const runway = typeof deps.estimateTurnsRemaining === "function" ? deps.estimateTurnsRemaining(totalBal, cheapModel) : { balanceUsd: totalBal, costPerTurn: deps.modelCostPerTurn?.(cheapModel) ?? null, turnsRemaining: null, unlimited: false };
8810
- const runwayText = runway.costPerTurn === 0 ? `unlimited on ${cheapModel}` : runway.turnsRemaining != null && runway.costPerTurn != null ? `${Number(runway.turnsRemaining).toLocaleString()} turns on ${cheapModel} @ $${deps.formatUsd(runway.costPerTurn)}/turn` : "n/a";
8859
+ const runwayText = runway.costPerTurn === 0 ? `unlimited on ${cheapModel}` : runway.turnsRemaining != null && runway.costPerTurn != null ? `${Number(runway.turnsRemaining).toLocaleString()} turns on ${cheapModel} @ $${deps.formatUsd(runway.costPerTurn)}/turn` : totalBal > 0 ? `balance snapshot present; turn estimate unavailable for ${cheapModel || "cheap slot"}` : "n/a";
8860
+ const runwayOk = totalBal > 0 || runway.turnsRemaining != null || runway.costPerTurn === 0;
8811
8861
  const creditOk = credit >= CREDIT_MIN_OK;
8812
8862
  results.push({
8813
8863
  ok: creditOk,
@@ -8817,11 +8867,11 @@ ${L.repeat(40)}`);
8817
8867
  fix: creditOk ? null : "run `trinity medium` to reduce spend"
8818
8868
  });
8819
8869
  results.push({
8820
- ok: runway.turnsRemaining != null || runway.costPerTurn === 0,
8821
- okLabel: runway.turnsRemaining != null || runway.costPerTurn === 0 ? "\u2705" : "\u274C",
8870
+ ok: runwayOk,
8871
+ okLabel: runwayOk ? "\u2705" : "\u274C",
8822
8872
  label: "runway",
8823
8873
  detail: totalBal > 0 ? `$${totalBal.toFixed(2)} left -> ${runwayText}` : "no cached balance yet",
8824
- fix: runway.turnsRemaining == null && runway.costPerTurn !== 0 ? "wait for a balance snapshot or configure a known cheap slot" : null
8874
+ fix: runwayOk ? null : "wait for a balance snapshot or configure a known cheap slot"
8825
8875
  });
8826
8876
  try {
8827
8877
  const state = deps.safeJsonParse(deps.readFileSync(deps.STATE_FILE, "utf-8"));
@@ -8966,7 +9016,10 @@ ${L.repeat(40)}`);
8966
9016
  return "\u23F8 Blackbox decision engine DISABLED.";
8967
9017
  }
8968
9018
  if (mode === "reset") {
8969
- deps._blackboxTracker = null;
9019
+ if (typeof deps.resetBlackboxTracker === "function")
9020
+ deps.resetBlackboxTracker();
9021
+ else
9022
+ deps._blackboxTracker = null;
8970
9023
  const state = deps.loadBlackboxState();
8971
9024
  const sid = deps._OC_SID;
8972
9025
  delete state.sessions[sid];
@@ -9017,7 +9070,7 @@ ${L.repeat(40)}`);
9017
9070
  " trinity enable/disable Toggle vibeOS plugin on/off",
9018
9071
  " trinity enforce on Block brain-tier writes/edits (save $$)",
9019
9072
  " trinity lock on/off Lock model at session start (skip auto-reconcile)",
9020
- " trinity mode <profile> Set optimization profile (built-in + branded modes)",
9073
+ " trinity mode <profile> Set optimization profile (balanced|budget|quality|speed|longrun|audit|forensic|auto + branded modes)",
9021
9074
  " trinity thinking full|brief|off Set reasoning depth",
9022
9075
  "",
9023
9076
  "GUARDRAILS:",
@@ -9443,16 +9496,17 @@ import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, appen
9443
9496
  import { join as join14, dirname as dirname10, basename as basename6 } from "node:path";
9444
9497
  import { createHash as createHash3 } from "node:crypto";
9445
9498
 
9446
- // src/lib/mode-policy.ts
9499
+ // src/lib/mode-policy.js
9447
9500
  var STRESS_QUALITY_THRESHOLD = 1.5;
9448
9501
  var BASELINE_MODE = "budget";
9449
9502
  var LOOP_REGIMES = /* @__PURE__ */ new Set(["LOOPING", "DIVERGENT"]);
9450
9503
  var QUALITY_REGIMES = /* @__PURE__ */ new Set(["CONVERGING", "CLOSED"]);
9451
- var MANUAL_MODES = /* @__PURE__ */ new Set(["balanced", "quality", "speed", "longrun", "vibemax", "vibeqmax", "vibeultrax"]);
9504
+ var MANUAL_MODES = /* @__PURE__ */ new Set(["balanced", "quality", "speed", "longrun", "audit", "forensic", "vibemax", "vibeqmax", "vibeultrax"]);
9452
9505
  function normalizeMode(mode) {
9453
9506
  const normalized = String(mode || BASELINE_MODE).toLowerCase();
9454
- if (normalized === "auto" || normalized === "") return BASELINE_MODE;
9455
- if (normalized === "budget" || normalized === "quality" || normalized === "speed" || normalized === "longrun" || normalized === "balanced" || normalized === "vibemax" || normalized === "vibeqmax" || normalized === "vibeultrax") {
9507
+ if (normalized === "auto" || normalized === "")
9508
+ return BASELINE_MODE;
9509
+ if (normalized === "budget" || normalized === "quality" || normalized === "speed" || normalized === "longrun" || normalized === "balanced" || normalized === "audit" || normalized === "forensic" || normalized === "vibemax" || normalized === "vibeqmax" || normalized === "vibeultrax") {
9456
9510
  return normalized;
9457
9511
  }
9458
9512
  return BASELINE_MODE;
@@ -9464,9 +9518,12 @@ function isManualOverride(mode) {
9464
9518
  return MANUAL_MODES.has(normalizeMode(mode));
9465
9519
  }
9466
9520
  function chooseEpisodeMode(regime, suggestedMode, stress) {
9467
- if (suggestedMode === "vibeultrax" || suggestedMode === "vibeqmax" || suggestedMode === "vibemax") return suggestedMode;
9468
- if (LOOP_REGIMES.has(regime) || suggestedMode === "speed") return "speed";
9469
- if (QUALITY_REGIMES.has(regime) || suggestedMode === "quality") return "quality";
9521
+ if (suggestedMode === "vibeultrax" || suggestedMode === "vibeqmax" || suggestedMode === "vibemax" || suggestedMode === "audit" || suggestedMode === "forensic")
9522
+ return suggestedMode;
9523
+ if (LOOP_REGIMES.has(regime) || suggestedMode === "speed")
9524
+ return "speed";
9525
+ if (QUALITY_REGIMES.has(regime) || suggestedMode === "quality")
9526
+ return "quality";
9470
9527
  return stress > STRESS_QUALITY_THRESHOLD ? "quality" : "budget";
9471
9528
  }
9472
9529
  function defaultPolicy() {
@@ -9487,15 +9544,19 @@ function defaultPolicy() {
9487
9544
  }
9488
9545
  function modeToSlot(mode) {
9489
9546
  const normalized = normalizeMode(mode);
9490
- if (normalized === "speed") return "medium";
9491
- if (normalized === "quality" || normalized === "longrun" || normalized === "vibeultrax" || normalized === "vibeqmax") return "brain";
9547
+ if (normalized === "speed")
9548
+ return "medium";
9549
+ if (normalized === "quality" || normalized === "longrun" || normalized === "audit" || normalized === "forensic" || normalized === "vibeultrax" || normalized === "vibeqmax")
9550
+ return "brain";
9492
9551
  return "cheap";
9493
9552
  }
9494
9553
  function loadSessionPolicy() {
9495
9554
  const state = loadBlackboxState();
9496
- if (!state.sessions || typeof state.sessions !== "object") state.sessions = {};
9555
+ if (!state.sessions || typeof state.sessions !== "object")
9556
+ state.sessions = {};
9497
9557
  const sid = _OC_SID;
9498
- if (!state.sessions[sid] || typeof state.sessions[sid] !== "object") state.sessions[sid] = {};
9558
+ if (!state.sessions[sid] || typeof state.sessions[sid] !== "object")
9559
+ state.sessions[sid] = {};
9499
9560
  const session = state.sessions[sid];
9500
9561
  if (!session.mode_policy || typeof session.mode_policy !== "object") {
9501
9562
  session.mode_policy = defaultPolicy();
@@ -10141,7 +10202,7 @@ function ensureProjectContext(hookDirectory) {
10141
10202
  return resolved;
10142
10203
  }
10143
10204
  var latestUserIntent = null;
10144
- var _OC_SID4 = "opencode-" + (process.pid || "x") + "-" + Date.now();
10205
+ var _OC_SID3 = "opencode-" + (process.pid || "x") + "-" + Date.now();
10145
10206
  var _latestBlackboxState3 = null;
10146
10207
  var _latestBlackboxLoopMsg3 = null;
10147
10208
  var _latestBlackboxPivotMsg3 = null;
@@ -10283,7 +10344,7 @@ function syncControlSettings(cv, options = {}) {
10283
10344
  if (!cv)
10284
10345
  return;
10285
10346
  try {
10286
- const sid = _OC_SID4;
10347
+ const sid = _OC_SID3;
10287
10348
  if (!cv.agent_mode) {
10288
10349
  try {
10289
10350
  clearWorkspaceFollowupPauseForSession(sid);
@@ -10309,16 +10370,19 @@ function syncControlSettings(cv, options = {}) {
10309
10370
  }
10310
10371
  writeIf("enabled", true);
10311
10372
  const compatibilityMode = currentSel.onboarding_mode === "assist";
10373
+ const flowManuallyDisabled = currentSel.flow_enabled === false && currentSel.flow_enforce === false;
10312
10374
  writeIf("delegation_enforce", compatibilityMode ? cv.enforcement_mode === "strict" : cv.enforcement_mode !== "relaxed");
10313
- if (compatibilityMode) {
10314
- writeIf("flow_enabled", cv.flow_mode === "strict");
10315
- writeIf("flow_enforce", cv.flow_mode === "strict");
10316
- } else if (cv.flow_mode === "audit") {
10317
- writeIf("flow_enabled", true);
10318
- writeIf("flow_enforce", false);
10319
- } else {
10320
- writeIf("flow_enabled", true);
10321
- writeIf("flow_enforce", true);
10375
+ if (!flowManuallyDisabled) {
10376
+ if (compatibilityMode) {
10377
+ writeIf("flow_enabled", cv.flow_mode === "strict");
10378
+ writeIf("flow_enforce", cv.flow_mode === "strict");
10379
+ } else if (cv.flow_mode === "audit") {
10380
+ writeIf("flow_enabled", true);
10381
+ writeIf("flow_enforce", false);
10382
+ } else {
10383
+ writeIf("flow_enabled", true);
10384
+ writeIf("flow_enforce", true);
10385
+ }
10322
10386
  }
10323
10387
  if (compatibilityMode) {
10324
10388
  writeIf("tdd_enforce", cv.tdd_mode === "strict");
@@ -10341,7 +10405,8 @@ function syncControlSettings(cv, options = {}) {
10341
10405
  }
10342
10406
  }
10343
10407
  const slot = cv.tier_bias;
10344
- if (slot && slot !== "auto") {
10408
+ const slotLocked = currentSel.slot_locked === true;
10409
+ if (slot && slot !== "auto" && !slotLocked) {
10345
10410
  const existingSlot = loadSessionSlot(sid);
10346
10411
  if (existingSlot !== slot) {
10347
10412
  writeSessionSlot2(sid, slot);
@@ -10503,7 +10568,7 @@ async function trackBlackbox(messages) {
10503
10568
  const tracker = getBlackboxTracker();
10504
10569
  const localState = tracker.update(latestUserIntent);
10505
10570
  const state = loadBlackboxState();
10506
- const sid = _OC_SID4;
10571
+ const sid = _OC_SID3;
10507
10572
  ensureProjectContext(process.cwd() || "");
10508
10573
  const serialized = tracker.serialize();
10509
10574
  const existingSession = state.sessions[sid] || {};
@@ -10831,7 +10896,7 @@ var onSystemTransform = async (_input, output) => {
10831
10896
  const regime2 = _latestBlackboxState3?.sub_regime || classifyTurnSimple2(latestUserIntent || "");
10832
10897
  const calRecord = JSON.stringify({
10833
10898
  ts: (/* @__PURE__ */ new Date()).toISOString(),
10834
- sid: _OC_SID4,
10899
+ sid: _OC_SID3,
10835
10900
  mode: _currentTemplate,
10836
10901
  regime: regime2,
10837
10902
  stress: stressScore,
@@ -10998,7 +11063,7 @@ function readLifetimeSavings2() {
10998
11063
  reconcileStateFromLedger();
10999
11064
  const raw = readFileSync14(STATE_FILE2, "utf-8");
11000
11065
  const state = safeJsonParse3(raw);
11001
- const ses = state?.sessions?.[typeof _OC_SID5 !== "undefined" ? _OC_SID5 : ""] || {};
11066
+ const ses = state?.sessions?.[getSessionId()] || {};
11002
11067
  return {
11003
11068
  ltTasks: roundUsd2(state?.lifetime?.total_savings_usd || 0),
11004
11069
  ltCache: roundUsd2(state?.lifetime?.cache_savings_usd || 0),
@@ -11026,7 +11091,9 @@ function readLifetimeSavings2() {
11026
11091
  return { ltTasks: 0, ltCache: 0, ltCost: 0, count: 0, sesTasks: 0, sesCache: 0, sesTaskDelegations: 0, sesDuration: 0, sesRatePerHour: 0, sesTrend: "", sesToolBreakdown: {}, sesModelTurns: {}, quality_avg: 0 };
11027
11092
  }
11028
11093
  }
11029
- var _OC_SID5 = "opencode-" + (process.pid || "x") + "-" + Date.now();
11094
+ function getSessionId() {
11095
+ return getCurrentSessionId();
11096
+ }
11030
11097
  function scoreTaskQuality(outputText, promptText) {
11031
11098
  if (typeof outputText !== "string" || outputText.length === 0)
11032
11099
  return 0;
@@ -11053,7 +11120,7 @@ function scoreTaskQuality(outputText, promptText) {
11053
11120
  function readRewardSignals() {
11054
11121
  try {
11055
11122
  const state = loadBlackboxState();
11056
- const session = state?.sessions?.[_OC_SID5] || {};
11123
+ const session = state?.sessions?.[getSessionId()] || {};
11057
11124
  const policy = session?.mode_policy || {};
11058
11125
  return {
11059
11126
  stableStreak: Math.max(0, Number(policy.stable_streak || 0)),
@@ -11130,7 +11197,8 @@ async function _appendFooter(input, output, directory3) {
11130
11197
  return;
11131
11198
  const { ltTasks, ltCache, ltCost, count, sesTasks, sesEdit, sesCredit, sesC7, sesQuota, sesCache, sesTaskDelegations, sesDuration, sesRatePerHour, sesTrend, sesToolBreakdown, sesModelTurns, quality_avg } = readLifetimeSavings2();
11132
11199
  const { stableStreak, problemStreak } = readRewardSignals();
11133
- const sessionSlot = loadBlackboxState()?.sessions?.[_OC_SID5]?.active_slot || loadSessionSlot(_OC_SID5);
11200
+ const sid = getSessionId();
11201
+ const sessionSlot = loadBlackboxState()?.sessions?.[sid]?.active_slot || loadSessionSlot(sid);
11134
11202
  const slot = sessionSlot || loadSelection3().active_slot || "brain";
11135
11203
  const brainModel = slot === "brain" ? TRINITY_BRAIN || currentModel : slot === "medium" ? TRINITY_MEDIUM || currentModel : TRINITY_CHEAP || currentModel || "";
11136
11204
  let liveModel = "";
@@ -11164,7 +11232,7 @@ async function _appendFooter(input, output, directory3) {
11164
11232
  type: "session",
11165
11233
  summary: "Session cost: $" + formatUsd(ltCost) + " | cache saved: $" + formatUsd(ltCache) + " | delegation saved: $" + formatUsd(Number(sesTasks || 0)) + " | task delegations: " + Number(sesTaskDelegations || 0),
11166
11234
  metrics: {
11167
- sessionId: _OC_SID5,
11235
+ sessionId: sid,
11168
11236
  projectFingerprint: currentProjectFingerprint || "unknown",
11169
11237
  projectName: currentProjectName || "unknown",
11170
11238
  sessionCost: ltCost,
@@ -11253,7 +11321,7 @@ ${vibeLine}`;
11253
11321
  syncOutcomeToApi(finalOutcome);
11254
11322
  try {
11255
11323
  mkdirSync11(getVibeOSHome10(), { recursive: true });
11256
- appendFileSync6(join15(getVibeOSHome10(), "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: _OC_SID5, outcome: finalOutcome }) + "\n");
11324
+ appendFileSync6(join15(getVibeOSHome10(), "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: getSessionId(), outcome: finalOutcome }) + "\n");
11257
11325
  } catch {
11258
11326
  }
11259
11327
  }
@@ -13323,6 +13391,18 @@ var onToolExecuteAfter = async (input, output) => {
13323
13391
  return obj.message;
13324
13392
  return obj;
13325
13393
  }
13394
+ if (enforcementBlocked) {
13395
+ const target = _payload(output);
13396
+ const blockMsg = pendingUiNote || `[delegation] ${String(input?.tool || "tool")} blocked by enforcement`;
13397
+ const replaceIfNeeded = (key) => {
13398
+ if (typeof target?.[key] === "string" && /oldString not found/i.test(target[key]))
13399
+ target[key] = blockMsg;
13400
+ };
13401
+ replaceIfNeeded("error");
13402
+ replaceIfNeeded("result");
13403
+ replaceIfNeeded("text");
13404
+ replaceIfNeeded("content");
13405
+ }
13326
13406
  if (pendingUiNote) {
13327
13407
  const target = _payload(output);
13328
13408
  if (enforcementBlocked) {
@@ -13639,6 +13719,7 @@ function ensureDeferredBootstrap() {
13639
13719
  } catch {
13640
13720
  }
13641
13721
  }
13722
+ var _apiFallbackSince2 = null;
13642
13723
  var activeJob2 = null;
13643
13724
  var fp = "";
13644
13725
  var _mcpServerRuntime = null;
@@ -14044,6 +14125,10 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14044
14125
  setBlackboxEnabled,
14045
14126
  loadBlackboxState,
14046
14127
  saveBlackboxState,
14128
+ isApiFallback: () => isApiFallback(),
14129
+ get _apiFallbackSince() {
14130
+ return _apiFallbackSince2;
14131
+ },
14047
14132
  reportsIndex: reportsIndexStable,
14048
14133
  saveReportsIndex: saveReportsIndexStable,
14049
14134
  backupFile: backupFileStable,
@@ -14058,6 +14143,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14058
14143
  getTodos,
14059
14144
  markTodoDone,
14060
14145
  syncFlowTodosToNative,
14146
+ resetBlackboxTracker,
14061
14147
  get _blackboxTracker() {
14062
14148
  return getBlackboxTracker();
14063
14149
  },
@@ -14293,6 +14379,8 @@ ${report.narrative}`);
14293
14379
  fallbackThinking: thinkingLevel(loadCredit()),
14294
14380
  backendConnected: isApiConnected2(),
14295
14381
  backendHealthUrl: `${VIBEOS_API_URL}/health`,
14382
+ apiFallbackMode: isApiFallback(),
14383
+ apiFallbackSince: _apiFallbackSince2,
14296
14384
  modelLocked: _modelLocked,
14297
14385
  lockedSlot: _lockedSlot,
14298
14386
  lockedModel: _lockedModel
@@ -14460,6 +14548,7 @@ export {
14460
14548
  enforceTestFile,
14461
14549
  extractExports,
14462
14550
  getBlackboxResolution,
14551
+ getCurrentSessionId,
14463
14552
  getScratchpadHit,
14464
14553
  getSessionIndexPath,
14465
14554
  getSessionScratchpadDir,
@@ -14482,6 +14571,9 @@ export {
14482
14571
  scoreStress,
14483
14572
  server,
14484
14573
  setCurrentModel,
14574
+ setCurrentProjectFingerprint,
14575
+ setCurrentProjectName,
14576
+ setCurrentSessionId,
14485
14577
  setCurrentTier,
14486
14578
  setTrinityBrain,
14487
14579
  setTrinityCheap,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.24.13",
3
+ "version": "0.24.14",
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",