vibeostheog 0.25.13 → 0.25.15

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,13 @@
1
+ ## 0.25.15
2
+ - fix: restore M1 installer deploy
3
+ Remove local dashboard base stamp
4
+ Harden LOOPING control vector
5
+
6
+
7
+ ## 0.25.14
8
+ - fix: register OpenCode on both homes
9
+
10
+
1
11
  ## 0.25.13
2
12
  - fix: prompt before installer deploy
3
13
 
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__ = "";
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";
@@ -4476,10 +4476,8 @@ var DASHBOARD_DIR = resolveDashboardDir();
4476
4476
  var DASHBOARD_CONFIG_PATH = join4(DASHBOARD_DIR, "vibeos-dashboard-config.js");
4477
4477
  function writeDashboardBaseConfig(baseUrl) {
4478
4478
  try {
4479
- if (!baseUrl)
4480
- return null;
4481
4479
  mkdirSync3(DASHBOARD_DIR, { recursive: true });
4482
- const payload = `window.__VIBEOS_DASHBOARD_BASE__ = ${JSON.stringify(baseUrl.replace(/\/$/, ""))};
4480
+ const payload = `window.__VIBEOS_DASHBOARD_BASE__ = ${JSON.stringify((baseUrl || "").replace(/\/$/, ""))};
4483
4481
  `;
4484
4482
  writeFileSync4(DASHBOARD_CONFIG_PATH, payload, "utf-8");
4485
4483
  return DASHBOARD_CONFIG_PATH;
@@ -6874,6 +6872,42 @@ var ResolutionTracker = class _ResolutionTracker {
6874
6872
  normalizeText(text) {
6875
6873
  return (text || "").toLowerCase().replace(/[^a-z0-9\s]+/g, " ").replace(/\s+/g, " ").trim();
6876
6874
  }
6875
+ normalizeActivity(activity, action, text) {
6876
+ const fallbackSignature = this.normalizeText(action || text || "");
6877
+ if (!activity) {
6878
+ return {
6879
+ signature: fallbackSignature || "",
6880
+ tool: null,
6881
+ target: null,
6882
+ action: action || null,
6883
+ repeat_count: 1,
6884
+ outcome: null
6885
+ };
6886
+ }
6887
+ if (typeof activity === "string") {
6888
+ const sig = this.normalizeText(activity);
6889
+ return {
6890
+ signature: sig || fallbackSignature || "",
6891
+ tool: null,
6892
+ target: null,
6893
+ action: action || null,
6894
+ repeat_count: 1,
6895
+ outcome: null
6896
+ };
6897
+ }
6898
+ const tool2 = this.normalizeText(activity.tool || activity.toolName || activity.kind || "");
6899
+ const target = this.normalizeText(activity.target || activity.filePath || activity.file_path || activity.path || activity.command || "");
6900
+ const normalizedAction = this.normalizeText(activity.action || activity.kind || action || "");
6901
+ const signature = this.normalizeText(activity.signature || [tool2, target, normalizedAction, activity.outcome || ""].filter(Boolean).join(" "));
6902
+ return {
6903
+ signature: signature || fallbackSignature || "",
6904
+ tool: tool2 || null,
6905
+ target: target || null,
6906
+ action: normalizedAction || action || null,
6907
+ repeat_count: Number(activity.repeat_count || activity.repeatCount || 1) || 1,
6908
+ outcome: typeof activity.outcome === "string" ? activity.outcome : activity.outcome ?? null
6909
+ };
6910
+ }
6877
6911
  getRepeatStreak() {
6878
6912
  if (this.history.length < 2)
6879
6913
  return 0;
@@ -6889,7 +6923,38 @@ var ResolutionTracker = class _ResolutionTracker {
6889
6923
  }
6890
6924
  return streak;
6891
6925
  }
6892
- update(userText, features, action, entropy, uncertainty, embedding = null) {
6926
+ getActivityRepeatStreak() {
6927
+ if (this.history.length < 2)
6928
+ return 0;
6929
+ 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;
6930
+ if (!normalizedLast)
6931
+ return 0;
6932
+ let streak = 1;
6933
+ for (let i = this.history.length - 2; i >= 0; i--) {
6934
+ const normalized = this.normalizeActivity(this.history[i].activity, this.history[i].action, this.history[i].text).signature;
6935
+ if (!normalized || normalized !== normalizedLast)
6936
+ break;
6937
+ streak++;
6938
+ }
6939
+ return streak;
6940
+ }
6941
+ getTargetRepeatStreak() {
6942
+ if (this.history.length < 2)
6943
+ return 0;
6944
+ 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;
6945
+ if (!normalizedLast)
6946
+ return 0;
6947
+ let streak = 1;
6948
+ for (let i = this.history.length - 2; i >= 0; i--) {
6949
+ const normalized = this.normalizeActivity(this.history[i].activity, this.history[i].action, this.history[i].text).target;
6950
+ if (!normalized || normalized !== normalizedLast)
6951
+ break;
6952
+ streak++;
6953
+ }
6954
+ return streak;
6955
+ }
6956
+ update(userText, features, action, entropy, uncertainty, embedding = null, activity = null) {
6957
+ const normalizedActivity = this.normalizeActivity(activity, action, userText);
6893
6958
  const entry = {
6894
6959
  text: userText,
6895
6960
  features: { ...features },
@@ -6897,6 +6962,7 @@ var ResolutionTracker = class _ResolutionTracker {
6897
6962
  entropy,
6898
6963
  uncertainty,
6899
6964
  embedding: embedding ? [...embedding] : null,
6965
+ activity: normalizedActivity,
6900
6966
  timestamp: Date.now() / 1e3
6901
6967
  };
6902
6968
  if (this.history.length >= 2) {
@@ -6965,6 +7031,8 @@ var ResolutionTracker = class _ResolutionTracker {
6965
7031
  const featureContradiction = this.calcFeatureContradiction();
6966
7032
  const embeddingDelta = this.calcEmbeddingDelta();
6967
7033
  const repeatStreak = this.getRepeatStreak();
7034
+ const activityRepeatStreak = this.getActivityRepeatStreak();
7035
+ const targetRepeatStreak = this.getTargetRepeatStreak();
6968
7036
  const isLooping = this.detectLoop();
6969
7037
  const intentState = this.computeIntentState();
6970
7038
  const continuityState = this.continuityState(intentState);
@@ -7000,9 +7068,10 @@ var ResolutionTracker = class _ResolutionTracker {
7000
7068
  const momentum = this.calcMomentum(entropyTrend, actionConsistency, embeddingDelta, isLooping, lastEntry.action, lastEntry.entropy);
7001
7069
  let loopLevel = "none";
7002
7070
  if (isLooping) {
7003
- if (repeatStreak >= 3 || this.loopCount >= 4)
7071
+ const repeatSignal = Math.max(repeatStreak, activityRepeatStreak, targetRepeatStreak);
7072
+ if (repeatSignal >= 3 || this.loopCount >= 4)
7004
7073
  loopLevel = "escalated";
7005
- else if (repeatStreak >= 2 || this.loopCount >= 3)
7074
+ else if (repeatSignal >= 2 || this.loopCount >= 3)
7006
7075
  loopLevel = "assertive";
7007
7076
  else if (this.loopCount >= 2)
7008
7077
  loopLevel = "suggestive";
@@ -7019,7 +7088,9 @@ var ResolutionTracker = class _ResolutionTracker {
7019
7088
  action_consistency: Math.round(actionConsistency * 1e4) / 1e4,
7020
7089
  entropy_trend: Math.round(entropyTrend * 1e4) / 1e4,
7021
7090
  feature_contradiction: Math.round(featureContradiction * 1e4) / 1e4,
7022
- embedding_delta: Math.round(embeddingDelta * 1e4) / 1e4
7091
+ embedding_delta: Math.round(embeddingDelta * 1e4) / 1e4,
7092
+ activity_repeat_streak: Math.round(activityRepeatStreak * 1e4) / 1e4,
7093
+ target_repeat_streak: Math.round(targetRepeatStreak * 1e4) / 1e4
7023
7094
  },
7024
7095
  intent_state: {
7025
7096
  volatility_score: Math.round(intentState.volatility_score * 1e4) / 1e4,
@@ -7030,6 +7101,8 @@ var ResolutionTracker = class _ResolutionTracker {
7030
7101
  is_looping: isLooping,
7031
7102
  loop_consecutive: this.loopCount,
7032
7103
  repeat_streak: repeatStreak,
7104
+ activity_repeat_streak: activityRepeatStreak,
7105
+ target_repeat_streak: targetRepeatStreak,
7033
7106
  loop_intervention_level: loopLevel,
7034
7107
  pivot_detected: pivotDetected,
7035
7108
  pivot_score: Math.round(pivotScore * 1e4) / 1e4,
@@ -7083,7 +7156,7 @@ var ResolutionTracker = class _ResolutionTracker {
7083
7156
  return 1 - cosineSimilarity2(a, b);
7084
7157
  }
7085
7158
  detectLoop() {
7086
- return this.loopCount >= 2 || this.getRepeatStreak() >= 2;
7159
+ return this.loopCount >= 2 || this.getRepeatStreak() >= 2 || this.getActivityRepeatStreak() >= 2 || this.getTargetRepeatStreak() >= 2;
7087
7160
  }
7088
7161
  computeIntentState() {
7089
7162
  const last = this.history[this.history.length - 1];
@@ -7197,7 +7270,10 @@ var ResolutionTracker = class _ResolutionTracker {
7197
7270
  }
7198
7271
  static deserialize(data) {
7199
7272
  const tracker = new _ResolutionTracker(data.sessionId || "session", data.maxHistory || 10);
7200
- tracker.history = Array.isArray(data.history) ? data.history : [];
7273
+ tracker.history = Array.isArray(data.history) ? data.history.map((entry) => ({
7274
+ ...entry,
7275
+ activity: entry?.activity || null
7276
+ })) : [];
7201
7277
  tracker.loopCount = Number(data.loopCount || 0);
7202
7278
  tracker.pivotHistory = Array.isArray(data.pivotHistory) ? data.pivotHistory : [];
7203
7279
  tracker.outcomeHistory = Array.isArray(data.outcomeHistory) ? data.outcomeHistory : [];
@@ -7330,6 +7406,7 @@ init_state();
7330
7406
  init_selection_manager();
7331
7407
 
7332
7408
  // src/lib/classifiers.js
7409
+ init_state();
7333
7410
  function detectOutcomeSignal(text) {
7334
7411
  if (!text)
7335
7412
  return null;
@@ -7339,7 +7416,29 @@ function detectOutcomeSignal(text) {
7339
7416
  return "negative";
7340
7417
  return null;
7341
7418
  }
7342
- function scoreStress(text) {
7419
+ function countTrailingRepeat(items, signatureOf) {
7420
+ if (!Array.isArray(items) || items.length === 0)
7421
+ return 0;
7422
+ const last = signatureOf(items[items.length - 1]);
7423
+ if (!last)
7424
+ return 0;
7425
+ let streak = 0;
7426
+ for (let i = items.length - 1; i >= 0; i--) {
7427
+ if (signatureOf(items[i]) !== last)
7428
+ break;
7429
+ streak++;
7430
+ }
7431
+ return streak;
7432
+ }
7433
+ function normalizeActivitySignature(event) {
7434
+ if (!event || typeof event !== "object")
7435
+ return "";
7436
+ const tool2 = String(event.tool || "").trim().toLowerCase();
7437
+ const target = String(event.target || "").trim().toLowerCase();
7438
+ const action = String(event.action || event.kind || "").trim().toLowerCase();
7439
+ return [tool2, target, action].filter(Boolean).join(":");
7440
+ }
7441
+ function scoreStress(text, context = {}) {
7343
7442
  if (!text || typeof text !== "string")
7344
7443
  return 0;
7345
7444
  const t = text.toLowerCase();
@@ -7378,6 +7477,48 @@ function scoreStress(text) {
7378
7477
  const qeCombos = text.match(/\?!|!\?/g);
7379
7478
  if (qeCombos)
7380
7479
  score += qeCombos.length * 0.1;
7480
+ const behavioralPhrases = [
7481
+ { 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 },
7482
+ { 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 },
7483
+ { re: /\b(blocked again|stuck again|failed again|fails again|this is not working|nothing changed|no change)\b/gi, weight: 0.1 }
7484
+ ];
7485
+ for (const { re, weight } of behavioralPhrases) {
7486
+ const hits = (t.match(re) || []).length;
7487
+ score += hits * weight;
7488
+ }
7489
+ const sessionId = String(_OC_SID || "");
7490
+ let blackboxState = context?.blackboxState || null;
7491
+ if (!blackboxState) {
7492
+ try {
7493
+ const raw = loadBlackboxState();
7494
+ blackboxState = raw?.sessions?.[sessionId] || null;
7495
+ } catch {
7496
+ }
7497
+ }
7498
+ const recentEvents = Array.isArray(context?.recentToolEvents) ? context.recentToolEvents : Array.isArray(recentToolEvents) ? recentToolEvents : [];
7499
+ const recentWindow = recentEvents.slice(-8);
7500
+ const toolRepeatStreak = countTrailingRepeat(recentWindow, normalizeActivitySignature);
7501
+ if (toolRepeatStreak >= 2) {
7502
+ score += 0.05 + Math.min(0.2, (toolRepeatStreak - 1) * 0.04);
7503
+ }
7504
+ const repeatedTargets = countTrailingRepeat(recentWindow, (event) => String(event?.target || "").trim().toLowerCase());
7505
+ if (repeatedTargets >= 2) {
7506
+ score += 0.04 + Math.min(0.12, (repeatedTargets - 1) * 0.03);
7507
+ }
7508
+ const outcomeHistory = Array.isArray(context?.outcomeHistory) ? context.outcomeHistory : Array.isArray(blackboxState?.outcomeHistory) ? blackboxState.outcomeHistory : [];
7509
+ const recentOutcomes = outcomeHistory.slice(-5);
7510
+ const negativeOutcomes = recentOutcomes.filter((o) => /negative|failed|unresolved|loop_detected/i.test(String(o?.outcome || ""))).length;
7511
+ if (negativeOutcomes >= 1) {
7512
+ score += 0.03 * negativeOutcomes + Math.min(0.12, negativeOutcomes * 0.02);
7513
+ }
7514
+ const loopCount = Number(blackboxState?.loop_count ?? blackboxState?.loopConsecutive ?? blackboxState?.loop_consecutive ?? 0);
7515
+ if (blackboxState?.is_looping || loopCount >= 2) {
7516
+ score += 0.08 + Math.min(0.15, loopCount * 0.02);
7517
+ }
7518
+ const repeatStreak = Number(blackboxState?.repeat_streak ?? 0);
7519
+ if (repeatStreak >= 2) {
7520
+ score += 0.05 + Math.min(0.08, repeatStreak * 0.02);
7521
+ }
7381
7522
  if (text.length < 30)
7382
7523
  score += 0.06;
7383
7524
  else if (text.length < 80)
@@ -7537,7 +7678,7 @@ function autoSelectMode2(subRegime, stressMultiplier) {
7537
7678
  if (regime === "AUDIT" || regime === "FORENSIC")
7538
7679
  return regime.toLowerCase();
7539
7680
  if (regime === "LOOPING")
7540
- return "speed";
7681
+ return "quality";
7541
7682
  if (regime === "CONVERGING" || regime === "CLOSED")
7542
7683
  return "quality";
7543
7684
  if (regime === "IMPLEMENTING")
@@ -7658,22 +7799,27 @@ function computeControlVector2(_state, _action, _optimizationMode) {
7658
7799
  const subRegime = _state?.sub_regime || "INIT";
7659
7800
  const stress = Number(_state?.latest_stress_multiplier ?? 0);
7660
7801
  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";
7802
+ const loopingHardening = String(subRegime).toUpperCase() === "LOOPING";
7803
+ const hardenedTierBias = loopingHardening ? "brain" : tierBias;
7804
+ const hardenedMode = loopingHardening ? "quality" : mode;
7805
+ const hardenedModeRoot = loopingHardening ? { mode_root: "quality", mode_family: "brain-runtime", cascade_depth: 1, pipeline_root: ["brain"] } : modeRoot;
7661
7806
  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",
7807
+ enforcement_mode: loopingHardening ? "strict" : isStrict ? "strict" : isRelaxed ? "relaxed" : "normal",
7808
+ enforcement_reason: loopingHardening ? "[optimize: LOOPING] recovery posture \u2014 tighten enforcement and preserve outcome detection" : `[optimize: ${mode}] using safe offline defaults`,
7809
+ flow_mode: loopingHardening ? "strict" : isStrict ? "strict" : isRelaxed ? "audit" : "normal",
7665
7810
  flow_focus: [],
7666
- tdd_mode: isStrict ? "strict" : isRelaxed ? "lazy" : "normal",
7811
+ tdd_mode: loopingHardening ? "strict" : isStrict ? "strict" : isRelaxed ? "lazy" : "normal",
7667
7812
  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",
7813
+ tier_bias: hardenedTierBias,
7814
+ thinking_mode: loopingHardening ? "brief" : isStrict ? "full" : mode === "longrun" ? "brief" : isRelaxed ? "off" : "auto",
7815
+ stress_multiplier: loopingHardening ? Math.max(1.5, stress) : 1,
7816
+ context7_urgency: loopingHardening ? "required" : isStrict ? "required" : "preferred",
7817
+ wbp_verbosity: loopingHardening ? "detailed" : isStrict ? "verbose" : isRelaxed ? "minimal" : "normal",
7673
7818
  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") ? [
7819
+ optimization_mode: hardenedMode,
7820
+ ...hardenedModeRoot,
7821
+ outcome_detection: true,
7822
+ directives: isRelaxed && !loopingHardening && (subRegime === "EXPLORING" || subRegime === "INIT" || subRegime === "AUDIT" || subRegime === "FORENSIC" || subRegime === "LOOPING") ? [
7677
7823
  `[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
7824
  ] : []
7679
7825
  };
@@ -7731,6 +7877,29 @@ function normalizeBlackboxFeatures(text) {
7731
7877
  uncertainty: computeBlackboxUncertainty(features)
7732
7878
  };
7733
7879
  }
