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 +1 -4
- package/src/lib/api-client.js +0 -6
- package/src/lib/hooks/footer.js +13 -13
- package/src/lib/pricing.js +18 -4
- package/src/lib/runtime-state.js +1 -1
- package/src/lib/state.js +11 -9
- package/src/lib/trinity-rebuild.js +14 -5
- package/src/lib/trinity-tool.js +16 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeostheog",
|
|
3
|
-
"version": "0.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
|
}
|
package/src/lib/api-client.js
CHANGED
|
@@ -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() {
|
package/src/lib/hooks/footer.js
CHANGED
|
@@ -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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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"],
|
package/src/lib/pricing.js
CHANGED
|
@@ -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
|
-
:
|
|
169
|
+
: classify(resolved || raw) === "high"
|
|
164
170
|
? "brain"
|
|
165
|
-
:
|
|
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 *
|
|
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
|
-
|
|
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();
|
package/src/lib/runtime-state.js
CHANGED
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 >=
|
|
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,
|
|
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
|
-
|
|
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
|
|
331
|
+
const normalizeModelIdLocal = (id) => String(id || "").toLowerCase()
|
|
323
332
|
.replace(/\./g, "-")
|
|
324
333
|
.replace(/^(openrouter|opencode|deepseek|anthropic|google)\//, "");
|
|
325
|
-
const isDeprecatedDeepseekChat = (id) =>
|
|
334
|
+
const isDeprecatedDeepseekChat = (id) => normalizeModelIdLocal(id).includes("deepseek-chat");
|
|
326
335
|
const hasReplacementDeepseek = unique.some((m) => {
|
|
327
|
-
const raw =
|
|
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 =
|
|
345
|
+
const raw = normalizeModelIdLocal(id);
|
|
337
346
|
if (raw.includes("deepseek-v4-flash"))
|
|
338
347
|
return 2;
|
|
339
348
|
if (raw.includes("deepseek-chat"))
|
package/src/lib/trinity-tool.js
CHANGED
|
@@ -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 >
|
|
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 >
|
|
70
|
-
const stressLabel = stressScore >
|
|
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 >
|
|
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 =
|
|
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 >=
|
|
899
|
+
const creditOk = credit >= CREDIT_MIN_OK;
|
|
890
900
|
results.push({
|
|
891
901
|
ok: creditOk, okLabel: creditOk ? "\u2705" : "\u274c",
|
|
892
902
|
label: "credits",
|