vibeostheog 0.22.22 → 0.22.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.22.22",
3
+ "version": "0.22.24",
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",
@@ -89,8 +89,5 @@
89
89
  "eslint": "^10.4.0",
90
90
  "express": "^5.2.1",
91
91
  "typescript": "^5.9.3"
92
- },
93
- "dependencies": {
94
- "vibeoscore": "file:vibeoscore-1.0.2.tgz"
95
92
  }
96
93
  }
@@ -535,10 +535,6 @@ export function setApiToken(newToken) {
535
535
  persistPrimaryApiEnvState({ token: VIBEOS_API_TOKEN, disabled: false });
536
536
  if (_anomalyDetector)
537
537
  _anomalyDetector.reset();
538
- _apiClient = null;
539
- _apiFallbackMode = false;
540
- _apiFallbackSince = null;
541
- resetApiConnection();
542
538
  console.error("[vibeOS] API token updated via setApiToken");
543
539
  }
544
540
  catch (e) {
@@ -675,8 +671,6 @@ function syncApiTokenFromDisk() {
675
671
  VIBEOS_API_DISABLED = false;
676
672
  VIBEOS_API_TOKEN ||= EMBEDDED_API_TOKEN;
677
673
  VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && (!!VIBEOS_API_TOKEN || !!VIBEOS_API_BOOTSTRAP_TOKEN);
678
- _apiFallbackMode = false;
679
- _apiFallbackSince = null;
680
674
  }
681
675
  }