7880
+ function summarizeRecentToolActivity(limit = 5) {
7881
+ const events = Array.isArray(recentToolEvents) ? recentToolEvents.slice(-limit) : [];
7882
+ if (events.length === 0)
7883
+ return null;
7884
+ const last = events[events.length - 1] || {};
7885
+ const signature = `${String(last.tool || "").trim().toLowerCase()}:${String(last.target || "").trim().toLowerCase()}`;
7886
+ let repeatCount = 0;
7887
+ for (let i = events.length - 1; i >= 0; i--) {
7888
+ const cur = events[i] || {};
7889
+ const curSignature = `${String(cur.tool || "").trim().toLowerCase()}:${String(cur.target || "").trim().toLowerCase()}`;
7890
+ if (curSignature !== signature)
7891
+ break;
7892
+ repeatCount++;
7893
+ }
7894
+ return {
7895
+ tool: String(last.tool || "").toLowerCase(),
7896
+ target: String(last.target || "").toLowerCase(),
7897
+ action: String(last.action || last.kind || "").toLowerCase(),
7898
+ signature,
7899
+ repeat_count: repeatCount,
7900
+ recent_count: events.length
7901
+ };
7902
+ }
7734
7903
  function normalizeBlackboxHistoryEntry(entry) {
7735
7904
  const text = typeof entry?.text === "string" ? entry.text : "";
7736
7905
  const fallback2 = normalizeBlackboxFeatures(text);
@@ -7744,7 +7913,8 @@ function normalizeBlackboxHistoryEntry(entry) {
7744
7913
  embedding: Array.isArray(entry?.embedding) ? [...entry.embedding] : null,
7745
7914
  timestamp: Number.isFinite(Number(entry?.timestamp)) ? Number(entry.timestamp) : Date.now() / 1e3,
7746
7915
  is_pivot: Boolean(entry?.is_pivot),
7747
- outcome: typeof entry?.outcome === "string" ? entry.outcome : entry?.outcome ?? null
7916
+ outcome: typeof entry?.outcome === "string" ? entry.outcome : entry?.outcome ?? null,
7917
+ activity: entry?.activity && typeof entry.activity === "object" ? { ...entry.activity } : null
7748
7918
  };
7749
7919
  }
