vibeostheog 0.24.15 → 0.24.16

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.16
2
+ - fix: serialize model tiers writes
3
+ - test: add concurrent tiers write regression
4
+
5
+
1
6
  ## 0.24.15
2
7
  - feat: smooth delegation UX — conversational reasoning over error injection
3
8
  - fix: preserve local agent mode in remote control merge
package/dist/vibeOS.js CHANGED
@@ -2451,14 +2451,16 @@ function loadSelection() {
2451
2451
  function writeSelection(key, value) {
2452
2452
  const TIERS_FILE3 = join3(getVibeOSHome2(), "model-tiers.json");
2453
2453
  try {
2454
- const j = safeJsonParse2(readFileSync3(TIERS_FILE3, "utf-8"));
2455
- if (!j.selection)
2456
- j.selection = {};
2457
- j.selection[key] = value;
2458
- const tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
2459
- writeFileSync3(tmp, JSON.stringify(j, null, 2) + "\n");
2460
- renameSync2(tmp, TIERS_FILE3);
2461
- return true;
2454
+ return withFileLock(TIERS_FILE3, () => {
2455
+ const j = safeJsonParse2(readFileSync3(TIERS_FILE3, "utf-8"));
2456
+ if (!j.selection)
2457
+ j.selection = {};
2458
+ j.selection[key] = value;
2459
+ const tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
2460
+ writeFileSync3(tmp, JSON.stringify(j, null, 2) + "\n");
2461
+ renameSync2(tmp, TIERS_FILE3);
2462
+ return true;
2463
+ });
2462
2464
  } catch (err) {
2463
2465
  console.error(`[vibeOS] writeSelection failed: ${err.message}`);
2464
2466
  return false;
@@ -5874,18 +5876,20 @@ function _refreshModel(directory3) {
5874
5876
  console.error(`[vibeOS] model refresh (config): ${oldModel}(${oldTier}) \u2192 ${currentModel}(${currentTier})`);
5875
5877
  try {
5876
5878
  if (existsSync6(TIERS_FILE3)) {
5877
- const t = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
5878
- for (const s of getTrinitySlotOrder(t)) {
5879
- if (t?.trinity?.[s]?.oc === cfgModel) {
5880
- t.selection.active_slot = s;
5881
- const _tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
5882
- writeFileSync5(_tmp, JSON.stringify(t, null, 2) + "\n", "utf-8");
5883
- renameSync4(_tmp, TIERS_FILE3);
5884
- if (DEBUG_INTERNALS)
5885
- console.error(`[vibeOS] model refresh (config): synced active_slot \u2192 ${s}`);
5886
- break;
5879
+ withFileLock2(TIERS_FILE3, () => {
5880
+ const t = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
5881
+ for (const s of getTrinitySlotOrder(t)) {
5882
+ if (t?.trinity?.[s]?.oc === cfgModel) {
5883
+ t.selection.active_slot = s;
5884
+ const _tmp = TIERS_FILE3 + ".tmp." + Date.now() + "." + Math.random().toString(36).slice(2, 8);
5885
+ writeFileSync5(_tmp, JSON.stringify(t, null, 2) + "\n", "utf-8");
5886
+ renameSync4(_tmp, TIERS_FILE3);
5887
+ if (DEBUG_INTERNALS)
5888
+ console.error(`[vibeOS] model refresh (config): synced active_slot \u2192 ${s}`);
5889
+ break;
5890
+ }
5887
5891
  }
5888
- }
5892
+ });
5889
5893
  }
5890
5894
  } catch {
5891
5895
  }
@@ -5897,25 +5901,27 @@ function _refreshModel(directory3) {
5897
5901
  function applySlot2(slot, projectDir = "") {
5898
5902
  try {
5899
5903
  const TIERS_FILE3 = join5(getVibeOSHome4(), "model-tiers.json");
5900
- const j = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
5901
- const ocModel = j?.trinity?.[slot]?.oc;
5902
- if (!ocModel)
5903
- return { ok: false, reason: `slot '${slot}' has no oc model` };
5904
- j.selection.active_slot = slot;
5905
- const _tmp = TIERS_FILE3 + ".tmp." + Date.now();
5906
- writeFileSync5(_tmp, JSON.stringify(j, null, 2) + "\n", "utf-8");
5907
- renameSync4(_tmp, TIERS_FILE3);
5908
- const dir = projectDir || process.cwd();
5909
- const localOcConfig = join5(dir, "opencode.json");
5910
- const ocConfig = existsSync6(localOcConfig) ? localOcConfig : join5(getOpenCodeHome(), "opencode.json");
5911
- if (existsSync6(ocConfig)) {
5912
- const oc = safeJsonParse3(readFileSync5(ocConfig, "utf-8"));
5913
- oc.model = ocModel;
5914
- writeFileSync5(ocConfig, JSON.stringify(oc, null, 2) + "\n");
5915
- }
5916
- clearWorkspaceFollowupPauseForSession(getCurrentSessionId());
5917
- _refreshModel(dir);
5918
- return { ok: true, ocModel };
5904
+ return withFileLock2(TIERS_FILE3, () => {
5905
+ const j = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
5906
+ const ocModel = j?.trinity?.[slot]?.oc;
5907
+ if (!ocModel)
5908
+ return { ok: false, reason: `slot '${slot}' has no oc model` };
5909
+ j.selection.active_slot = slot;
5910
+ const _tmp = TIERS_FILE3 + ".tmp." + Date.now();
5911
+ writeFileSync5(_tmp, JSON.stringify(j, null, 2) + "\n", "utf-8");
5912
+ renameSync4(_tmp, TIERS_FILE3);
5913
+ const dir = projectDir || process.cwd();
5914
+ const localOcConfig = join5(dir, "opencode.json");
5915
+ const ocConfig = existsSync6(localOcConfig) ? localOcConfig : join5(getOpenCodeHome(), "opencode.json");
5916
+ if (existsSync6(ocConfig)) {
5917
+ const oc = safeJsonParse3(readFileSync5(ocConfig, "utf-8"));
5918
+ oc.model = ocModel;
5919
+ writeFileSync5(ocConfig, JSON.stringify(oc, null, 2) + "\n");
5920
+ }
5921
+ clearWorkspaceFollowupPauseForSession(getCurrentSessionId());
5922
+ _refreshModel(dir);
5923
+ return { ok: true, ocModel };
5924
+ });
5919
5925
  } catch (err) {
5920
5926
  return { ok: false, reason: err.message };
5921
5927
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.24.15",
3
+ "version": "0.24.16",
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",