vibeostheog 0.24.17 → 0.24.18

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,8 @@
1
+ ## 0.24.18
2
+ - fix: keep Return codename through 0.24 patch releases (#143)
3
+ - fix: preserve live metrics context in reports (#137)
4
+
5
+
1
6
  ## 0.24.16
2
7
  - fix: serialize model tiers writes
3
8
  - test: add concurrent tiers write regression
package/dist/vibeOS.js CHANGED
@@ -3279,9 +3279,9 @@ var MAX_SESSION_SCRATCHPAD_FILES = 200;
3279
3279
  var MAX_SESSION_SCRATCHPAD_BYTES = 2 * 1024 * 1024;
3280
3280
  var CORRUPTION_BACKUP_MAX = 5;
3281
3281
  var CORRUPTION_BACKUP_TTL_MS = 24 * 60 * 60 * 1e3;
3282
- var LEDGER_ROTATE_MAX_BYTES = 1 * 1024 * 1024;
3283
- var LEDGER_ROTATE_MAX_LINES = 5e4;
3284
- var LEDGER_ROTATE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
3282
+ var LEDGER_ROTATE_MAX_BYTES = 256 * 1024;
3283
+ var LEDGER_ROTATE_MAX_LINES = 1e4;
3284
+ var LEDGER_ROTATE_MAX_AGE_MS = 48 * 60 * 60 * 1e3;
3285
3285
  var ACTIVE_JOBS_STALE_MS = 72 * 60 * 60 * 1e3;
3286
3286
  var MAX_PTR_CANDIDATES = 50;
3287
3287
  var SUMMARY_HEAD_TRUNCATE = 500;
@@ -3440,6 +3440,19 @@ function _pruneCorruptionBackups(backupDir) {
3440
3440
  } catch {
3441
3441
  }
3442
3442
  }
3443
+ var _startupMaintenanceHome = "";
3444
+ function runStartupMaintenanceOnce() {
3445
+ try {
3446
+ const home = getVibeOSHome3();
3447
+ if (!home || home === _startupMaintenanceHome)
3448
+ return;
3449
+ _startupMaintenanceHome = home;
3450
+ _pruneCorruptionBackups(join4(home, ".backups"));
3451
+ loadActiveJobs();
3452
+ _compactSavingsLedgerIfNeeded();
3453
+ } catch {
3454
+ }
3455
+ }
3443
3456
  function _handleStateCorruption(path) {
3444
3457
  const backupDir = join4(VIBEOS_HOME, ".backups");
3445
3458
  mkdirSync3(backupDir, { recursive: true });
@@ -3724,7 +3737,42 @@ function loadBlackboxState() {
3724
3737
  _handleStateCorruption(blackboxFile);
3725
3738
  return { enabled: false, sessions: {} };
3726
3739
  }
3727
- return safeJsonParse3(readFileSync4(blackboxFile, "utf-8")) || { enabled: false, sessions: {} };
3740
+ const raw = safeJsonParse3(readFileSync4(blackboxFile, "utf-8")) || { enabled: false, sessions: {} };
3741
+ if (!raw.sessions || typeof raw.sessions !== "object")
3742
+ raw.sessions = {};
3743
+ const now = Date.now();
3744
+ let changed = false;
3745
+ for (const [sid, session] of Object.entries(raw.sessions)) {
3746
+ if (!session || typeof session !== "object")
3747
+ continue;
3748
+ const next = { ...session };
3749
+ const createdAtRaw = typeof next.createdAt === "string" ? next.createdAt : "";
3750
+ const updatedAtRaw = typeof next.updatedAt === "string" ? next.updatedAt : "";
3751
+ const startedRaw = typeof next.started === "string" ? next.started : "";
3752
+ const sessionStartedRaw = typeof next.session_started_at === "string" ? next.session_started_at : "";
3753
+ const anchorRaw = [createdAtRaw, updatedAtRaw, startedRaw, sessionStartedRaw].find((v) => v && !Number.isNaN(Date.parse(v)));
3754
+ const anchorMs = anchorRaw ? Date.parse(anchorRaw) : NaN;
3755
+ if (!Number.isFinite(Date.parse(createdAtRaw))) {
3756
+ next.createdAt = Number.isFinite(anchorMs) ? new Date(anchorMs).toISOString() : new Date(now).toISOString();
3757
+ changed = true;
3758
+ }
3759
+ if (!Number.isFinite(Date.parse(updatedAtRaw))) {
3760
+ next.updatedAt = next.createdAt || new Date(now).toISOString();
3761
+ changed = true;
3762
+ }
3763
+ if (typeof next.sessionId !== "string" || !next.sessionId.trim()) {
3764
+ next.sessionId = String(sid || "");
3765
+ changed = true;
3766
+ }
3767
+ raw.sessions[sid] = next;
3768
+ }
3769
+ if (changed) {
3770
+ try {
3771
+ saveBlackboxState(raw);
3772
+ } catch {
3773
+ }
3774
+ }
3775
+ return raw;
3728
3776
  } catch {
3729
3777
  _handleStateCorruption(blackboxFile);
3730
3778
  return { enabled: false, sessions: {} };
@@ -3733,9 +3781,32 @@ function loadBlackboxState() {
3733
3781
  function saveBlackboxState(state) {
3734
3782
  const blackboxFile = join4(getVibeOSHome3(), "blackbox-state.json");
3735
3783
  try {
3784
+ const next = state && typeof state === "object" ? state : { enabled: true, sessions: {} };
3785
+ next.sessions ??= {};
3786
+ const now = Date.now();
3787
+ for (const [sid, session] of Object.entries(next.sessions)) {
3788
+ if (!session || typeof session !== "object")
3789
+ continue;
3790
+ const record = session;
3791
+ const createdAtRaw = typeof record.createdAt === "string" ? record.createdAt : "";
3792
+ const updatedAtRaw = typeof record.updatedAt === "string" ? record.updatedAt : "";
3793
+ const startedRaw = typeof record.started === "string" ? record.started : "";
3794
+ const sessionStartedRaw = typeof record.session_started_at === "string" ? record.session_started_at : "";
3795
+ const anchorRaw = [createdAtRaw, updatedAtRaw, startedRaw, sessionStartedRaw].find((v) => v && !Number.isNaN(Date.parse(v)));
3796
+ const anchorMs = anchorRaw ? Date.parse(anchorRaw) : NaN;
3797
+ if (!Number.isFinite(Date.parse(createdAtRaw))) {
3798
+ record.createdAt = Number.isFinite(anchorMs) ? new Date(anchorMs).toISOString() : new Date(now).toISOString();
3799
+ }
3800
+ if (!Number.isFinite(Date.parse(updatedAtRaw))) {
3801
+ record.updatedAt = record.createdAt || new Date(now).toISOString();
3802
+ }
3803
+ if (typeof record.sessionId !== "string" || !record.sessionId.trim()) {
3804
+ record.sessionId = String(sid || "");
3805
+ }
3806
+ }
3736
3807
  mkdirSync3(dirname4(blackboxFile), { recursive: true });
3737
3808
  const tmp = blackboxFile + ".tmp";
3738
- writeFileSync4(tmp, JSON.stringify(state, null, 2) + "\n");
3809
+ writeFileSync4(tmp, JSON.stringify(next, null, 2) + "\n");
3739
3810
  renameSync3(tmp, blackboxFile);
3740
3811
  } catch (err) {
3741
3812
  console.error(`[vibeOS] saveBlackboxState failed: ${err.message}`);
@@ -4347,11 +4418,49 @@ function ensureProjectBucket(state, fp2) {
4347
4418
  researchChains: 0,
4348
4419
  context7Bypasses: 0,
4349
4420
  commonTopics: [],
4421
+ sessions: [],
4422
+ reports: [],
4423
+ updatedAt: null,
4424
+ lastSeen: null,
4350
4425
  techStack: detectTechStack(process.cwd())
4351
4426
  };
4352
4427
  }
4353
4428
  return state.project_hashes[fp2];
4354
4429
  }
4430
+ function touchProjectBucket(state, fp2, meta = {}) {
4431
+ if (!fp2 || fp2 === "unknown")
4432
+ return null;
4433
+ const bucket = ensureProjectBucket(state, fp2);
4434
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4435
+ bucket.updatedAt = now;
4436
+ bucket.lastSeen = now;
4437
+ if (typeof meta.projectName === "string" && meta.projectName.trim()) {
4438
+ bucket.projectName = meta.projectName.trim();
4439
+ }
4440
+ if (typeof meta.sessionId === "string" && meta.sessionId.trim()) {
4441
+ bucket.sessions ??= [];
4442
+ if (!bucket.sessions.includes(meta.sessionId)) {
4443
+ bucket.sessions.push(meta.sessionId);
4444
+ bucket.sessions = bucket.sessions.slice(-30);
4445
+ }
4446
+ bucket.totalSessions = Math.max(Number(bucket.totalSessions || 0), bucket.sessions.length);
4447
+ }
4448
+ if (typeof meta.reportId === "string" && meta.reportId.trim()) {
4449
+ bucket.reports ??= [];
4450
+ if (!bucket.reports.includes(meta.reportId)) {
4451
+ bucket.reports.push(meta.reportId);
4452
+ bucket.reports = bucket.reports.slice(-50);
4453
+ }
4454
+ }
4455
+ if (typeof meta.topic === "string" && meta.topic.trim()) {
4456
+ bucket.commonTopics ??= [];
4457
+ if (!bucket.commonTopics.includes(meta.topic)) {
4458
+ bucket.commonTopics.push(meta.topic);
4459
+ bucket.commonTopics = bucket.commonTopics.slice(-20);
4460
+ }
4461
+ }
4462
+ return bucket;
4463
+ }
4355
4464
  function detectTechStack(dir) {
4356
4465
  const stacks = [];
4357
4466
  try {
@@ -4482,6 +4591,18 @@ function recordCacheSaving(tool2, saveEst, meta = {}) {
4482
4591
  s.sessions[sid2].cache_savings_usd = roundUsd(Number(s.sessions[sid2].cache_savings_usd || 0) + delta);
4483
4592
  s.lifetime.cache_savings_usd = roundUsd(Number(s.lifetime.cache_savings_usd || 0) + delta);
4484
4593
  }
4594
+ try {
4595
+ if (currentProjectFingerprint) {
4596
+ const pstate = loadProjectState();
4597
+ touchProjectBucket(pstate, currentProjectFingerprint, {
4598
+ sessionId: sid2,
4599
+ projectName: currentProjectName || "",
4600
+ topic: meta?.hash ? String(meta.hash).slice(0, 16) : "cache"
4601
+ });
4602
+ saveProjectState(pstate);
4603
+ }
4604
+ } catch {
4605
+ }
4485
4606
  _pruneOldSessions(s);
4486
4607
  return s;
4487
4608
  });
@@ -4512,6 +4633,20 @@ function recordMissedContext7(saveEst) {
4512
4633
  const sid = _OC_SID;
4513
4634
  s.sessions[sid] ??= { total_savings_usd: 0, cache_savings_usd: 0, project_name: "", warns: [], cache_hits: [], seenWarnKeys: {} };
4514
4635
  s.sessions[sid].context7_missed_usd = Math.round(((s.sessions[sid].context7_missed_usd || 0) + saveEst) * 100) / 100;
4636
+ try {
4637
+ if (currentProjectFingerprint) {
4638
+ const pstate = loadProjectState();
4639
+ const bucket = touchProjectBucket(pstate, currentProjectFingerprint, {
4640
+ sessionId: sid,
4641
+ projectName: currentProjectName || "",
4642
+ topic: "context7"
4643
+ });
4644
+ if (bucket)
4645
+ bucket.context7Bypasses = (bucket.context7Bypasses || 0) + 1;
4646
+ saveProjectState(pstate);
4647
+ }
4648
+ } catch {
4649
+ }
4515
4650
  return s;
4516
4651
  });
4517
4652
  try {
@@ -4530,16 +4665,6 @@ function recordMissedContext7(saveEst) {
4530
4665
  _ledgerBufferTimer = setTimeout(_flushLedgerBuffer, LEDGER_BUFFER_FLUSH_MS);
4531
4666
  } catch {
4532
4667
  }
4533
- try {
4534
- if (currentProjectFingerprint) {
4535
- const pstate = loadProjectState();
4536
- const bucket = ensureProjectBucket(pstate, currentProjectFingerprint);
4537
- bucket.context7Bypasses = (bucket.context7Bypasses || 0) + 1;
4538
- bucket.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
4539
- saveProjectState(pstate);
4540
- }
4541
- } catch {
4542
- }
4543
4668
  try {
4544
4669
  updateGlobalLearning((gl) => {
4545
4670
  gl.context7_bypasses = Number(gl.context7_bypasses || 0) + 1;
@@ -7628,13 +7753,22 @@ function _parseMetrics(v) {
7628
7753
  function saveReport({ type = "manual", summary = "", findings = null, metrics = null, narrative = "", tags = [], fingerprint = null, status = "pending", task_description = "", outcome_verified = false } = {}) {
7629
7754
  const parsedFindings = _parseFindings(findings);
7630
7755
  const parsedMetrics = _parseMetrics(metrics);
7756
+ const metricsObject = parsedMetrics && typeof parsedMetrics === "object" && !Array.isArray(parsedMetrics) ? parsedMetrics : {};
7757
+ const metricsSessionId = typeof metricsObject.sessionId === "string" && metricsObject.sessionId.trim() ? metricsObject.sessionId.trim() : "";
7758
+ const metricsProjectName = typeof metricsObject.projectName === "string" && metricsObject.projectName.trim() ? metricsObject.projectName.trim() : "";
7759
+ const metricsProjectFingerprint = typeof metricsObject.projectFingerprint === "string" && metricsObject.projectFingerprint.trim() ? metricsObject.projectFingerprint.trim() : "";
7631
7760
  if (_wouldBeDuplicate(type, summary))
7632
7761
  return null;
7633
- const metricProjectFingerprint = typeof parsedMetrics?.projectFingerprint === "string" ? parsedMetrics.projectFingerprint : "";
7634
- const metricProjectName = typeof parsedMetrics?.projectName === "string" ? parsedMetrics.projectName : "";
7635
- const fp2 = fingerprint || currentProjectFingerprint2 || currentProjectFingerprint || metricProjectFingerprint || "unknown";
7636
- const projectName = currentProjectName2 || currentProjectName || metricProjectName || "unknown";
7637
- const sessionId = currentSessionId2 || getCurrentSessionId() || "unknown";
7762
+ if (!currentProjectFingerprint2 && metricsProjectFingerprint)
7763
+ currentProjectFingerprint2 = metricsProjectFingerprint;
7764
+ if (!currentProjectName2 && metricsProjectName)
7765
+ currentProjectName2 = metricsProjectName;
7766
+ if (!currentSessionId2 && metricsSessionId)
7767
+ currentSessionId2 = metricsSessionId;
7768
+ const liveSessionId = getCurrentSessionId() || getOcSessionId() || "";
7769
+ const fp2 = fingerprint || metricsProjectFingerprint || currentProjectFingerprint || currentProjectFingerprint2 || "unknown";
7770
+ const projectName = metricsProjectName || currentProjectName || currentProjectName2 || "unknown";
7771
+ const sessionId = metricsSessionId || liveSessionId || currentSessionId2 || "unknown";
7638
7772
  const id2 = generateReportId(type, fp2);
7639
7773
  const report = {
7640
7774
  meta: { id: id2, project: projectName, fingerprint: fp2, type, created: (/* @__PURE__ */ new Date()).toISOString(), sessionId },
@@ -7658,6 +7792,19 @@ function saveReport({ type = "manual", summary = "", findings = null, metrics =
7658
7792
  idx.reports.push({ id: id2, type, project: report.meta.project, fingerprint: fp2, created: report.meta.created, summary: _sum });
7659
7793
  writeFileSync9(reportsIndexPath, JSON.stringify(idx, null, 2) + "\n");
7660
7794
  });
7795
+ try {
7796
+ if (fp2 && fp2 !== "unknown") {
7797
+ const pstate = loadProjectState();
7798
+ touchProjectBucket(pstate, fp2, {
7799
+ sessionId,
7800
+ projectName: projectName || "",
7801
+ reportId: id2,
7802
+ topic: type || "report"
7803
+ });
7804
+ saveProjectState(pstate);
7805
+ }
7806
+ } catch {
7807
+ }
7661
7808
  } catch (err) {
7662
7809
  console.error(`[vibeOS] report/index write failed: ${err.message}`);
7663
7810
  return null;
@@ -9957,6 +10104,11 @@ function noteProjectPattern(kind, key, summary, meta = {}) {
9957
10104
  if (meta.path)
9958
10105
  row.path = meta.path;
9959
10106
  target[key] = row;
10107
+ touchProjectBucket(pstate, currentProjectFingerprint, {
10108
+ sessionId: getCurrentSessionId(),
10109
+ projectName: currentProjectName || "",
10110
+ topic: key
10111
+ });
9960
10112
  const entries = Object.entries(target);
9961
10113
  if (entries.length > 50) {
9962
10114
  entries.sort((a, b) => String(b[1]?.lastSeen || "").localeCompare(String(a[1]?.lastSeen || "")));
@@ -10190,6 +10342,9 @@ function recordSaving(tool2, reason, saveEst, meta = {}) {
10190
10342
  s.last_updated = (/* @__PURE__ */ new Date()).toISOString();
10191
10343
  _pruneOldSessions(s);
10192
10344
  });
10345
+ const projectFingerprint2 = typeof meta?.projectFingerprint === "string" && meta.projectFingerprint.trim() ? meta.projectFingerprint.trim() : currentProjectFingerprint || "";
10346
+ const projectName = typeof meta?.projectName === "string" && meta.projectName.trim() ? meta.projectName.trim() : currentProjectName || "";
10347
+ const sessionId = typeof meta?.sessionId === "string" && meta.sessionId.trim() ? meta.sessionId.trim() : getCurrentSessionId() || _OC_SID;
10193
10348
  const entry = JSON.stringify({
10194
10349
  ts: (/* @__PURE__ */ new Date()).toISOString(),
10195
10350
  usd: saveEst,
@@ -10197,9 +10352,21 @@ function recordSaving(tool2, reason, saveEst, meta = {}) {
10197
10352
  tool: tool2,
10198
10353
  reason,
10199
10354
  saveEst,
10200
- fgp: currentProjectFingerprint || ""
10355
+ fgp: projectFingerprint2
10201
10356
  });
10202
10357
  _ledgerBuffer.push(entry);
10358
+ try {
10359
+ if (projectFingerprint2) {
10360
+ const pstate = loadProjectState();
10361
+ touchProjectBucket(pstate, projectFingerprint2, {
10362
+ sessionId,
10363
+ projectName,
10364
+ topic: tool2 || reason || "saving"
10365
+ });
10366
+ saveProjectState(pstate);
10367
+ }
10368
+ } catch {
10369
+ }
10203
10370
  if (_ledgerBuffer.length >= LEDGER_BUFFER_MAX)
10204
10371
  _flushLedgerBuffer();
10205
10372
  else if (!_ledgerBufferTimer)
@@ -10370,6 +10537,17 @@ function ensureProjectContext(hookDirectory) {
10370
10537
  if (name && name !== currentProjectName)
10371
10538
  setCurrentProjectName(name);
10372
10539
  }
10540
+ try {
10541
+ if (resolved) {
10542
+ const pstate = loadProjectState();
10543
+ touchProjectBucket(pstate, resolved, {
10544
+ sessionId: _OC_SID3,
10545
+ projectName: currentProjectName || (hookDirectory ? hookDirectory.split("/").filter(Boolean).pop() || "" : "")
10546
+ });
10547
+ saveProjectState(pstate);
10548
+ }
10549
+ } catch {
10550
+ }
10373
10551
  return resolved;
10374
10552
  }
10375
10553
  var latestUserIntent = null;
@@ -13393,7 +13571,12 @@ ${argsJson}
13393
13571
  costDetector.record(modelCost);
13394
13572
  }
13395
13573
  if (_credit < 40 && !compatibilityMode) {
13396
- const total = recordSaving(t, "credit<40% high-tier", _estOpus, { firstWord: _firstWord });
13574
+ const total = recordSaving(t, "credit<40% high-tier", _estOpus, {
13575
+ firstWord: _firstWord,
13576
+ projectFingerprint: currentProjectFingerprint,
13577
+ projectName: currentProjectName || "",
13578
+ sessionId: getCurrentSessionId()
13579
+ });
13397
13580
  const msg = `[vibeOS] Quick win: ${resolveTierIcon("cheap")} cheap lane open \xB7 switch to ${resolveTierIcon("medium")} medium to save about ~$${_estOpus.toFixed(3)}/turn.`;
13398
13581
  if (shouldLogWarn(`${t}|credit|${_tierWord}`) && process.env.VIBEOS_DEBUG_DELEGATION === "1") {
13399
13582
  console.error(`[vibeOS] [delegation] ${msg}`);
@@ -13416,7 +13599,12 @@ ${argsJson}
13416
13599
  const isBlocked = apiResult?.blocked !== false;
13417
13600
  const savings = apiResult?.savings ?? _estEdit;
13418
13601
  if (isBlocked) {
13419
- const total2 = recordSaving(t, "delegation enforced", savings, { firstWord: _firstWord });
13602
+ const total2 = recordSaving(t, "delegation enforced", savings, {
13603
+ firstWord: _firstWord,
13604
+ projectFingerprint: currentProjectFingerprint,
13605
+ projectName: currentProjectName || "",
13606
+ sessionId: getCurrentSessionId()
13607
+ });
13420
13608
  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.`;
13421
13609
  enforcementBlocked = true;
13422
13610
  if (shouldLogWarn(`${t}|enforced|${_tierWord}`))
@@ -13424,7 +13612,12 @@ ${argsJson}
13424
13612
  return;
13425
13613
  }
13426
13614
  }
13427
- const total = recordSaving(t, "direct edit", _estEdit, { firstWord: _firstWord });
13615
+ const total = recordSaving(t, "direct edit", _estEdit, {
13616
+ firstWord: _firstWord,
13617
+ projectFingerprint: currentProjectFingerprint,
13618
+ projectName: currentProjectName || "",
13619
+ sessionId: getCurrentSessionId()
13620
+ });
13428
13621
  if (!compatibilityMode) {
13429
13622
  const msg = `[vibeOS] ${resolveTierIcon("cheap")} cheap lane \xB7 save about ~$${_estEdit.toFixed(3)} by delegating to Task. Try ${resolveTierIcon("medium")} medium.`;
13430
13623
  if (shouldLogWarn(`${t}|direct|${_tierWord}`) && process.env.VIBEOS_DEBUG_DELEGATION === "1") {
@@ -13463,7 +13656,11 @@ ${argsJson}
13463
13656
  softQuotaCounts[t] = (softQuotaCounts[t] ?? 0) + 1;
13464
13657
  const n = softQuotaCounts[t];
13465
13658
  if (n === SOFT_QUOTA_LIMIT + 1) {
13466
- const total = recordSaving(t, `soft quota exceeded (limit ${SOFT_QUOTA_LIMIT})`, SAVE_EST.SOFT_QUOTA);
13659
+ const total = recordSaving(t, `soft quota exceeded (limit ${SOFT_QUOTA_LIMIT})`, SAVE_EST.SOFT_QUOTA, {
13660
+ projectFingerprint: currentProjectFingerprint,
13661
+ projectName: currentProjectName || "",
13662
+ sessionId: getCurrentSessionId()
13663
+ });
13467
13664
  console.error(`[vibeOS] Bash usage is getting heavy (${n}/${SOFT_QUOTA_LIMIT}) \u2014 hand the next step to a Task subagent.`);
13468
13665
  }
13469
13666
  return;
@@ -14204,6 +14401,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14204
14401
  setShellDirectory(directory3 || "");
14205
14402
  registerSessionCleanupHandlers();
14206
14403
  pruneScratchpadOnce();
14404
+ runStartupMaintenanceOnce();
14207
14405
  const _bootstrapModel = await _resolveBootstrapModel(client2, directory3);
14208
14406
  if (_bootstrapModel.model) {
14209
14407
  setCurrentModel(_bootstrapModel.model);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.24.17",
3
+ "version": "0.24.18",
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",