7750
7920
  function normalizeBlackboxHistory(history) {
@@ -7771,7 +7941,8 @@ var _BlackboxStub = class __BlackboxStub {
7771
7941
  }
7772
7942
  update(text) {
7773
7943
  const normalized = normalizeBlackboxFeatures(text);
7774
- const state = this.tracker.update(text, normalized.features, normalized.action, normalized.entropy, normalized.uncertainty);
7944
+ const recentActivity = summarizeRecentToolActivity();
7945
+ const state = this.tracker.update(text, normalized.features, normalized.action, normalized.entropy, normalized.uncertainty, null, recentActivity);
7775
7946
  return { ...state, ...normalized };
7776
7947
  }
7777
7948
  snapshot() {
@@ -7896,8 +8067,10 @@ function computeLocalCalibration() {
7896
8067
  }
7897
8068
  function resolveEnforcementMode() {
7898
8069
  const sub = _latestBlackboxState2?.sub_regime || "INIT";
7899
- if (sub === "EXPLORING" || sub === "DIVERGENT" || sub === "LOOPING")
8070
+ if (sub === "EXPLORING" || sub === "DIVERGENT")
7900
8071
  return "relaxed";
8072
+ if (sub === "LOOPING")
8073
+ return "strict";
7901
8074
  if (sub === "CONVERGING" || sub === "CLOSED")
7902
8075
  return "strict";
7903
8076
  return "normal";
@@ -10791,7 +10964,11 @@ function isManualOverride(mode) {
10791
10964
  function chooseEpisodeMode(regime, suggestedMode, stress) {
10792
10965
  if (suggestedMode === "vibeultrax" || suggestedMode === "vibeqmax" || suggestedMode === "vibemax" || suggestedMode === "audit" || suggestedMode === "forensic")
10793
10966
  return suggestedMode;
10794
- if (LOOP_REGIMES.has(regime) || suggestedMode === "speed")
10967
+ if (regime === "LOOPING")
10968
+ return "quality";
10969
+ if (suggestedMode === "speed")
10970
+ return "speed";
10971
+ if (LOOP_REGIMES.has(regime))
10795
10972
  return "speed";
10796
10973
  if (QUALITY_REGIMES.has(regime) || suggestedMode === "quality")
10797
10974
  return "quality";
@@ -11454,7 +11631,7 @@ function resolveTemplate(prevTemplate, stressScore, userText, creditPercent, sub
11454
11631
  if (detectBudgetSignal(creditPercent)) {
11455
11632
  const regime = String(subRegime || "").toUpperCase();
11456
11633
  if (regime === "LOOPING" || regime === "DIVERGENT")
11457
- return "speed";
11634
+ return "quality";
11458
11635
  return "save";
11459
11636
  }
11460
11637
  if (detectStressSpike(stressScore))
@@ -15753,8 +15930,7 @@ async function ensureMcpServerRunning() {
15753
15930
  const actualPort = Number(mcpServer?.address?.()?.port || requestedPort);
15754
15931
  if (actualPort && actualPort !== requestedPort)
15755
15932
  persistMcpPort(actualPort);
15756
- if (actualPort)
15757
- writeDashboardBaseConfig(`http://127.0.0.1:${actualPort}`);
15933
+ writeDashboardBaseConfig(process.env.VIBEOS_DASHBOARD_BASE_URL?.trim() || "");
15758
15934
  console.error(`[vibeOS] MCP server on http://127.0.0.1:${actualPort}`);
15759
15935
  if (actualPort)
15760
15936
  console.error(`[vibeOS] Dashboard at http://127.0.0.1:${actualPort}/`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.25.13",
3
+ "version": "0.25.15",
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",
@@ -10,9 +10,15 @@ const ROOT = join(__dirname, "..")
10
10
 
11
11
  const bundlePath = join(ROOT, "dist", "vibeOS.js")
12
12
  const assetsPath = join(ROOT, "dist", "assets")
13
- const pluginDir = join(homedir(), ".config", "opencode", "plugins")
14
- const destPath = join(pluginDir, "vibeOS.js")
15
- const destAssets = join(pluginDir, "assets")
13
+
14
+ function resolveOpenCodeHomes() {
15
+ const override = process.env.VIBEOS_OPENCODE_HOME
16
+ if (override) return [override]
17
+ const base = homedir()
18
+ const configHome = join(base, ".config", "opencode")
19
+ const dotHome = join(base, ".opencode")
20
+ return [configHome, dotHome]
21
+ }
16
22
 
17
23
  if (!existsSync(bundlePath)) {
18
24
  process.stderr.write("[vibeOS deploy] ERROR: dist/vibeOS.js not found\n")
@@ -20,38 +26,47 @@ if (!existsSync(bundlePath)) {
20
26
  }
21
27
 
22
28
  try {
23
- if (!existsSync(pluginDir)) {
24
- mkdirSync(pluginDir, { recursive: true })
25
- }
26
29
  const bundle = readFileSync(bundlePath)
27
- writeFileSync(destPath, bundle)
28
- process.stderr.write(`[vibeOS deploy] dist/vibeOS.js -> ~/.config/opencode/plugins/vibeOS.js (${bundle.length} bytes)\n`)
30
+ for (const home of resolveOpenCodeHomes()) {
31
+ const pluginDir = join(home, "plugins")
32
+ const destPath = join(pluginDir, "vibeOS.js")
33
+ const destAssets = join(pluginDir, "assets")
29
34
 
30
- if (existsSync(assetsPath)) {
31
- cpSync(assetsPath, destAssets, { recursive: true, force: true })
32
- process.stderr.write(`[vibeOS deploy] dist/assets/ -> ~/.config/opencode/plugins/assets/\n`)
33
- }
35
+ if (!existsSync(pluginDir)) {
36
+ mkdirSync(pluginDir, { recursive: true })
37
+ }
38
+ writeFileSync(destPath, bundle)
39
+ process.stderr.write(`[vibeOS deploy] dist/vibeOS.js -> ${home}/plugins/vibeOS.js (${bundle.length} bytes)\n`)
34
40
 
35
- for (const staleDir of [join(pluginDir, "lib"), join(pluginDir, "utils"), join(pluginDir, "vibeOS-lib"), join(pluginDir, "vibeOS-api-server"), join(pluginDir, "dashboard", "dist")]) {
36
- if (existsSync(staleDir)) {
37
- rmSync(staleDir, { recursive: true, force: true })
41
+ if (existsSync(assetsPath)) {
42
+ cpSync(assetsPath, destAssets, { recursive: true, force: true })
43
+ process.stderr.write(`[vibeOS deploy] dist/assets/ -> ${home}/plugins/assets/\n`)
38
44
  }
45
+
46
+ for (const staleDir of [join(pluginDir, "lib"), join(pluginDir, "utils"), join(pluginDir, "vibeOS-lib"), join(pluginDir, "vibeOS-api-server"), join(pluginDir, "dashboard", "dist")]) {
47
+ if (existsSync(staleDir)) {
48
+ rmSync(staleDir, { recursive: true, force: true })
49
+ }
50
+ }
51
+ const rmTsRecursive = (d) => { for (const e of readdirSync(d)) { const f = join(d, e); if (statSync(f).isDirectory()) rmTsRecursive(f); else if (e.endsWith('.ts')) { rmSync(f); } } }
52
+ rmTsRecursive(pluginDir)
53
+ process.stderr.write(`[vibeOS deploy] Stripped .ts files from ${home}/plugins\n`)
39
54
  }
40
- const rmTsRecursive = (d) => { for (const e of readdirSync(d)) { const f = join(d, e); if (statSync(f).isDirectory()) rmTsRecursive(f); else if (e.endsWith('.ts')) { rmSync(f); } } }
41
- rmTsRecursive(pluginDir)
42
- process.stderr.write(`[vibeOS deploy] Stripped .ts files from plugin dir\n`)
43
55
 
44
56
  const envSrc = join(ROOT, ".env.production")
45
57
  if (existsSync(envSrc)) {
46
58
  const envContent = readFileSync(envSrc)
47
- const pluginEnvDest = join(pluginDir, ".env.production")
48
59
  const homeEnvDir = join(homedir(), ".claude")
49
60
  const homeEnvDest = join(homeEnvDir, ".env.production")
50
61
 
51
62
  mkdirSync(homeEnvDir, { recursive: true })
52
- writeFileSync(pluginEnvDest, envContent)
53
63
  writeFileSync(homeEnvDest, envContent)
54
- process.stderr.write(`[vibeOS deploy] Synced .env.production to plugin dir and ~/.claude\n`)
64
+ for (const home of resolveOpenCodeHomes()) {
65
+ const pluginEnvDir = join(home, "plugins")
66
+ mkdirSync(pluginEnvDir, { recursive: true })
67
+ writeFileSync(join(pluginEnvDir, ".env.production"), envContent)
68
+ }
69
+ process.stderr.write(`[vibeOS deploy] Synced .env.production to plugin dirs and ~/.claude\n`)
55
70
  }
56
71
 
57
72
  // ── Install nightly pricing sync cron if not already present ──
@@ -85,26 +100,28 @@ try {
85
100
 
86
101
  // Auto-register in opencode.json so OpenCode loads the plugin
87
102
  try {
88
- const ocConfigPath = join(homedir(), ".config", "opencode", "opencode.json")
89
- mkdirSync(dirname(ocConfigPath), { recursive: true })
90
- let config = {}
91
- if (existsSync(ocConfigPath)) {
92
- const raw = readFileSync(ocConfigPath, "utf-8")
93
- try {
94
- const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "")
95
- config = JSON.parse(cleaned)
96
- } catch {
97
- config = {}
103
+ for (const home of resolveOpenCodeHomes()) {
104
+ const ocConfigPath = join(home, "opencode.json")
105
+ mkdirSync(dirname(ocConfigPath), { recursive: true })
106
+ let config = {}
107
+ if (existsSync(ocConfigPath)) {
108
+ const raw = readFileSync(ocConfigPath, "utf-8")
109
+ try {
110
+ const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "")
111
+ config = JSON.parse(cleaned)
112
+ } catch {
113
+ config = {}
114
+ }
115
+ }
116
+ if (!config || typeof config !== "object" || Array.isArray(config)) config = {}
117
+ if (!Array.isArray(config.plugin)) config.plugin = []
118
+ const hasVibeOs = config.plugin.some(p => typeof p === "string" && p.includes("vibeOS"))
119
+ if (!hasVibeOs) {
120
+ config.$schema ||= "https://opencode.ai/config.json"
121
+ config.plugin.push("./plugins/vibeOS.js")
122
+ writeFileSync(ocConfigPath, JSON.stringify(config, null, 2) + "\n")
123
+ process.stderr.write(`[vibeOS deploy] Registered vibeOS in ${home}/opencode.json\n`)
98
124
  }
99
- }
100
- if (!config || typeof config !== "object" || Array.isArray(config)) config = {}
101
- if (!Array.isArray(config.plugin)) config.plugin = []
102
- const hasVibeOs = config.plugin.some(p => typeof p === "string" && p.includes("vibeOS"))
103
- if (!hasVibeOs) {
104
- config.$schema ||= "https://opencode.ai/config.json"
105
- config.plugin.push("./plugins/vibeOS.js")
106
- writeFileSync(ocConfigPath, JSON.stringify(config, null, 2) + "\n")
107
- process.stderr.write("[vibeOS deploy] Registered vibeOS in opencode.json\n")
108
125
  }
109
126
  } catch {
110
127
  process.stderr.write("[vibeOS deploy] Could not auto-register in opencode.json (plugin may need manual config)\n")