vibeostheog 0.25.14 → 0.25.17

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,18 @@
1
+ ## 0.25.17
2
+ - chore: v0.25.16
3
+ Revert "Remove local dashboard base stamp"
4
+
5
+
6
+ ## 0.25.16
7
+ - chore: patch release bump
8
+
9
+
10
+ ## 0.25.15
11
+ - fix: restore M1 installer deploy
12
+ Remove local dashboard base stamp
13
+ Harden LOOPING control vector
14
+
15
+
1
16
  ## 0.25.14
2
17
  - fix: register OpenCode on both homes
3
18
 
package/bin/setup.js CHANGED
@@ -5,30 +5,17 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
5
5
  import { dirname, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { homedir } from "node:os";
8
- import readline from "node:readline";
9
8
 
10
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
10
  const root = resolve(__dirname, "..");
12
11
  const args = process.argv.slice(2);
12
+ const command = args[0] ?? "setup";
13
+ const isInstallCommand = command === "setup" || command === "set";
13
14
  const isProject = args.includes("--project");
14
- const autoYes = args.includes("--yes") || args.includes("-y");
15
15
 
16
- async function confirmInstall() {
17
- if (autoYes) return true;
18
- if (!process.stdin.isTTY || !process.stderr.isTTY) return true;
19
- const prompt = readline.createInterface({ input: process.stdin, output: process.stderr });
20
- const answer = await new Promise((resolveAnswer) => {
21
- prompt.question("Install vibeOS into OpenCode? [y/N] ", (response) => {
22
- resolveAnswer(String(response || "").trim().toLowerCase());
23
- });
24
- });
25
- prompt.close();
26
- return answer === "y" || answer === "yes";
27
- }
28
-
29
- if (!(await confirmInstall())) {
30
- console.log("Install cancelled.");
31
- process.exit(0);
16
+ if (!isInstallCommand) {
17
+ console.error("Usage: vibeostheog set [--project] | vibeostheog setup [--project]");
18
+ process.exit(1);
32
19
  }
33
20
 
34
21
  // Deploy plugin files to ~/.config/opencode/plugins/ and register globally
@@ -1 +1 @@
1
- window.__VIBEOS_DASHBOARD_BASE__ = "http://127.0.0.1:56472";
1
+ window.__VIBEOS_DASHBOARD_BASE__ = "http://127.0.0.1:58227";
package/dist/vibeOS.js CHANGED
@@ -3416,7 +3416,7 @@ function autoSelectMode(subRegime, stressMultiplier) {
3416
3416
  if (regime === "AUDIT" || regime === "FORENSIC")
3417
3417
  return regime.toLowerCase();
3418
3418
  if (regime === "LOOPING")
3419
- return "speed";
3419
+ return "quality";
3420
3420
  if (regime === "CONVERGING" || regime === "CLOSED")
3421
3421
  return "quality";
3422
3422
  if (regime === "IMPLEMENTING")
@@ -3552,16 +3552,16 @@ var init_meta_controller = __esm({
3552
3552
  wbp_verbosity: "minimal"
3553
3553
  },
3554
3554
  LOOPING: {
3555
- enforcement_mode: "relaxed",
3556
- enforcement_reason: "user stuck \u2014 relax all enforcement, fresh perspective",
3557
- flow_mode: "audit",
3558
- flow_focus: ["suggest-alternative"],
3559
- tdd_mode: "lazy",
3560
- tdd_focus: [],
3561
- tier_bias: "medium",
3562
- thinking_mode: "off",
3563
- stress_multiplier: 0.3,
3564
- context7_urgency: "optional",
3555
+ enforcement_mode: "strict",
3556
+ enforcement_reason: "user stuck \u2014 tighten enforcement and switch to recovery posture",
3557
+ flow_mode: "strict",
3558
+ flow_focus: ["write-edit-check", "no-untouched-files", "suggest-alternative"],
3559
+ tdd_mode: "strict",
3560
+ tdd_focus: ["skeleton-on-write", "assertion-check"],
3561
+ tier_bias: "brain",
3562
+ thinking_mode: "brief",
3563
+ stress_multiplier: 2,
3564
+ context7_urgency: "required",
3565
3565
  wbp_verbosity: "detailed"
3566
3566
  },
3567
3567
  CLOSED: {
@@ -3824,7 +3824,7 @@ import { resolve as resolve2, dirname as dirname7 } from "node:path";
3824
3824
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3825
3825
  function fallback(sr, text) {
3826
3826
  if (sr === "LOOPING")
3827
- return "speed";
3827
+ return "quality";
3828
3828
  const t = String(text || "").toLowerCase();
3829
3829
  if (sr === "INIT" && t.length <= 42 && !/[\.\/\\]/.test(t))
3830
3830
  return "budget";
@@ -6874,6 +6874,42 @@ var ResolutionTracker = class _ResolutionTracker {
6874
6874
  normalizeText(text) {
6875
6875
  return (text || "").toLowerCase().replace(/[^a-z0-9\s]+/g, " ").replace(/\s+/g, " ").trim();
6876
6876
  }
6877
+ normalizeActivity(activity, action, text) {
6878
+ const fallbackSignature = this.normalizeText(action || text || "");
6879
+ if (!activity) {
6880
+ return {
6881
+ signature: fallbackSignature || "",
6882
+ tool: null,
6883
+ target: null,
6884
+ action: action || null,
6885
+ repeat_count: 1,
6886
+ outcome: null
6887
+ };
6888
+ }
6889
+ if (typeof activity === "string") {
6890
+ const sig = this.normalizeText(activity);
6891
+ return {
6892
+ signature: sig || fallbackSignature || "",
6893
+ tool: null,
6894
+ target: null,
6895
+ action: action || null,
6896
+ repeat_count: 1,
6897
+ outcome: null
6898
+ };
6899
+ }
6900
+ const tool2 = this.normalizeText(activity.tool || activity.toolName || activity.kind || "");
6901
+ const target = this.normalizeText(activity.target || activity.filePath || activity.file_path || activity.path || activity.command || "");
6902
+ const normalizedAction = this.normalizeText(activity.action || activity.kind || action || "");
6903
+ const signature = this.normalizeText(activity.signature || [tool2, target, normalizedAction, activity.outcome || ""].filter(Boolean).join(" "));
6904
+ return {
6905
+ signature: signature || fallbackSignature || "",
6906
+ tool: tool2 || null,
6907
+ target: target || null,
6908
+ action: normalizedAction || action || null,
6909
+ repeat_count: Number(activity.repeat_count || activity.repeatCount || 1) || 1,
6910
+ outcome: typeof activity.outcome === "string" ? activity.outcome : activity.outcome ?? null
6911
+ };
6912
+ }
6877
6913
  getRepeatStreak() {
6878
6914
  if (this.history.length < 2)
6879
6915
  return 0;
@@ -6889,7 +6925,38 @@ var ResolutionTracker = class _ResolutionTracker {
6889
6925
  }
6890
6926
  return streak;
6891
6927
  }
6892
- update(userText, features, action, entropy, uncertainty, embedding = null) {
6928
+ getActivityRepeatStreak() {
6929
+ if (this.history.length < 2)
6930
+ return 0;
6931
+ const normalizedLast = this.normalizeActivity(this.history[this.history.length - 1].activity, this.history[this.history.length - 1].action, this.history[this.history.length - 1].text).signature;
6932
+ if (!normalizedLast)
6933
+ return 0;
6934
+ let streak = 1;
6935
+ for (let i = this.history.length - 2; i >= 0; i--) {
6936
+ const normalized = this.normalizeActivity(this.history[i].activity, this.history[i].action, this.history[i].text).signature;
6937
+ if (!normalized || normalized !== normalizedLast)
6938
+ break;
6939
+ streak++;
6940
+ }
6941
+ return streak;
6942
+ }
6943
+ getTargetRepeatStreak() {
6944
+ if (this.history.length < 2)
6945
+ return 0;
6946
+ const normalizedLast = this.normalizeActivity(this.history[this.history.length - 1].activity, this.history[this.history.length - 1].action, this.history[this.history.length - 1].text).target;
6947
+ if (!normalizedLast)
6948
+ return 0;
6949
+ let streak = 1;
6950
+ for (let i = this.history.length - 2; i >= 0; i--) {
6951
+ const normalized = this.normalizeActivity(this.history[i].activity, this.history[i].action, this.history[i].text).target;
6952
+ if (!normalized || normalized !== normalizedLast)
6953
+ break;
6954
+ streak++;
6955
+ }
6956
+ return streak;
6957
+ }
6958
+ update(userText, features, action, entropy, uncertainty, embedding = null, activity = null) {
6959
+ const normalizedActivity = this.normalizeActivity(activity, action, userText);
6893
6960
  const entry = {
6894
6961
  text: userText,
6895
6962
  features: { ...features },
@@ -6897,6 +6964,7 @@ var ResolutionTracker = class _ResolutionTracker {
6897
6964
  entropy,
6898
6965
  uncertainty,
6899
6966
  embedding: embedding ? [...embedding] : null,
6967
+ activity: normalizedActivity,
6900
6968
  timestamp: Date.now() / 1e3
6901
6969
  };
6902
6970
  if (this.history.length >= 2) {
@@ -6965,6 +7033,8 @@ var ResolutionTracker = class _ResolutionTracker {
6965
7033
  const featureContradiction = this.calcFeatureContradiction();
6966
7034
  const embeddingDelta = this.calcEmbeddingDelta();
6967
7035
  const repeatStreak = this.getRepeatStreak();
7036
+ const activityRepeatStreak = this.getActivityRepeatStreak();
7037
+ const targetRepeatStreak = this.getTargetRepeatStreak();
6968
7038
  const isLooping = this.detectLoop();
6969
7039
  const intentState = this.computeIntentState();
6970
7040
  const continuityState = this.continuityState(intentState);
@@ -7000,9 +7070,10 @@ var ResolutionTracker = class _ResolutionTracker {
7000
7070
  const momentum = this.calcMomentum(entropyTrend, actionConsistency, embeddingDelta, isLooping, lastEntry.action, lastEntry.entropy);
7001
7071
  let loopLevel = "none";
7002
7072
  if (isLooping) {
7003
- if (repeatStreak >= 3 || this.loopCount >= 4)
7073
+ const repeatSignal = Math.max(repeatStreak, activityRepeatStreak, targetRepeatStreak);
7074
+ if (repeatSignal >= 3 || this.loopCount >= 4)
7004
7075
  loopLevel = "escalated";
7005
- else if (repeatStreak >= 2 || this.loopCount >= 3)
7076
+ else if (repeatSignal >= 2 || this.loopCount >= 3)
7006
7077
  loopLevel = "assertive";
7007
7078
  else if (this.loopCount >= 2)
7008
7079
  loopLevel = "suggestive";
@@ -7019,7 +7090,9 @@ var ResolutionTracker = class _ResolutionTracker {
7019
7090
  action_consistency: Math.round(actionConsistency * 1e4) / 1e4,
7020
7091
  entropy_trend: Math.round(entropyTrend * 1e4) / 1e4,
7021
7092
  feature_contradiction: Math.round(featureContradiction * 1e4) / 1e4,
7022
- embedding_delta: Math.round(embeddingDelta * 1e4) / 1e4
7093
+ embedding_delta: Math.round(embeddingDelta * 1e4) / 1e4,
7094
+ activity_repeat_streak: Math.round(activityRepeatStreak * 1e4) / 1e4,
7095
+ target_repeat_streak: Math.round(targetRepeatStreak * 1e4) / 1e4
7023
7096
  },
7024
7097
  intent_state: {
7025
7098
  volatility_score: Math.round(intentState.volatility_score * 1e4) / 1e4,
@@ -7030,6 +7103,8 @@ var ResolutionTracker = class _ResolutionTracker {
7030
7103
  is_looping: isLooping,
7031
7104
  loop_consecutive: this.loopCount,
7032
7105
  repeat_streak: repeatStreak,
7106
+ activity_repeat_streak: activityRepeatStreak,
7107
+ target_repeat_streak: targetRepeatStreak,
7033
7108
  loop_intervention_level: loopLevel,
7034
7109
  pivot_detected: pivotDetected,
7035
7110
  pivot_score: Math.round(pivotScore * 1e4) / 1e4,
@@ -7083,7 +7158,7 @@ var ResolutionTracker = class _ResolutionTracker {
7083
7158
  return 1 - cosineSimilarity2(a, b);
7084
7159
  }
7085
7160
  detectLoop() {
7086
- return this.loopCount >= 2 || this.getRepeatStreak() >= 2;
7161
+ return this.loopCount >= 2 || this.getRepeatStreak() >= 2 || this.getActivityRepeatStreak() >= 2 || this.getTargetRepeatStreak() >= 2;
7087
7162
  }
7088
7163
  computeIntentState() {
7089
7164
  const last = this.history[this.history.length - 1];
@@ -7197,7 +7272,10 @@ var ResolutionTracker = class _ResolutionTracker {
7197
7272
  }
7198
7273
  static deserialize(data) {
7199
7274
  const tracker = new _ResolutionTracker(data.sessionId || "session", data.maxHistory || 10);
7200
- tracker.history = Array.isArray(data.history) ? data.history : [];
7275
+ tracker.history = Array.isArray(data.history) ? data.history.map((entry) => ({
7276
+ ...entry,
7277
+ activity: entry?.activity || null
7278
+ })) : [];
7201
7279
  tracker.loopCount = Number(data.loopCount || 0);
7202
7280
  tracker.pivotHistory = Array.isArray(data.pivotHistory) ? data.pivotHistory : [];
7203
7281
  tracker.outcomeHistory = Array.isArray(data.outcomeHistory) ? data.outcomeHistory : [];
@@ -7330,6 +7408,7 @@ init_state();
7330
7408
  init_selection_manager();
7331
7409
 
7332
7410
  // src/lib/classifiers.js
7411
+ init_state();
7333
7412
  function detectOutcomeSignal(text) {
7334
7413
  if (!text)
7335
7414
  return null;
@@ -7339,7 +7418,29 @@ function detectOutcomeSignal(text) {
7339
7418
  return "negative";
7340
7419
  return null;
7341
7420
  }
7342
- function scoreStress(text) {
7421
+ function countTrailingRepeat(items, signatureOf) {
7422
+ if (!Array.isArray(items) || items.length === 0)
7423
+ return 0;
7424
+ const last = signatureOf(items[items.length - 1]);
7425
+ if (!last)
7426
+ return 0;
7427
+ let streak = 0;
7428
+ for (let i = items.length - 1; i >= 0; i--) {
7429
+ if (signatureOf(items[i]) !== last)
7430
+ break;
7431
+ streak++;
7432
+ }
7433
+ return streak;
7434
+ }
7435
+ function normalizeActivitySignature(event) {
7436
+ if (!event || typeof event !== "object")
7437
+ return "";
7438
+ const tool2 = String(event.tool || "").trim().toLowerCase();
7439
+ const target = String(event.target || "").trim().toLowerCase();
7440
+ const action = String(event.action || event.kind || "").trim().toLowerCase();
7441
+ return [tool2, target, action].filter(Boolean).join(":");
7442
+ }
7443
+ function scoreStress(text, context = {}) {
7343
7444
  if (!text || typeof text !== "string")
7344
7445
  return 0;
7345
7446
  const t = text.toLowerCase();
@@ -7378,6 +7479,48 @@ function scoreStress(text) {
7378
7479
  const qeCombos = text.match(/\?!|!\?/g);
7379
7480
  if (qeCombos)
7380
7481
  score += qeCombos.length * 0.1;
7482
+ const behavioralPhrases = [
7483
+ { re: /\b(restart|restarts|restarted|restart again|restart it|retry|retries|retrial|rerun|redo|repeat the step|try again|another attempt|another pass)\b/gi, weight: 0.09 },
7484
+ { re: /\b(still failing|keeps failing|keeps breaking|still broken|same issue|same result|same error|new error|new issue|broke again|breaks again|every fix|every time|over and over|again and again)\b/gi, weight: 0.12 },
7485
+ { re: /\b(blocked again|stuck again|failed again|fails again|this is not working|nothing changed|no change)\b/gi, weight: 0.1 }
7486
+ ];
7487
+ for (const { re, weight } of behavioralPhrases) {
7488
+ const hits = (t.match(re) || []).length;
7489
+ score += hits * weight;
7490
+ }
7491
+ const sessionId = String(_OC_SID || "");
7492
+ let blackboxState = context?.blackboxState || null;
7493
+ if (!blackboxState) {
7494
+ try {
7495
+ const raw = loadBlackboxState();
7496
+ blackboxState = raw?.sessions?.[sessionId] || null;
7497
+ } catch {
7498
+ }
7499
+ }
7500
+ const recentEvents = Array.isArray(context?.recentToolEvents) ? context.recentToolEvents : Array.isArray(recentToolEvents) ? recentToolEvents : [];
7501
+ const recentWindow = recentEvents.slice(-8);
7502
+ const toolRepeatStreak = countTrailingRepeat(recentWindow, normalizeActivitySignature);
7503
+ if (toolRepeatStreak >= 2) {
7504
+ score += 0.05 + Math.min(0.2, (toolRepeatStreak - 1) * 0.04);
7505
+ }
7506
+ const repeatedTargets = countTrailingRepeat(recentWindow, (event) => String(event?.target || "").trim().toLowerCase());
7507
+ if (repeatedTargets >= 2) {
7508
+ score += 0.04 + Math.min(0.12, (repeatedTargets - 1) * 0.03);
7509
+ }
7510
+ const outcomeHistory = Array.isArray(context?.outcomeHistory) ? context.outcomeHistory : Array.isArray(blackboxState?.outcomeHistory) ? blackboxState.outcomeHistory : [];
7511
+ const recentOutcomes = outcomeHistory.slice(-5);
7512
+ const negativeOutcomes = recentOutcomes.filter((o) => /negative|failed|unresolved|loop_detected/i.test(String(o?.outcome || ""))).length;
7513
+ if (negativeOutcomes >= 1) {
7514
+ score += 0.03 * negativeOutcomes + Math.min(0.12, negativeOutcomes * 0.02);
7515
+ }
7516
+ const loopCount = Number(blackboxState?.loop_count ?? blackboxState?.loopConsecutive ?? blackboxState?.loop_consecutive ?? 0);
7517
+ if (blackboxState?.is_looping || loopCount >= 2) {
7518
+ score += 0.08 + Math.min(0.15, loopCount * 0.02);
7519
+ }
7520
+ const repeatStreak = Number(blackboxState?.repeat_streak ?? 0);
7521
+ if (repeatStreak >= 2) {
7522
+ score += 0.05 + Math.min(0.08, repeatStreak * 0.02);
7523
+ }
7381
7524
  if (text.length < 30)
7382
7525
  score += 0.06;
7383
7526
  else if (text.length < 80)
@@ -7537,7 +7680,7 @@ function autoSelectMode2(subRegime, stressMultiplier) {
7537
7680
  if (regime === "AUDIT" || regime === "FORENSIC")
7538
7681
  return regime.toLowerCase();
7539
7682
  if (regime === "LOOPING")
7540
- return "speed";
7683
+ return "quality";
7541
7684
  if (regime === "CONVERGING" || regime === "CLOSED")
7542
7685
  return "quality";
7543
7686
  if (regime === "IMPLEMENTING")
@@ -7658,22 +7801,27 @@ function computeControlVector2(_state, _action, _optimizationMode) {
7658
7801
  const subRegime = _state?.sub_regime || "INIT";
7659
7802
  const stress = Number(_state?.latest_stress_multiplier ?? 0);
7660
7803
  const tierBias = stress > QUALITY_STRESS_THRESHOLD2 ? "brain" : subRegime === "CONVERGING" || subRegime === "CLOSED" ? "brain" : subRegime === "REFINING" || subRegime === "LOOPING" ? "medium" : mode === "quality" || mode === "longrun" || mode === "vibeultrax" || mode === "vibeqmax" || mode === "forensic" || mode === "audit" ? "brain" : mode === "speed" || mode === "vibemax" || mode === "vibelitex" ? "medium" : mode === "balanced" ? "auto" : "cheap";
7804
+ const loopingHardening = String(subRegime).toUpperCase() === "LOOPING";
7805
+ const hardenedTierBias = loopingHardening ? "brain" : tierBias;
7806
+ const hardenedMode = loopingHardening ? "quality" : mode;
7807
+ const hardenedModeRoot = loopingHardening ? { mode_root: "quality", mode_family: "brain-runtime", cascade_depth: 1, pipeline_root: ["brain"] } : modeRoot;
7661
7808
  return {
7662
- enforcement_mode: isStrict ? "strict" : isRelaxed ? "relaxed" : "normal",
7663
- enforcement_reason: `[optimize: ${mode}] using safe offline defaults`,
7664
- flow_mode: isStrict ? "strict" : isRelaxed ? "audit" : "normal",
7809
+ enforcement_mode: loopingHardening ? "strict" : isStrict ? "strict" : isRelaxed ? "relaxed" : "normal",
7810
+ enforcement_reason: loopingHardening ? "[optimize: LOOPING] recovery posture \u2014 tighten enforcement and preserve outcome detection" : `[optimize: ${mode}] using safe offline defaults`,
7811
+ flow_mode: loopingHardening ? "strict" : isStrict ? "strict" : isRelaxed ? "audit" : "normal",
7665
7812
  flow_focus: [],
7666
- tdd_mode: isStrict ? "strict" : isRelaxed ? "lazy" : "normal",
7813
+ tdd_mode: loopingHardening ? "strict" : isStrict ? "strict" : isRelaxed ? "lazy" : "normal",
7667
7814
  tdd_focus: [],
7668
- tier_bias: tierBias,
7669
- thinking_mode: isStrict ? "full" : mode === "longrun" ? "brief" : isRelaxed ? "off" : "auto",
7670
- stress_multiplier: 1,
7671
- context7_urgency: isStrict ? "required" : "preferred",
7672
- wbp_verbosity: isStrict ? "verbose" : isRelaxed ? "minimal" : "normal",
7815
+ tier_bias: hardenedTierBias,
7816
+ thinking_mode: loopingHardening ? "brief" : isStrict ? "full" : mode === "longrun" ? "brief" : isRelaxed ? "off" : "auto",
7817
+ stress_multiplier: loopingHardening ? Math.max(1.5, stress) : 1,
7818
+ context7_urgency: loopingHardening ? "required" : isStrict ? "required" : "preferred",
7819
+ wbp_verbosity: loopingHardening ? "detailed" : isStrict ? "verbose" : isRelaxed ? "minimal" : "normal",
7673
7820
  agent_mode: (subRegime === "REFINING" || subRegime === "CONVERGING" || subRegime === "CLOSED") && stress <= QUALITY_STRESS_THRESHOLD2 ? "plan" : void 0,
7674
- optimization_mode: mode,
7675
- ...modeRoot,
7676
- directives: isRelaxed && (subRegime === "EXPLORING" || subRegime === "INIT" || subRegime === "AUDIT" || subRegime === "FORENSIC" || subRegime === "LOOPING") ? [
7821
+ optimization_mode: hardenedMode,
7822
+ ...hardenedModeRoot,
7823
+ outcome_detection: true,
7824
+ directives: isRelaxed && !loopingHardening && (subRegime === "EXPLORING" || subRegime === "INIT" || subRegime === "AUDIT" || subRegime === "FORENSIC" || subRegime === "LOOPING") ? [
7677
7825
  `[speed guard] VERIFY BEFORE ACT - Speed-oriented mode "${mode}" is active and user intent is ${subRegime}. Before modifying files or executing commands, first verify the current state. When a request is ambiguous between "check and report" vs "fix", always choose CHECK FIRST. Treat "look at", "check", "investigate", "tell me about" as requests for information, not action items.`
7678
7826
  ] : []
7679
7827
  };
@@ -7731,6 +7879,29 @@ function normalizeBlackboxFeatures(text) {
7731
7879
  uncertainty: computeBlackboxUncertainty(features)
7732
7880
  };
7733
7881
  }
7882
+ function summarizeRecentToolActivity(limit = 5) {
7883
+ const events = Array.isArray(recentToolEvents) ? recentToolEvents.slice(-limit) : [];
7884
+ if (events.length === 0)
7885
+ return null;
7886
+ const last = events[events.length - 1] || {};
7887
+ const signature = `${String(last.tool || "").trim().toLowerCase()}:${String(last.target || "").trim().toLowerCase()}`;
7888
+ let repeatCount = 0;
7889
+ for (let i = events.length - 1; i >= 0; i--) {
7890
+ const cur = events[i] || {};
7891
+ const curSignature = `${String(cur.tool || "").trim().toLowerCase()}:${String(cur.target || "").trim().toLowerCase()}`;
7892
+ if (curSignature !== signature)
7893
+ break;
7894
+ repeatCount++;
7895
+ }
7896
+ return {
7897
+ tool: String(last.tool || "").toLowerCase(),
7898
+ target: String(last.target || "").toLowerCase(),
7899
+ action: String(last.action || last.kind || "").toLowerCase(),
7900
+ signature,
7901
+ repeat_count: repeatCount,
7902
+ recent_count: events.length
7903
+ };
7904
+ }
7734
7905
  function normalizeBlackboxHistoryEntry(entry) {
7735
7906
  const text = typeof entry?.text === "string" ? entry.text : "";
7736
7907
  const fallback2 = normalizeBlackboxFeatures(text);
@@ -7744,7 +7915,8 @@ function normalizeBlackboxHistoryEntry(entry) {
7744
7915
  embedding: Array.isArray(entry?.embedding) ? [...entry.embedding] : null,
7745
7916
  timestamp: Number.isFinite(Number(entry?.timestamp)) ? Number(entry.timestamp) : Date.now() / 1e3,
7746
7917
  is_pivot: Boolean(entry?.is_pivot),
7747
- outcome: typeof entry?.outcome === "string" ? entry.outcome : entry?.outcome ?? null
7918
+ outcome: typeof entry?.outcome === "string" ? entry.outcome : entry?.outcome ?? null,
7919
+ activity: entry?.activity && typeof entry.activity === "object" ? { ...entry.activity } : null
7748
7920
  };
7749
7921
  }
7750
7922
  function normalizeBlackboxHistory(history) {
@@ -7771,7 +7943,8 @@ var _BlackboxStub = class __BlackboxStub {
7771
7943
  }
7772
7944
  update(text) {
7773
7945
  const normalized = normalizeBlackboxFeatures(text);
7774
- const state = this.tracker.update(text, normalized.features, normalized.action, normalized.entropy, normalized.uncertainty);
7946
+ const recentActivity = summarizeRecentToolActivity();
7947
+ const state = this.tracker.update(text, normalized.features, normalized.action, normalized.entropy, normalized.uncertainty, null, recentActivity);
7775
7948
  return { ...state, ...normalized };
7776
7949
  }
7777
7950
  snapshot() {
@@ -7896,8 +8069,10 @@ function computeLocalCalibration() {
7896
8069
  }
7897
8070
  function resolveEnforcementMode() {
7898
8071
  const sub = _latestBlackboxState2?.sub_regime || "INIT";
7899
- if (sub === "EXPLORING" || sub === "DIVERGENT" || sub === "LOOPING")
8072
+ if (sub === "EXPLORING" || sub === "DIVERGENT")
7900
8073
  return "relaxed";
8074
+ if (sub === "LOOPING")
8075
+ return "strict";
7901
8076
  if (sub === "CONVERGING" || sub === "CLOSED")
7902
8077
  return "strict";
7903
8078
  return "normal";
@@ -10791,7 +10966,11 @@ function isManualOverride(mode) {
10791
10966
  function chooseEpisodeMode(regime, suggestedMode, stress) {
10792
10967
  if (suggestedMode === "vibeultrax" || suggestedMode === "vibeqmax" || suggestedMode === "vibemax" || suggestedMode === "audit" || suggestedMode === "forensic")
10793
10968
  return suggestedMode;
10794
- if (LOOP_REGIMES.has(regime) || suggestedMode === "speed")
10969
+ if (regime === "LOOPING")
10970
+ return "quality";
10971
+ if (suggestedMode === "speed")
10972
+ return "speed";
10973
+ if (LOOP_REGIMES.has(regime))
10795
10974
  return "speed";
10796
10975
  if (QUALITY_REGIMES.has(regime) || suggestedMode === "quality")
10797
10976
  return "quality";
@@ -11454,7 +11633,7 @@ function resolveTemplate(prevTemplate, stressScore, userText, creditPercent, sub
11454
11633
  if (detectBudgetSignal(creditPercent)) {
11455
11634
  const regime = String(subRegime || "").toUpperCase();
11456
11635
  if (regime === "LOOPING" || regime === "DIVERGENT")
11457
- return "speed";
11636
+ return "quality";
11458
11637
  return "save";
11459
11638
  }
11460
11639
  if (detectStressSpike(stressScore))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.25.14",
3
+ "version": "0.25.17",
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",
@@ -17,21 +17,9 @@ function resolveOpenCodeHomes() {
17
17
  const base = homedir()
18
18
  const configHome = join(base, ".config", "opencode")
19
19
  const dotHome = join(base, ".opencode")
20
- const roots = [configHome, dotHome]
21
- const existing = roots.filter((dir) => {
22
- try {
23
- const oc = readFileSync(join(dir, "opencode.json"), "utf8")
24
- return Boolean(String(oc || "").trim())
25
- } catch {
26
- try {
27
- const oc = readFileSync(join(dir, "opencode.jsonc"), "utf8")
28
- return Boolean(String(oc || "").trim())
29
- } catch {
30
- return false
31
- }
32
- }
33
- })
34
- return Array.from(new Set(existing.length > 0 ? existing : roots))
20
+ const desktopHome = process.env.VIBEOS_OPENCODE_DESKTOP_HOME
21
+ || (process.platform === "darwin" ? join(base, "Library", "Application Support", "ai.opencode.desktop") : null)
22
+ return [configHome, dotHome, desktopHome].filter(Boolean)
35
23
  }
36
24
 
37
25
  if (!existsSync(bundlePath)) {
@@ -76,9 +64,9 @@ try {
76
64
  mkdirSync(homeEnvDir, { recursive: true })
77
65
  writeFileSync(homeEnvDest, envContent)
78
66
  for (const home of resolveOpenCodeHomes()) {
79
- const pluginEnvDest = join(home, "plugins", ".env.production")
80
- mkdirSync(join(home, "plugins"), { recursive: true })
81
- writeFileSync(pluginEnvDest, envContent)
67
+ const pluginEnvDir = join(home, "plugins")
68
+ mkdirSync(pluginEnvDir, { recursive: true })
69
+ writeFileSync(join(pluginEnvDir, ".env.production"), envContent)
82
70
  }
83
71
  process.stderr.write(`[vibeOS deploy] Synced .env.production to plugin dirs and ~/.claude\n`)
84
72
  }