682
676
  export function getApiClient() {
@@ -220,21 +220,21 @@ async function _appendFooter(input, output, directory) {
220
220
  saveReport({
221
221
  type: "session",
222
222
  summary: "Session cost: $" + formatUsd(ltCost) + " | cache saved: $" + formatUsd(ltCache) + " | delegation saved: $" + formatUsd(Number(sesTasks || 0)) + " | task delegations: " + Number(sesTaskDelegations || 0),
223
- metrics: {
224
- sessionId: _OC_SID,
225
- projectFingerprint: currentProjectFingerprint || "unknown",
226
- projectName: currentProjectName || "unknown",
227
- sessionCost: ltCost,
223
+ metrics: {
224
+ sessionId: _OC_SID,
225
+ projectFingerprint: currentProjectFingerprint || "unknown",
226
+ projectName: currentProjectName || "unknown",
227
+ sessionCost: ltCost,
228
228
  cacheSavings: ltCache,
229
229
  delegationSavingsUsd: sesTasks,
230
- taskDelegationCount: sesTaskDelegations,
231
- // Backward compatibility (legacy field historically misnamed)
232
- tasksDelegated: sesTaskDelegations,
233
- model: resolvedModel || currentModel,
234
- slot: loadSelection().active_slot || "unknown",
235
- editSavings: sesEdit,
236
- creditSavings: sesCredit,
237
- context7Savings: sesC7,
230
+ taskDelegationCount: sesTaskDelegations,
231
+ // Backward compatibility (legacy field historically misnamed)
232
+ tasksDelegated: sesTaskDelegations,
233
+ model: resolvedModel || currentModel,
234
+ slot: loadSelection().active_slot || "unknown",
235
+ editSavings: sesEdit,
236
+ creditSavings: sesCredit,
237
+ context7Savings: sesC7,
238
238
  quotaSavings: sesQuota,
239
239
  },
240
240
  tags: ["auto", "cost"],
@@ -111,6 +111,12 @@ export function classify(m) {
111
111
  return "high";
112
112
  if (MID_TIER_RE.test(s))
113
113
  return "mid";
114
+ // Fallback: strip provider prefix and test bare name
115
+ const bare = s.includes("/") ? s.split("/").slice(1).join("/") : s;
116
+ if (HIGH_TIER_RE.test(bare))
117
+ return "high";
118
+ if (MID_TIER_RE.test(bare))
119
+ return "mid";
114
120
  return "budget";
115
121
  }
116
122
  // Map a model ID to a human-readable label with tier icon.
@@ -160,9 +166,9 @@ export function resolveExecutionIdentity(modelId, directory = "") {
160
166
  const normalized = normalizeModelId(resolved || raw);
161
167
  const quality = isModelFree(resolved || raw)
162
168
  ? "free"
163
- : HIGH_TIER_RE.test(normalized)
169
+ : classify(resolved || raw) === "high"
164
170
  ? "brain"
165
- : MID_TIER_RE.test(normalized)
171
+ : classify(resolved || raw) === "mid"
166
172
  ? "medium"
167
173
  : "cheap";
168
174
  return {
@@ -260,6 +266,8 @@ export function trendDisplay(sesTrend) {
260
266
  const CACHE_SAVED_PER_1M_INPUT_TOKENS = 0.10;
261
267
  // Approximate bytes per token for JSON/text content (varies 3-6, use 4 as safe estimate).
262
268
  const BYTES_PER_TOKEN = 4;
269
+ // Average tokens per turn for cost estimation heuristic.
270
+ const AVG_TOKENS_PER_TURN = 375;
263
271
  export function parseOpenRouterInputPer1M(modelRow) {
264
272
  const p = modelRow?.pricing || {};
265
273
  const inTok = Number(p.prompt ?? p.input ?? p.request);
@@ -300,7 +308,7 @@ export function cacheSavePer1MInputTokens(model) {
300
308
  }
301
309
  const turnCost = modelCostPerTurn(model);
302
310
  if (Number.isFinite(turnCost) && turnCost > 0) {
303
- return Math.round(turnCost * 375 * 100) / 100;
311
+ return Math.round(turnCost * AVG_TOKENS_PER_TURN * 100) / 100;
304
312
  }
305
313
  return CACHE_SAVED_PER_1M_INPUT_TOKENS;
306
314
  }
@@ -929,7 +937,13 @@ function resolveConfiguredModelId(model, configs = []) {
929
937
  matches.add(id);
930
938
  }
931
939
  }
932
- return matches.size === 1 ? [...matches][0] : raw;
940
+ if (matches.size === 0)
941
+ return raw;
942
+ if (matches.size === 1)
943
+ return [...matches][0];
944
+ // Multiple providers have this model — prefer provider-qualified name over bare
945
+ const qualified = [...matches].find(m => m.includes("/"));
946
+ return qualified || raw;
933
947
  }
934
948
  export function resolveDisplayModelId(model, directory = "") {
935
949
  const raw = String(model || "").trim();
@@ -3,7 +3,7 @@ function getRuntimeState() {
3
3
  const g = globalThis;
4
4
  if (!g[RUNTIME_KEY]) {
5
5
  g[RUNTIME_KEY] = {
6
- apiConnected: false,
6
+ apiConnected: true,
7
7
  apiFallbackMode: false,
8
8
  apiFallbackSince: null,
9
9
  sessionId: "opencode-" + (process.pid || "x") + "-" + Date.now(),
package/src/lib/state.js CHANGED
@@ -47,6 +47,8 @@ const MAX_SCRATCHPAD_FILES = 1000;
47
47
  const MAX_SCRATCHPAD_BYTES = 10 * 1024 * 1024;
48
48
  const MAX_SESSION_SCRATCHPAD_FILES = 200;
49
49
  const MAX_SESSION_SCRATCHPAD_BYTES = 2 * 1024 * 1024;
50
+ const MAX_PTR_CANDIDATES = 50;
51
+ const SUMMARY_HEAD_TRUNCATE = 500;
50
52
  function getVibeOSHome() {
51
53
  return VIBEOS_CONTEXT.getStore()?.home || process.env.VIBEOS_HOME || join(process.env.HOME || "", ".claude");
52
54
  }
@@ -839,7 +841,7 @@ function scanRecentScratchpad(dir, titleCase, maxScan = 2000) {
839
841
  const ptrFiles = entries.filter(e => e.endsWith(".ptr"));
840
842
  const ptrCandidates = [];
841
843
  for (const pf of ptrFiles) {
842
- if (ptrCandidates.length >= 50)
844
+ if (ptrCandidates.length >= MAX_PTR_CANDIDATES)
843
845
  break;
844
846
  try {
845
847
  const st = statSync(join(dir, pf));
@@ -902,14 +904,14 @@ function getScratchpadHit(toolLower, args, baseDir = null) {
902
904
  }
903
905
  }
904
906
  catch { }
907
+ }
908
+ if (!fullPath) {
909
+ const recent = scanRecentScratchpad(sessionDir, titleCase, 2000);
910
+ if (recent)
911
+ return recent;
912
+ return null;
913
+ }
905
914
  }
906
- if (!fullPath) {
907
- const recent = scanRecentScratchpad(sessionDir, titleCase, 2000);
908
- if (recent)
909
- return recent;
910
- return null;
911
- }
912
- }
913
915
  try {
914
916
  const st = statSync(fullPath);
915
917
  const ageSec = (Date.now() - st.mtimeMs) / 1000;
@@ -1011,7 +1013,7 @@ function _pruneScratchpadDir(targetDir, opts = {}) {
1011
1013
  if (!existsSync(summaryPath))
1012
1014
  try {
1013
1015
  const content = readFileSync(fullPath, "utf-8");
1014
- writeFileSync(summaryPath, content.slice(0, 500).replace(/\n+/g, " ").trim() + (content.length > 500 ? "…" : ""));
1016
+ writeFileSync(summaryPath, content.slice(0, SUMMARY_HEAD_TRUNCATE).replace(/\n+/g, " ").trim() + (content.length > SUMMARY_HEAD_TRUNCATE ? "…" : ""));
1015
1017
  }
1016
1018
  catch { }
1017
1019
  const head = _readHead(fullPath);
@@ -210,11 +210,20 @@ function _modelCost(id) {
210
210
  function _modelTier(id) {
211
211
  if (!id)
212
212
  return "budget";
213
+ // Test both the full provider-qualified ID and the bare name
213
214
  const high = HIGH_TIER_RE?.test?.(id);
214
215
  if (high)
215
216
  return "high";
216
217
  const mid = MID_TIER_RE?.test?.(id);
217
- return mid ? "mid" : "budget";
218
+ if (mid)
219
+ return "mid";
220
+ // Fallback: strip provider prefix and test bare name
221
+ const bare = String(id).includes("/") ? String(id).split("/").slice(1).join("/") : String(id);
222
+ if (HIGH_TIER_RE?.test?.(bare))
223
+ return "high";
224
+ if (MID_TIER_RE?.test?.(bare))
225
+ return "mid";
226
+ return "budget";
218
227
  }
219
228
  export async function discoverAvailableModels(providers, auth) {
220
229
  const all = collectConfiguredProviderModels(providers);
@@ -319,12 +328,12 @@ export function classifyAndRankModels(models) {
319
328
  }
320
329
  if (unique.length === 0)
321
330
  return null;
322
- const normalizeModelId = (id) => String(id || "").toLowerCase()
331
+ const normalizeModelIdLocal = (id) => String(id || "").toLowerCase()
323
332
  .replace(/\./g, "-")
324
333
  .replace(/^(openrouter|opencode|deepseek|anthropic|google)\//, "");
325
- const isDeprecatedDeepseekChat = (id) => normalizeModelId(id).includes("deepseek-chat");
334
+ const isDeprecatedDeepseekChat = (id) => normalizeModelIdLocal(id).includes("deepseek-chat");
326
335
  const hasReplacementDeepseek = unique.some((m) => {
327
- const raw = normalizeModelId(m.id);
336
+ const raw = normalizeModelIdLocal(m.id);
328
337
  return raw.startsWith("deepseek-") && !raw.includes("deepseek-chat");
329
338
  });
330
339
  const ranked = hasReplacementDeepseek
@@ -333,7 +342,7 @@ export function classifyAndRankModels(models) {
333
342
  if (ranked.length === 0)
334
343
  return null;
335
344
  const modelPreference = (id) => {
336
- const raw = normalizeModelId(id);
345
+ const raw = normalizeModelIdLocal(id);
337
346
  if (raw.includes("deepseek-v4-flash"))
338
347
  return 2;
339
348
  if (raw.includes("deepseek-chat"))
@@ -3,6 +3,16 @@ import { join } from "node:path";
3
3
  import { LABEL_MODES, buildDeterministicTrinity, resolveExecutionIdentity } from "./pricing.js";
4
4
  import { BRANDED_MODES, RUNTIME_MODES } from "./mode-router.js";
5
5
  import { invalidateApiToken } from "./api-client.js";
6
+ // ── Named constants (magic number extraction) ────────────────────────
7
+ const MIN_TOOL_BREAKDOWN_THRESHOLD = 0.005;
8
+ const STRESS_GAUGE_CRITICAL = 0.85;
9
+ const STRESS_GAUGE_HIGH = 0.7;
10
+ const STRESS_GAUGE_ELEVATED = 0.5;
11
+ const STRESS_GAUGE_CALM = 0.3;
12
+ const STRESS_GAUGE_MIN = 0.1;
13
+ const MOMENTUM_SIGNIFICANT_THRESHOLD = 0.3;
14
+ const DIAGNOSE_BUDGET_LINES = 50;
15
+ const CREDIT_MIN_OK = 40;
6
16
  export function createTrinityTool(deps) {
7
17
  return {
8
18
  description: "Control the vibeOS plugin and active model slot. " +
@@ -57,7 +67,7 @@ export function createTrinityTool(deps) {
57
67
  const sesRate = sv.sesRatePerHour || 0;
58
68
  const missedC7 = sv.missedC7 || 0;
59
69
  const toolBreakdown = sv.sesToolBreakdown || {};
60
- const topTools = Object.entries(toolBreakdown).filter(([, v]) => v > 0.005).sort((a, b) => b[1] - a[1]).slice(0, 5);
70
+ const topTools = Object.entries(toolBreakdown).filter(([, v]) => v > MIN_TOOL_BREAKDOWN_THRESHOLD).sort((a, b) => b[1] - a[1]).slice(0, 5);
61
71
  const brainModel = tiers?.brain?.oc || "(unset)";
62
72
  const mediumModel = tiers?.medium?.oc || "(unset)";
63
73
  cheapModel = tiers?.cheap?.oc || cheapModel;
@@ -66,8 +76,8 @@ export function createTrinityTool(deps) {
66
76
  const lockedModel = deps._lockedModel || null;
67
77
  const onboardingMode = sel.onboarding_mode || "strict";
68
78
  const stressScore = deps.latestUserIntent ? deps.scoreStress(deps.latestUserIntent) : 0;
69
- const stressBar = stressScore > 0.85 ? "█" : stressScore > 0.7 ? "▆" : stressScore > 0.5 ? "▅" : stressScore > 0.3 ? "▃" : stressScore > 0.1 ? "▂" : "▁";
70
- const stressLabel = stressScore > 0.7 ? "high" : stressScore > 0.4 ? "elevated" : stressScore > 0.1 ? "calm" : "none";
79
+ const stressBar = stressScore > STRESS_GAUGE_CRITICAL ? "█" : stressScore > STRESS_GAUGE_HIGH ? "▆" : stressScore > STRESS_GAUGE_ELEVATED ? "▅" : stressScore > STRESS_GAUGE_CALM ? "▃" : stressScore > STRESS_GAUGE_MIN ? "▂" : "▁";
80
+ const stressLabel = stressScore > STRESS_GAUGE_HIGH ? "high" : stressScore > 0.4 ? "elevated" : stressScore > STRESS_GAUGE_MIN ? "calm" : "none";
71
81
  const totalTurns = (sv.sesModelTurns?.brain || 0) + (sv.sesModelTurns?.worker || 0);
72
82
  const brainPct = totalTurns > 0 ? Math.round((sv.sesModelTurns.brain / totalTurns) * 100) : 0;
73
83
  const workerPct = 100 - brainPct;
@@ -80,7 +90,7 @@ export function createTrinityTool(deps) {
80
90
  try {
81
91
  const res = deps._latestBlackboxState || deps.getBlackboxResolution();
82
92
  if (res && res.n_interactions > 3) {
83
- const momentumIcon = res.momentum > 0.3 ? "up up" : res.momentum > 0 ? "up" : res.momentum < -0.3 ? "down down" : res.momentum < 0 ? "down" : "flat";
93
+ const momentumIcon = res.momentum > MOMENTUM_SIGNIFICANT_THRESHOLD ? "up up" : res.momentum > 0 ? "up" : res.momentum < -MOMENTUM_SIGNIFICANT_THRESHOLD ? "down down" : res.momentum < 0 ? "down" : "flat";
84
94
  const loopTag = res.is_looping ? " (loop)" : "";
85
95
  decisionLine = `${res.resolution} ${res.sub_regime} ${momentumIcon}${loopTag}`;
86
96
  }
@@ -863,7 +873,7 @@ export function createTrinityTool(deps) {
863
873
  results.push({ ok: false, okLabel: "\u274c", label: "model probe", detail: "no current model detected" });
864
874
  }
865
875
  const credit = deps.loadCredit();
866
- let budget = 50;
876
+ let budget = DIAGNOSE_BUDGET_LINES;
867
877
  let totalBal = 0;
868
878
  try {
869
879
  const j = deps.safeJsonParse(deps.readFileSync(deps.TIERS_FILE, "utf-8"));
@@ -886,7 +896,7 @@ export function createTrinityTool(deps) {
886
896
  : runway.turnsRemaining != null && runway.costPerTurn != null
887
897
  ? `${Number(runway.turnsRemaining).toLocaleString()} turns on ${cheapModel} @ $${deps.formatUsd(runway.costPerTurn)}/turn`
888
898
  : "n/a";
889
- const creditOk = credit >= 40;
899
+ const creditOk = credit >= CREDIT_MIN_OK;
890
900
  results.push({
891
901
  ok: creditOk, okLabel: creditOk ? "\u2705" : "\u274c",
892
902
  label: "credits",