vibeostheog 0.20.16 → 0.22.6
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 +19 -0
- package/README.md +119 -137
- package/bin/setup.js +33 -36
- package/package.json +3 -3
- package/src/lib/api-client.js +95 -0
- package/src/lib/cost-anomaly.js +75 -0
- package/src/lib/hooks/chat-transform.js +24 -18
- package/src/lib/hooks/tool-execute.js +20 -0
- package/src/lib/mode-router.js +100 -0
- package/src/lib/state.js +23 -17
- package/src/lib/trinity-tool.js +23 -30
- package/src/lib/turn-classify.js +24 -20
- package/src/vibeOS-lib/blackbox/index.js +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 0.22.6
|
|
2
|
+
- feat: wire CostAnomalyDetector into tool-execute hook
|
|
3
|
+
- feat: replace TokenAnomalyDetector with CostAnomalyDetector
|
|
4
|
+
- feat: replace TokenAnomalyDetector with CostAnomalyDetector
|
|
5
|
+
- fix: bin/setup.js now delegates to deploy.mjs for proper plugin install
|
|
6
|
+
- fix: restore anomaly detector class in TS source, add mega regression tests
|
|
7
|
+
- fix: read path prefers global scratchpad over stale session-local copies
|
|
8
|
+
- fix: cross-session cache corruptions and hallucinations
|
|
9
|
+
- fix: VibeUltraX mode, anomaly token guard, regression suite
|
|
10
|
+
- chore: bump to 0.22.5
|
|
11
|
+
- chore: bump to 0.22.3
|
|
12
|
+
- chore: add regression tests for anomaly throttle permanent break fix
|
|
13
|
+
- chore: bump to 0.22.2
|
|
14
|
+
Merge pull request #103 from DrunkkToys/fix/anomaly-class-ts-source
|
|
15
|
+
Merge pull request #101 from DrunkkToys/fix/anomaly-throttle-permanent-break
|
|
16
|
+
Merge pull request #100 from DrunkkToys/fix/cross-session-corruptions
|
|
17
|
+
reorder policy comparison table by quality descending; deduplicate VibeUltraX formatting fix
|
|
18
|
+
|
|
19
|
+
|
|
1
20
|
## 0.20.16
|
|
2
21
|
- fix: skip cache savings for free models + add modelCostPerTurn fallback + regression tests
|
|
3
22
|
- fix: wire incrementTurnCounter into onToolExecuteAfter so session compaction fires at turn 7+
|
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# vibeOS for OpenCode
|
|
2
2
|
|
|
3
|
-
**Prices validated: May 28, 2026** — verified against OpenRouter `/api/v1/models`
|
|
4
|
-
|
|
5
3
|
Cost-aware control plane for OpenCode Desktop. Keeps expensive models on strategy, routes implementation to cheaper tiers, surfaces savings in real time.
|
|
6
4
|
|
|
7
5
|
For teams, vibeOS adds practical guardrails: delegation enforcement, flow and TDD controls, pattern learning, stress-aware routing, VibeBoX decision tracking, reporting, and remote API protection for the core algorithms.
|
|
@@ -10,97 +8,103 @@ For teams, vibeOS adds practical guardrails: delegation enforcement, flow and TD
|
|
|
10
8
|
|
|
11
9
|
Every `write`/`edit`/`notebookedit` on the **brain tier** is intercepted, cost-estimated, and blocked with a visible enforcement note. Work must be delegated to **medium** or **cheap**. This is the primary savings mechanism.
|
|
12
10
|
|
|
13
|
-
### Per-Turn Cost (700 input + 300 output tokens)
|
|
14
|
-
|
|
15
|
-
| Tier | Model | Per Turn | Per 100 Turns | vs Opus |
|
|
16
|
-
|------|-------|----------|---------------|---------|
|
|
17
|
-
| Brain | `claude-opus-4-7` | **$0.0330** | **$3.30** | — |
|
|
18
|
-
| Medium | `claude-sonnet-4-6` | **$0.0066** | **$0.66** | saves 80% |
|
|
19
|
-
| Cheap | `claude-haiku-4-5` | **$0.0022** | **$0.22** | saves 93% |
|
|
20
|
-
|
|
21
|
-
*Source: `src/lib/pricing.ts:279-285`. Conservative estimates — actual OpenRouter live: Opus $0.011, Sonnet $0.0066, Haiku $0.0022 per turn. The plugin over-estimates brain cost so savings are always understated.*
|
|
22
|
-
|
|
23
11
|
### Savings per Delegation
|
|
24
12
|
|
|
25
13
|
| Move | Per Turn | 10x | 100x | 1,000x |
|
|
26
14
|
|------|----------|-----|------|--------|
|
|
27
|
-
| Opus
|
|
28
|
-
| Opus
|
|
29
|
-
| Sonnet
|
|
15
|
+
| Opus -> Haiku | $0.0308 | $0.31 | $3.08 | $30.80 |
|
|
16
|
+
| Opus -> Sonnet | $0.0264 | $0.26 | $2.64 | $26.40 |
|
|
17
|
+
| Sonnet -> Haiku | $0.0044 | $0.04 | $0.44 | $4.40 |
|
|
30
18
|
|
|
31
|
-
Every blocked brain-tier write/edit saves at least $0.026 (Opus
|
|
19
|
+
Every blocked brain-tier write/edit saves at least $0.026 (Opus -> Sonnet). The running total is tracked in `~/.claude/delegation-state.json` and displayed in the live footer.
|
|
32
20
|
|
|
33
|
-
##
|
|
21
|
+
## Model Tiers
|
|
34
22
|
|
|
35
|
-
Benchmarked on the DeepSeek v4 family
|
|
23
|
+
Benchmarked on the DeepSeek v4 family. Prices based on 700 input + 300 output tokens per turn.
|
|
36
24
|
|
|
37
|
-
|
|
25
|
+
> DeepSeek Chat costs $0/turn when routed through the Direct DeepSeek provider (no OpenRouter markup).
|
|
38
26
|
|
|
39
|
-
| Model | API ID | Per Turn | Per 1K Turns |
|
|
40
|
-
|
|
41
|
-
| v4 Pro
|
|
42
|
-
| v4 Flash
|
|
43
|
-
| DeepSeek Chat
|
|
27
|
+
| Slot | Model | API ID | Per Turn | Per 1K Turns | Tier |
|
|
28
|
+
|------|-------|--------|----------|--------------|------|
|
|
29
|
+
| brain | v4 Pro | `deepseek/deepseek-v4-pro` | $0.00057 | $0.58 | high |
|
|
30
|
+
| medium | v4 Flash | `deepseek/deepseek-v4-flash` | $0.000182 | $0.18 | mid |
|
|
31
|
+
| cheap | DeepSeek Chat | `deepseek/deepseek-chat` | $0.00 | $0.00 | budget |
|
|
32
|
+
| cheap (local) | MagicCoder:7b | `magicoder:7b` (Ollama) | $0.00 | $0.00 | budget |
|
|
44
33
|
|
|
45
|
-
|
|
34
|
+
*Source: `src/lib/pricing.ts`. Conservative estimates — savings are always understated.*
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|---|---|---|---|---|---|---|---|---|---|
|
|
49
|
-
| **Raw Top Tier** | v4 Pro | full | — | — | — | baseline | $0.00057 | 1.00x | — |
|
|
50
|
-
| **VibeQMaX** (quality) | v4 Pro | full | strict | strict | quality | ~baseline | $0.00029 | 0.50x | **50%** |
|
|
51
|
-
| **VibeMaX** ⭐ | v4 Flash | full | strict | strict | quality | ~70% | $0.00021 | 0.37x | **63%** |
|
|
52
|
-
| **speed** | v4 Flash | off | relaxed | audit | lazy | ~55% | $0.00018 | 0.32x | 68% |
|
|
53
|
-
| **budget** | DeepSeek Chat | off | relaxed | audit | lazy | ~40% | $0.00015 | 0.26x | 74% |
|
|
54
|
-
| **auto** | varies | auto | auto | auto | auto | varies | varies | varies | varies |
|
|
36
|
+
## Optimization Modes
|
|
55
37
|
|
|
56
|
-
###
|
|
38
|
+
### Policy Comparison — Sorted by Quality Descending
|
|
39
|
+
|
|
40
|
+
| Policy | Quality vs Brain | Cost vs Brain | Savings | Method |
|
|
41
|
+
|--------|-----------------|--------------|---------|--------|
|
|
42
|
+
| VibeUltraX | 107% | 0.58x | 42% | local -> Flash -> Pro cascade |
|
|
43
|
+
| VibeQMaX | ~100% | 0.50x | 50% | same model, framework optimizations |
|
|
44
|
+
| Raw Brain | 100% | 1.00x | - | baseline |
|
|
45
|
+
| VibeMaX | ~75% | 0.18x | 82% | trained cascade (conservative escalate) |
|
|
46
|
+
| Budget | ~40% | 0.00x | 100% | direct routing |
|
|
47
|
+
|
|
48
|
+
**VibeUltraX** — Cascade pipeline: MagicCoder:7b (local Ollama) proposes, v4 Flash reviews, v4 Pro refines. Benchmarked at **107% of Brain quality** at 58% cost (local inference is free, only Flash/Pro API calls cost).
|
|
49
|
+
|
|
50
|
+
**VibeQMaX (Quality Max)** — Routes strategic turns through v4 Pro with full thinking, strict enforcement, strict flow checks, and quality TDD. Write/edit turns delegated to cheaper tiers per enforcement rules. Effective blended cost ~$0.00029/turn (50% of Raw Brain).
|
|
51
|
+
|
|
52
|
+
**Raw Brain** — v4 Pro (no framework). Baseline for all comparisons. 100% quality at 1.00x cost.
|
|
53
|
+
|
|
54
|
+
**VibeMaX (ML-Optimized, Default)** — Intelligent cost-quality sweet spot. Routes through v4 Flash (medium) and uses a random forest classifier (29 trees, gini-split, trained on telemetry) to decide each turn. Classifies on 11 derived features: message length, code block density, urgency, complexity, repetition, question ratio, and more. Benchmarked at ~75% of Brain quality at 18% of cost.
|
|
55
|
+
|
|
56
|
+
**Budget** — DeepSeek Chat. Direct routing. ~40% quality at 0.00x cost. 100% savings.
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
### Cost vs Quality Visual
|
|
59
59
|
|
|
60
60
|
```
|
|
61
61
|
Quality
|
|
62
|
-
baseline
|
|
63
|
-
|
|
64
|
-
~
|
|
65
|
-
~
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
baseline . Raw Brain . VibeQMaX
|
|
63
|
+
107% | . VibeUltraX
|
|
64
|
+
~75% | . VibeMaX (default)
|
|
65
|
+
~55% | . Speed
|
|
66
|
+
~40% | . Budget
|
|
67
|
+
|
|
|
68
|
+
+--------------------------------
|
|
69
|
+
1.0x 0.50x 0.32x 0.18x 0.00x
|
|
70
|
+
Cost Multiplier
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
###
|
|
73
|
-
|
|
74
|
-
**VibeQMaX (Quality Max)** — The highest-assurance configuration. Routes strategic turns through `deepseek/deepseek-v4-pro` with full thinking, strict enforcement, strict flow checks, and quality TDD. Write/edit turns are delegated to cheaper tiers per enforcement rules, yielding an **effective blended cost of ~$0.00029/turn (≈50% of Raw Top Tier)**. Guardrails include: delegation enforcement blocks costly mistakes, flow pattern validation prevents structural issues, TDD skeleton generation ensures test coverage, and context7 optimization reduces context waste. VibeQMaX maps to the system's **quality** mode — brain-tier settings with the full vibeOS control plane active.
|
|
73
|
+
### Configuration Per Mode
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
| Mode | Model | Thinking | Enforcement | Flow | TDD |
|
|
76
|
+
|------|-------|----------|-------------|------|-----|
|
|
77
|
+
| Raw Brain | v4 Pro | full | - | - | - |
|
|
78
|
+
| VibeQMaX | v4 Pro | full | strict | strict | quality |
|
|
79
|
+
| VibeUltraX | cascade (local->Flash->Pro) | auto | auto | auto | auto |
|
|
80
|
+
| VibeMaX | v4 Flash (auto-escalate) | auto | auto | auto | auto |
|
|
81
|
+
| Speed | v4 Flash | off | relaxed | audit | lazy |
|
|
82
|
+
| Budget | DeepSeek Chat | off | relaxed | audit | lazy |
|
|
77
83
|
|
|
78
84
|
### Benchmark Details
|
|
79
85
|
|
|
80
|
-
All tests run with
|
|
86
|
+
All tests run with DeepSeek v4 family. Quality scores measured against Raw Brain (v4 Pro, full thinking, no vibeOS overhead). VibeMaX quality benchmark derived from real session telemetry with bootstrap confidence intervals (36 bootstrap samples). Pareto frontier computed from 70 holdout scenarios across 170 training samples via hyperparameter sweep. VibeUltra is the first mode that beats Raw Brain on both accuracy and cost — Pareto-dominant.
|
|
81
87
|
|
|
82
|
-
|
|
88
|
+
Benchmarked on 1000 simulated questions across 20 runs, using model accuracies from MMLU-Pro / GPQA Diamond with real error correlation data.
|
|
83
89
|
|
|
84
90
|
## Features
|
|
85
91
|
|
|
86
92
|
| Feature | What it does |
|
|
87
93
|
|---------|-------------|
|
|
88
94
|
| **Delegation enforcement** | Blocks write/edit on brain tier. Routes to medium or cheap. |
|
|
89
|
-
| **Live savings footer** | Model, provider, cumulative savings, cache savings, stress
|
|
95
|
+
| **Live savings footer** | Model, provider, cumulative savings, cache savings, stress level, lock/enforcement tags. |
|
|
90
96
|
| **Web dashboard** | SolidJS SPA with SSE real-time push. Model split, savings, session history, trinity controls. |
|
|
91
|
-
| **Trinity runtime** |
|
|
92
|
-
| **Flow enforcer** | Pattern-rule checks on write/edit. Extracts TODO/FIXME into
|
|
97
|
+
| **Trinity runtime** | Switch tiers mid-session. Change optimization mode. Flow/TDD/enforcement toggles. |
|
|
98
|
+
| **Flow enforcer** | Pattern-rule checks on write/edit. Extracts TODO/FIXME into append-only queue. |
|
|
93
99
|
| **TDD enforcer** | Auto-creates test skeletons for changed source. Strict mode: TODO tests fail. |
|
|
94
100
|
| **Pattern learner** | Tracks recurring struggle/routine patterns per project. |
|
|
95
101
|
| **VibeBoX** | 7 sub-regimes, 11 features per turn, 4 loop intervention levels, PIVOT/SWITCH detection. Auto-mode maps regime to optimization mode. |
|
|
96
102
|
| **Stress-aware routing** | Stress gauge in footer. Stress > 1.5 escalates to quality mode. |
|
|
97
|
-
| **Cache savings** | Separate
|
|
98
|
-
| **Report tools** |
|
|
103
|
+
| **Cache savings** | Separate cache_savings_usd tracking for scratchpad cache hits. |
|
|
104
|
+
| **Report tools** | report-save, report-list, report-read, research-audit. |
|
|
99
105
|
| **MCP server** | Extended tool capabilities + dashboard serving + SSE push endpoint. |
|
|
100
|
-
| **Remote API** | Fastify server at
|
|
101
|
-
| **Session lock** |
|
|
102
|
-
|
|
103
|
-
---
|
|
106
|
+
| **Remote API** | Fastify server at api.vibetheog.com. Token auth with seat/license management. |
|
|
107
|
+
| **Session lock** | trinity lock on|off — freezes model at session start. |
|
|
104
108
|
|
|
105
109
|
## How It Works
|
|
106
110
|
|
|
@@ -115,48 +119,30 @@ All tests run with `deepseek/deepseek-v4-pro` (brain), `deepseek/deepseek-v4-fla
|
|
|
115
119
|
| `tool.execute.after` | Injects delegation UI notes |
|
|
116
120
|
| `message.updated` | Fallback footer for versions without text.complete |
|
|
117
121
|
| `experimental.session.compacting` | Preserves savings state |
|
|
118
|
-
| `shell.env` | Injects
|
|
122
|
+
| `shell.env` | Injects OPENCODE_MODEL_TIER and OPENCODE_MODEL |
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
## Local Models (Ollama)
|
|
121
125
|
|
|
122
|
-
|
|
126
|
+
To use **MagicCoder:7b** or other local models with vibeOS and OpenCode Desktop:
|
|
123
127
|
|
|
124
|
-
###
|
|
125
|
-
|
|
126
|
-
Model tier classification, static pricing (~20 models), stress scoring, context budget, turn classification, TDD skeleton gen, flow enforcement, savings ledger, session metrics, reports, footer, dashboard, smart cache, VibeBoX fallback.
|
|
127
|
-
|
|
128
|
-
### Requires Remote API (api-token)
|
|
129
|
-
|
|
130
|
-
Bootstrap token exchange, advanced VibeBoX with full session history, dynamic per-prompt delegation, cross-session calibration, live pricing fetch beyond static map.
|
|
131
|
-
|
|
132
|
-
---
|
|
128
|
+
### Setup
|
|
133
129
|
|
|
130
|
+
1. Install Ollama — `curl -fsSL https://ollama.ai/install.sh | sh`
|
|
131
|
+
2. Pull MagicCoder — `ollama pull magicoder:7b`
|
|
132
|
+
3. Add provider — In OpenCode settings, add an Ollama provider (default: http://localhost:11434)
|
|
133
|
+
4. Detect — Run `trinity rebuild` — MagicCoder:7b appears in the model dropdown
|
|
134
|
+
5. Assign slot — `trinity set cheap magicoder:7b`
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
### Minimum Hardware
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- Stress scoring, context budget estimation, and turn classification
|
|
144
|
-
- TDD skeleton generation, text compression, and flow enforcement
|
|
145
|
-
- Savings ledger, session metrics, reports, and footer/dashboard rendering
|
|
146
|
-
- Session-scoped smart cache for duplicate tool output detection
|
|
147
|
-
|
|
148
|
-
### Requires Remote API
|
|
149
|
-
|
|
150
|
-
- Bootstrap token exchange (required for initial API setup)
|
|
151
|
-
- Advanced VibeBoX decision engine with full session history tracking
|
|
152
|
-
- Dynamic per-prompt delegation decisions (local fallback uses a safe "block all writes on high tier" default)
|
|
153
|
-
- Learned subagent routing patterns across projects (local fallback uses a static exploratory keyword list)
|
|
154
|
-
- Optimization mode selection via advanced VibeBoX (local fallback uses rule-based selection)
|
|
155
|
-
- Aggregated cross-session calibration and model retraining
|
|
156
|
-
- Live pricing fetch for models beyond the hardcoded map
|
|
157
|
-
|
|
158
|
-
When the remote API is unreachable, the plugin degrades gracefully to rule-based local algorithms. Core enforcement features continue working — the plugin stays functional and safe, just less adaptive in its routing and mode decisions.
|
|
138
|
+
| Component | Minimum |
|
|
139
|
+
|-----------|---------|
|
|
140
|
+
| CPU | Apple Silicon (M1+) or x86_64 with AVX2 |
|
|
141
|
+
| RAM | **16 GB** (MagicCoder:7b @ Q4_K_M uses ~5 GB + context overhead) |
|
|
142
|
+
| GPU | Integrated (M1 16GB unified memory) or NVIDIA 6GB+ VRAM |
|
|
143
|
+
| Storage | 4 GB free for model weights |
|
|
159
144
|
|
|
145
|
+
> **Note**: Local models run entirely on your machine. vibeOS treats them as any other OpenCode provider — pricing shows $0.00/turn.
|
|
160
146
|
|
|
161
147
|
## Install
|
|
162
148
|
|
|
@@ -165,7 +151,7 @@ npx vibeostheog setup --project # per-project
|
|
|
165
151
|
npx vibeostheog setup # global ~/.config/opencode/
|
|
166
152
|
```
|
|
167
153
|
|
|
168
|
-
|
|
154
|
+
One-command setup: deploys plugin files and registers in opencode.json. Restart OpenCode Desktop.
|
|
169
155
|
|
|
170
156
|
Local dev checkout:
|
|
171
157
|
|
|
@@ -175,8 +161,6 @@ Local dev checkout:
|
|
|
175
161
|
}
|
|
176
162
|
```
|
|
177
163
|
|
|
178
|
-
---
|
|
179
|
-
|
|
180
164
|
## Commands
|
|
181
165
|
|
|
182
166
|
`trinity help` for full reference. Commands register in the TUI sidebar.
|
|
@@ -189,7 +173,7 @@ Local dev checkout:
|
|
|
189
173
|
| `trinity enable\|disable` | Toggle plugin on/off |
|
|
190
174
|
| `trinity mode budget\|quality\|speed\|longrun\|auto` | Set optimization mode |
|
|
191
175
|
| `trinity thinking full\|brief\|off` | Reasoning depth |
|
|
192
|
-
| `trinity enforce on\|off` | Toggle enforcement |
|
|
176
|
+
| `trinity enforce on\|off` | Toggle delegation enforcement |
|
|
193
177
|
| `trinity lock on\|off` | Freeze model for session |
|
|
194
178
|
| `trinity flow on\|off` | Toggle flow enforcer |
|
|
195
179
|
| `trinity flow enforce on\|off` | Toggle auto-extract TODOs |
|
|
@@ -198,15 +182,13 @@ Local dev checkout:
|
|
|
198
182
|
| `trinity project` | Per-project analytics |
|
|
199
183
|
| `trinity patterns` / `trinity patterns clear` | Pattern inspection |
|
|
200
184
|
| `trinity diagnose` | Health check |
|
|
201
|
-
| `trinity
|
|
185
|
+
| `trinity blackbox on\|off\|status\|reset` | Decision engine control |
|
|
202
186
|
| `trinity repair-state preview\|apply` | Fix state collisions |
|
|
203
187
|
| `trinity guard` | Refresh AGENTS.md / README.md |
|
|
204
188
|
| `trinity api-token <token\|invalidate>` | Manage remote API token |
|
|
205
189
|
| `trinity api-bootstrap-token <token>` | Bootstrap token exchange |
|
|
206
190
|
|
|
207
|
-
**Report commands**:
|
|
208
|
-
|
|
209
|
-
---
|
|
191
|
+
**Report commands**: report-save, report-list, report-read, research-audit
|
|
210
192
|
|
|
211
193
|
## Live Footer
|
|
212
194
|
|
|
@@ -214,40 +196,44 @@ Local dev checkout:
|
|
|
214
196
|
— Model: claude-sonnet-4-6 | Provider: Anthropic | $4.82 saved | $1.20 cached | ENFORCE | LOCK | Quality | VIBE —
|
|
215
197
|
```
|
|
216
198
|
|
|
217
|
-
Provider, model, delegation savings, cache savings, stress
|
|
218
|
-
|
|
219
|
-
---
|
|
199
|
+
Provider, model, delegation savings, cache savings, stress level (low/elevated/high), lock/enforcement tags, optimization mode. Persisted in ~/.claude/delegation-state.json.
|
|
220
200
|
|
|
221
201
|
## Architecture
|
|
222
202
|
|
|
223
203
|
### Plugin Source
|
|
224
204
|
|
|
225
|
-
Single-file runtime `src/index.js` (5529+ lines). TypeScript source of truth at `src/vibeOS-lib/*.ts` and `src/utils/*.ts`. Build: `npm run build` (tsc +
|
|
205
|
+
Single-file runtime `src/index.js` (5529+ lines). TypeScript source of truth at `src/vibeOS-lib/*.ts` and `src/utils/*.ts`. Build: `npm run build` (tsc compile + sync-ts-build + deploy script).
|
|
226
206
|
|
|
227
|
-
### State Files (
|
|
207
|
+
### State Files (~/.claude/)
|
|
228
208
|
|
|
229
209
|
| File | Purpose |
|
|
230
210
|
|------|---------|
|
|
231
|
-
|
|
|
232
|
-
|
|
|
233
|
-
|
|
|
234
|
-
|
|
|
235
|
-
|
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
211
|
+
| delegation-state.json | Sessions, warns, cache hits, lifetime totals |
|
|
212
|
+
| model-tiers.json | brain/medium/cheap model IDs |
|
|
213
|
+
| project-states.json | Per-project memory, analytics, report references |
|
|
214
|
+
| reports/ | Saved report JSON files |
|
|
215
|
+
| savings-ledger.jsonl | Append-only savings and credit event log |
|
|
216
|
+
| global-learning.json | Cross-project pattern learning, pricing hints |
|
|
217
|
+
| model-pricing-cache.json | Cached pricing by model ID |
|
|
218
|
+
| active-jobs.json | In-flight delegation records |
|
|
219
|
+
| blackbox-state.json | Per-project resolution tracker, session outcomes |
|
|
220
|
+
| .flow-todo-queue.jsonl | Flow enforcer TODO queue |
|
|
221
|
+
| .flow-dedup-keys.json | Deduplication set for flow TODO |
|
|
222
|
+
| .enforcement-cooldown.jsonl | Per-tool cooldown for warn coalescing |
|
|
243
223
|
|
|
244
|
-
###
|
|
224
|
+
### Local vs Remote
|
|
245
225
|
|
|
226
|
+
**Fully functional locally:** Model tier classification, static pricing, stress scoring, context budget, turn classification, TDD skeleton generation, flow enforcement, savings ledger, session metrics, reports, footer, dashboard, smart cache, VibeBoX fallback.
|
|
246
227
|
|
|
247
|
-
|
|
228
|
+
**Requires remote API (api-token):** Bootstrap token exchange, advanced VibeBoX with full session history, dynamic per-prompt delegation, cross-session calibration, live pricing fetch beyond static map, learned subagent routing.
|
|
229
|
+
|
|
230
|
+
When the remote API is unreachable, the plugin degrades gracefully to rule-based local algorithms. Core enforcement features continue working.
|
|
231
|
+
|
|
232
|
+
### VibeBoX Decision Engine
|
|
248
233
|
|
|
234
|
+
7 sub-regimes (INIT, DIVERGENT, EXPLORING, REFINING, CONVERGING, CLOSED, LOOPING). Classification via entropy trends, action consistency, feature contradiction, embedding drift. 11 derived features per turn. 4 loop intervention levels. PIVOT/SWITCH detection. Outcome tracking from satisfaction signals.
|
|
249
235
|
|
|
250
|
-
Regime
|
|
236
|
+
Regime -> mode mapping via syncControlSettings():
|
|
251
237
|
|
|
252
238
|
| Regime | Mode | Enforce | Flow | TDD | Tier | Think |
|
|
253
239
|
|--------|------|---------|------|-----|------|-------|
|
|
@@ -259,35 +245,31 @@ Stress > 1.5 escalates any regime to quality.
|
|
|
259
245
|
|
|
260
246
|
### Remote API Server
|
|
261
247
|
|
|
262
|
-
`src/vibeOS-api-server/` — Fastify + SQLite at
|
|
248
|
+
`src/vibeOS-api-server/` — Fastify + SQLite at api.vibetheog.com. Endpoints: delegation check, tier routing, stress scoring, VibeBoX analysis/calibration, TDD skeleton gen, pattern observation, pricing fetch, context compression. Auth via VIBEOS_API_TOKEN. Client: `src/vibeOS-api-server/client.js` with automatic local fallback.
|
|
263
249
|
|
|
264
250
|
### Dashboard
|
|
265
251
|
|
|
266
|
-
SolidJS SPA at `src/dashboard/`. Build: `npm run build:dashboard` (vite). Served by MCP server or standalone. SSE
|
|
267
|
-
|
|
268
|
-
---
|
|
252
|
+
SolidJS SPA at `src/dashboard/`. Build: `npm run build:dashboard` (vite). Served by MCP server or standalone. SSE /events for real-time push.
|
|
269
253
|
|
|
270
254
|
## Environment Variables
|
|
271
255
|
|
|
272
256
|
| Variable | Default | Effect |
|
|
273
257
|
|----------|---------|--------|
|
|
274
|
-
|
|
|
275
|
-
|
|
|
276
|
-
|
|
|
277
|
-
|
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
|
|
|
281
|
-
|
|
|
282
|
-
|
|
283
|
-
---
|
|
258
|
+
| VIBEOS_API_URL | https://api.vibetheog.com | Remote API base URL |
|
|
259
|
+
| VIBEOS_API_TOKEN | unset | Remote API auth |
|
|
260
|
+
| VIBEOS_API_DISABLED | false | Invalidate alpha token |
|
|
261
|
+
| VIBEOS_API_BOOTSTRAP_TOKEN | unset | Bootstrap exchange |
|
|
262
|
+
| VIBEOS_API_ENABLED | true | Set false for local-only |
|
|
263
|
+
| CLAUDE_CREDIT_PERCENT | 100 | Credit override |
|
|
264
|
+
| CLAUDE_CONTEXT7_AVAILABLE | unset | Context7 optimization |
|
|
265
|
+
| VIBEOS_MCP_PORT | 3001 | MCP server port |
|
|
284
266
|
|
|
285
267
|
## Troubleshooting
|
|
286
268
|
|
|
287
269
|
| Symptom | Fix |
|
|
288
270
|
|---------|-----|
|
|
289
|
-
| Plugin not loading | Check
|
|
290
|
-
| Model won't switch | `trinity rebuild` then `trinity set brain
|
|
271
|
+
| Plugin not loading | Check opencode.json entry. Restart Desktop. |
|
|
272
|
+
| Model won't switch | `trinity rebuild` then `trinity set brain|medium|cheap` |
|
|
291
273
|
| Writes/edits blocked | Enforcement active — delegate to cheap tier |
|
|
292
274
|
| No footer visible | Verify plugin enabled, completions running |
|
|
293
275
|
| Dashboard blank | `npm run build` then restart |
|
package/bin/setup.js
CHANGED
|
@@ -1,48 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
4
5
|
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { homedir } from "node:os";
|
|
6
8
|
|
|
7
|
-
const
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const root = resolve(__dirname, "..");
|
|
8
11
|
const args = process.argv.slice(2);
|
|
9
|
-
const command = args[0] ?? "setup";
|
|
10
12
|
const isProject = args.includes("--project");
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
// Deploy plugin files to ~/.config/opencode/plugins/ and register globally
|
|
15
|
+
const deployScript = resolve(root, "scripts", "deploy.mjs");
|
|
16
|
+
if (!existsSync(deployScript)) {
|
|
17
|
+
console.error("Fatal: scripts/deploy.mjs not found at", deployScript);
|
|
14
18
|
process.exit(1);
|
|
15
19
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let config = {};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
config = {};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
execSync(`node "${deployScript}"`, { stdio: "inherit", cwd: root });
|
|
21
|
+
|
|
22
|
+
// For per-project setup, also register in project-level opencode.json
|
|
23
|
+
if (isProject) {
|
|
24
|
+
const configPath = resolve(process.cwd(), "opencode.json");
|
|
25
|
+
let config = {};
|
|
26
|
+
if (existsSync(configPath)) {
|
|
27
|
+
try {
|
|
28
|
+
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
29
|
+
} catch {
|
|
30
|
+
config = {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) config = {};
|
|
34
|
+
if (!config.$schema) config.$schema = "https://opencode.ai/config.json";
|
|
35
|
+
if (!Array.isArray(config.plugin)) config.plugin = [];
|
|
36
|
+
const pluginRef = resolve(homedir(), ".config", "opencode", "plugins", "vibeOS.js");
|
|
37
|
+
if (!config.plugin.includes(pluginRef)) {
|
|
38
|
+
config.plugin.push(pluginRef);
|
|
39
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
40
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
41
|
+
}
|
|
42
|
+
console.log(`vibeOS registered in ${configPath}`);
|
|
34
43
|
}
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
config.plugin = [];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!config.plugin.includes(pkgName)) {
|
|
41
|
-
config.plugin.push(pkgName);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
45
|
-
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
46
|
-
|
|
47
|
-
console.log(`${pkgName} registered in ${configPath}`);
|
|
48
|
-
console.log("Restart OpenCode to activate the plugin.");
|
|
45
|
+
console.log("Done. Restart OpenCode to activate the plugin.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeostheog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.6",
|
|
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",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"checkpoint:validate": "node scripts/checkpoint-validate.mjs",
|
|
15
15
|
"test:scripts": "node --test scripts/tests/checkpoint-validate.test.mjs tests/release-pack.test.mjs",
|
|
16
16
|
"ts:audit": "node scripts/ts-audit.mjs",
|
|
17
|
-
"test": "VIBEOS_MCP_PORT=0 node --test --test-timeout=240000 tests/deep_integration.test.mjs tests/production_regressions.test.mjs tests/release_hardening_tigerteam.test.mjs tests/test_api_migration.neutral.test.mjs tests/test_const_assignment_regression.test.mjs tests/test_delegation_enforcer.test.mjs tests/test_diagnose_cmd.test.mjs tests/test_install_and_recovery.test.mjs tests/test_internals_stress_patterns_offtopic.test.mjs tests/test_saveos_e2e_cleanup.test.mjs tests/test_tdd_enforcer.test.mjs src/tests/*.test.js src/utils/tests/*.test.mjs \"src/vibeOS-lib/tests/auto-select-mode.test.mjs\" \"src/vibeOS-lib/tests/blackbox-regression.test.mjs\" \"src/vibeOS-lib/tests/blackbox-smoke.test.mjs\" \"src/vibeOS-lib/tests/budget-first-mode.test.mjs\" \"src/vibeOS-lib/tests/flow-enforcer.test.mjs\" \"src/vibeOS-lib/tests/flow-secrets.test.mjs\" \"src/vibeOS-lib/tests/session-metrics.test.mjs\" \"src/vibeOS-lib/tests/test_stress.test.mjs\"",
|
|
18
|
-
"test:ci": "VIBEOS_MCP_PORT=0 node --test --test-timeout=30000 tests/production_regressions.test.mjs tests/release_hardening_tigerteam.test.mjs tests/test_const_assignment_regression.test.mjs tests/test_diagnose_cmd.test.mjs tests/test_install_and_recovery.test.mjs tests/test_saveos_e2e_cleanup.test.mjs tests/test_tdd_enforcer.test.mjs src/tests/*.test.js src/utils/tests/*.test.mjs \"src/vibeOS-lib/tests/auto-select-mode.test.mjs\" \"src/vibeOS-lib/tests/blackbox-regression.test.mjs\" \"src/vibeOS-lib/tests/blackbox-smoke.test.mjs\" \"src/vibeOS-lib/tests/budget-first-mode.test.mjs\" \"src/vibeOS-lib/tests/flow-enforcer.test.mjs\" \"src/vibeOS-lib/tests/flow-secrets.test.mjs\" \"src/vibeOS-lib/tests/session-metrics.test.mjs\" \"src/vibeOS-lib/tests/test_stress.test.mjs\"",
|
|
17
|
+
"test": "VIBEOS_MCP_PORT=0 node --test --test-timeout=240000 tests/deep_integration.test.mjs tests/production_regressions.test.mjs tests/release_hardening_tigerteam.test.mjs tests/test_api_migration.neutral.test.mjs tests/test_const_assignment_regression.test.mjs tests/test_delegation_enforcer.test.mjs tests/test_diagnose_cmd.test.mjs tests/test_install_and_recovery.test.mjs tests/test_internals_stress_patterns_offtopic.test.mjs tests/test_saveos_e2e_cleanup.test.mjs tests/test_tdd_enforcer.test.mjs tests/test_10fixes_regression.test.mjs tests/test_cross_session_regression.test.mjs tests/test_mega_all_fixes.test.mjs tests/test_smart_cache_regression.test.mjs src/tests/*.test.js src/utils/tests/*.test.mjs \"src/vibeOS-lib/tests/auto-select-mode.test.mjs\" \"src/vibeOS-lib/tests/blackbox-regression.test.mjs\" \"src/vibeOS-lib/tests/blackbox-smoke.test.mjs\" \"src/vibeOS-lib/tests/budget-first-mode.test.mjs\" \"src/vibeOS-lib/tests/flow-enforcer.test.mjs\" \"src/vibeOS-lib/tests/flow-secrets.test.mjs\" \"src/vibeOS-lib/tests/session-metrics.test.mjs\" \"src/vibeOS-lib/tests/test_stress.test.mjs\"",
|
|
18
|
+
"test:ci": "VIBEOS_MCP_PORT=0 node --test --test-timeout=30000 tests/production_regressions.test.mjs tests/release_hardening_tigerteam.test.mjs tests/test_const_assignment_regression.test.mjs tests/test_diagnose_cmd.test.mjs tests/test_install_and_recovery.test.mjs tests/test_saveos_e2e_cleanup.test.mjs tests/test_tdd_enforcer.test.mjs tests/test_10fixes_regression.test.mjs tests/test_cross_session_regression.test.mjs tests/test_mega_all_fixes.test.mjs tests/test_smart_cache_regression.test.mjs src/tests/*.test.js src/utils/tests/*.test.mjs \"src/vibeOS-lib/tests/auto-select-mode.test.mjs\" \"src/vibeOS-lib/tests/blackbox-regression.test.mjs\" \"src/vibeOS-lib/tests/blackbox-smoke.test.mjs\" \"src/vibeOS-lib/tests/budget-first-mode.test.mjs\" \"src/vibeOS-lib/tests/flow-enforcer.test.mjs\" \"src/vibeOS-lib/tests/flow-secrets.test.mjs\" \"src/vibeOS-lib/tests/session-metrics.test.mjs\" \"src/vibeOS-lib/tests/test_stress.test.mjs\"",
|
|
19
19
|
"codex:guard": "bash plugins/vibetheog-codex/scripts/run-guard.sh",
|
|
20
20
|
"codex:guard:full": "VIBETHEOG_GUARD_FULL=1 bash plugins/vibetheog-codex/scripts/run-guard.sh",
|
|
21
21
|
"codex:hook:precommit": "bash plugins/vibetheog-codex/hooks/pre-commit.sh",
|
package/src/lib/api-client.js
CHANGED
|
@@ -38,6 +38,66 @@ export class VibeOSNetworkError extends Error {
|
|
|
38
38
|
this.name = "VibeOSNetworkError";
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
const ANOMALY_BURST_WINDOW_MS = 5000;
|
|
42
|
+
const ANOMALY_BURST_THRESHOLD = 10;
|
|
43
|
+
const ANOMALY_FREQ_WINDOW_MS = 600_000;
|
|
44
|
+
const ANOMALY_STDDEV_FACTOR = 3;
|
|
45
|
+
const ANOMALY_WARMUP_MS = 30_000;
|
|
46
|
+
const ANOMALY_COOLDOWN_MS = 120_000;
|
|
47
|
+
class TokenAnomalyDetector {
|
|
48
|
+
burstHistory = [];
|
|
49
|
+
freqHistory = [];
|
|
50
|
+
lastWarnTime = 0;
|
|
51
|
+
anomalyTriggered = false;
|
|
52
|
+
disabled = false;
|
|
53
|
+
startedAt = Date.now();
|
|
54
|
+
get isWarmup() {
|
|
55
|
+
return Date.now() - this.startedAt < ANOMALY_WARMUP_MS;
|
|
56
|
+
}
|
|
57
|
+
record() {
|
|
58
|
+
if (this.disabled || this.isWarmup)
|
|
59
|
+
return;
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
this.burstHistory = this.burstHistory.filter(t => now - t < ANOMALY_BURST_WINDOW_MS);
|
|
62
|
+
this.burstHistory.push(now);
|
|
63
|
+
this.freqHistory.push(now);
|
|
64
|
+
}
|
|
65
|
+
checkBurst() {
|
|
66
|
+
return this.burstHistory.length > ANOMALY_BURST_THRESHOLD;
|
|
67
|
+
}
|
|
68
|
+
checkFrequency() {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const window = this.freqHistory.filter(t => now - t < ANOMALY_FREQ_WINDOW_MS);
|
|
71
|
+
if (window.length < 10)
|
|
72
|
+
return false;
|
|
73
|
+
const mean = window.length / (ANOMALY_FREQ_WINDOW_MS / 60_000);
|
|
74
|
+
const recent = this.burstHistory.length / (ANOMALY_BURST_WINDOW_MS / 1000);
|
|
75
|
+
return recent > mean * ANOMALY_STDDEV_FACTOR;
|
|
76
|
+
}
|
|
77
|
+
throttleIfAnomalous() {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
if (this.disabled || this.isWarmup)
|
|
80
|
+
return false;
|
|
81
|
+
if (this.anomalyTriggered)
|
|
82
|
+
return true;
|
|
83
|
+
if (this.checkBurst() || this.checkFrequency()) {
|
|
84
|
+
this.anomalyTriggered = true;
|
|
85
|
+
this.lastWarnTime = now;
|
|
86
|
+
console.error("[vibeOS] Token anomaly detected — throttling API calls");
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (this.lastWarnTime && now - this.lastWarnTime > ANOMALY_COOLDOWN_MS) {
|
|
90
|
+
this.anomalyTriggered = false;
|
|
91
|
+
}
|
|
92
|
+
return this.anomalyTriggered;
|
|
93
|
+
}
|
|
94
|
+
reset() {
|
|
95
|
+
this.burstHistory = [];
|
|
96
|
+
this.freqHistory = [];
|
|
97
|
+
this.anomalyTriggered = false;
|
|
98
|
+
this.lastWarnTime = 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
41
101
|
function normalizeApiToken(token, fallback = "") {
|
|
42
102
|
const clean = String(token || "").trim();
|
|
43
103
|
return API_TOKEN_RE.test(clean) ? clean : fallback;
|
|
@@ -229,6 +289,15 @@ export class VibeOSApiClient {
|
|
|
229
289
|
stress_score: stressScore,
|
|
230
290
|
});
|
|
231
291
|
}
|
|
292
|
+
async getModes() {
|
|
293
|
+
return this.request("/api/v1/modes", {}, "GET");
|
|
294
|
+
}
|
|
295
|
+
async selectMode(mode) {
|
|
296
|
+
return this.request("/api/v1/mode/select", { mode });
|
|
297
|
+
}
|
|
298
|
+
async classifyQuery(text, state) {
|
|
299
|
+
return this.request("/api/v1/mode/classify", { text, state: state || {} });
|
|
300
|
+
}
|
|
232
301
|
async classifyTier(model, customRegex = null) {
|
|
233
302
|
return this.request("/api/v1/tier/classify", { model, custom_regex: customRegex });
|
|
234
303
|
}
|
|
@@ -424,6 +493,19 @@ export let VIBEOS_API_DISABLED = readApiDisabledFromDisk() || isTruthyFlag(proce
|
|
|
424
493
|
export let VIBEOS_API_TOKEN = VIBEOS_API_DISABLED ? "" : (readTokenFromDisk() || normalizeApiToken(process.env.VIBEOS_API_TOKEN, "") || EMBEDDED_API_TOKEN);
|
|
425
494
|
export let VIBEOS_API_BOOTSTRAP_TOKEN = VIBEOS_API_DISABLED ? "" : (readBootstrapTokenFromDisk() || process.env.VIBEOS_API_BOOTSTRAP_TOKEN || "");
|
|
426
495
|
export let VIBEOS_API_ENABLED = !VIBEOS_API_DISABLED && process.env.VIBEOS_API_ENABLED !== "false" && (!!VIBEOS_API_TOKEN || !!VIBEOS_API_BOOTSTRAP_TOKEN);
|
|
496
|
+
let _anomalyDetector = null;
|
|
497
|
+
function getAnomalyDetector() {
|
|
498
|
+
if (!_anomalyDetector)
|
|
499
|
+
_anomalyDetector = new TokenAnomalyDetector();
|
|
500
|
+
return _anomalyDetector;
|
|
501
|
+
}
|
|
502
|
+
export function setAnomalyDetection(enabled) {
|
|
503
|
+
const d = getAnomalyDetector();
|
|
504
|
+
d.disabled = !enabled;
|
|
505
|
+
if (enabled)
|
|
506
|
+
d.reset();
|
|
507
|
+
console.error(`[vibeOS] Anomaly detection ${enabled ? "enabled" : "disabled"}`);
|
|
508
|
+
}
|
|
427
509
|
function persistBootstrapToken(token) {
|
|
428
510
|
const clean = String(token || "").trim();
|
|
429
511
|
try {
|
|
@@ -451,6 +533,8 @@ export function setApiToken(newToken) {
|
|
|
451
533
|
VIBEOS_API_BOOTSTRAP_TOKEN = readBootstrapTokenFromDisk() || VIBEOS_API_BOOTSTRAP_TOKEN;
|
|
452
534
|
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && (!!VIBEOS_API_TOKEN || !!VIBEOS_API_BOOTSTRAP_TOKEN);
|
|
453
535
|
persistPrimaryApiEnvState({ token: VIBEOS_API_TOKEN, disabled: false });
|
|
536
|
+
if (_anomalyDetector)
|
|
537
|
+
_anomalyDetector.reset();
|
|
454
538
|
console.error("[vibeOS] API token updated via setApiToken");
|
|
455
539
|
}
|
|
456
540
|
catch (e) {
|
|
@@ -466,6 +550,8 @@ export function invalidateApiToken() {
|
|
|
466
550
|
_apiClient = null;
|
|
467
551
|
_apiFallbackMode = false;
|
|
468
552
|
_apiFallbackSince = null;
|
|
553
|
+
if (_anomalyDetector)
|
|
554
|
+
_anomalyDetector.reset();
|
|
469
555
|
persistBootstrapToken("");
|
|
470
556
|
persistPrimaryApiEnvState({ token: "", disabled: true });
|
|
471
557
|
resetApiConnection();
|
|
@@ -615,6 +701,15 @@ export async function remoteCall(method, args, fallbackFn) {
|
|
|
615
701
|
return fallbackFn();
|
|
616
702
|
return null;
|
|
617
703
|
}
|
|
704
|
+
const detector = getAnomalyDetector();
|
|
705
|
+
detector.record();
|
|
706
|
+
if (detector.throttleIfAnomalous()) {
|
|
707
|
+
// Don't set _apiFallbackMode — detector's own cooldown resets it.
|
|
708
|
+
// This lets the API retry naturally after the throttle window.
|
|
709
|
+
if (fallbackFn)
|
|
710
|
+
return fallbackFn();
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
618
713
|
try {
|
|
619
714
|
const client = getApiClient();
|
|
620
715
|
if (!client) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
* SPDX-FileCopyrightText: 2026 vibeOS <https://github.com/DrunkkToys/vibeOS>
|
|
5
|
+
*
|
|
6
|
+
* Cost anomaly detector — monitors per-turn model costs and warns
|
|
7
|
+
* when a model cost spikes significantly above the running average.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const COST_WINDOW_SIZE = 20
|
|
11
|
+
const COST_ANOMALY_THRESHOLD = 3
|
|
12
|
+
const COST_WARMUP_SAMPLES = 5
|
|
13
|
+
|
|
14
|
+
export class CostAnomalyDetector {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.costHistory = []
|
|
17
|
+
this.disabled = false
|
|
18
|
+
this.currentAnomalyModel = null
|
|
19
|
+
this.currentAnomalyCost = 0
|
|
20
|
+
this.currentAnomalyMean = 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
record(cost) {
|
|
24
|
+
if (this.disabled) return
|
|
25
|
+
this.costHistory.push(cost)
|
|
26
|
+
if (this.costHistory.length > COST_WINDOW_SIZE) {
|
|
27
|
+
this.costHistory.shift()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get mean() {
|
|
32
|
+
if (this.costHistory.length === 0) return 0
|
|
33
|
+
return this.costHistory.reduce((a, b) => a + b, 0) / this.costHistory.length
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
checkAnomaly(model, cost) {
|
|
37
|
+
if (this.disabled) return false
|
|
38
|
+
if (this.costHistory.length < COST_WARMUP_SAMPLES) return false
|
|
39
|
+
const avg = this.mean
|
|
40
|
+
if (avg <= 0 || cost <= avg) return false
|
|
41
|
+
const ratio = cost / avg
|
|
42
|
+
if (ratio > COST_ANOMALY_THRESHOLD) {
|
|
43
|
+
this.currentAnomalyModel = model
|
|
44
|
+
this.currentAnomalyCost = cost
|
|
45
|
+
this.currentAnomalyMean = avg
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
clearAnomaly() {
|
|
52
|
+
this.currentAnomalyModel = null
|
|
53
|
+
this.currentAnomalyCost = 0
|
|
54
|
+
this.currentAnomalyMean = 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
reset() {
|
|
58
|
+
this.costHistory = []
|
|
59
|
+
this.clearAnomaly()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let _costDetector = null
|
|
64
|
+
|
|
65
|
+
export function getCostAnomalyDetector() {
|
|
66
|
+
if (!_costDetector) _costDetector = new CostAnomalyDetector()
|
|
67
|
+
return _costDetector
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function setCostAnomalyDetection(enabled) {
|
|
71
|
+
const d = getCostAnomalyDetector()
|
|
72
|
+
d.disabled = !enabled
|
|
73
|
+
if (enabled) d.reset()
|
|
74
|
+
console.error(`[vibeOS] Cost anomaly detection ${enabled ? "enabled" : "disabled"}`)
|
|
75
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
-
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { join, basename } from "node:path";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { currentModel, currentProjectFingerprint, currentProjectName, _blackboxEnabled, loadSelection, writeSelection, safeJsonParse, applyDecadence, getSessionScratchpadDir, ensureSessionScratchpadDirs, indexAppend, briefedProjects, getActiveJobForProject, loadTodos, promotedProjectPatterns, detectTechStack, projectFingerprint, TRINITY_OPENCODE_CONFIG, TIERS_FILE, loadGlobalLearning, setCurrentProjectFingerprint, setCurrentProjectName, stableJson, TOOL_NAME_NORMALIZE, _cacheDb, recordCacheSaving, } from "../state.js";
|
|
5
|
+
import { currentModel, currentProjectFingerprint, currentProjectName, _blackboxEnabled, loadSelection, writeSelection, safeJsonParse, applyDecadence, getSessionScratchpadDir, ensureSessionScratchpadDirs, indexAppend, briefedProjects, getActiveJobForProject, loadTodos, promotedProjectPatterns, detectTechStack, projectFingerprint, SCRATCHPAD_ROOT, TRINITY_OPENCODE_CONFIG, TIERS_FILE, loadGlobalLearning, setCurrentProjectFingerprint, setCurrentProjectName, stableJson, TOOL_NAME_NORMALIZE, _cacheDb, recordCacheSaving, } from "../state.js";
|
|
6
6
|
import { applySlot, TRINITY_CHEAP, TRINITY_MEDIUM, cacheSavePer1MInputTokens, } from "../pricing.js";
|
|
7
7
|
import { scoreStress, classifyTurnSimple, loadOptimizationMode, saveOptimizationMode, selectOptimizationModeRemote, computeControlVector, getBlackboxTracker, loadBlackboxState as loadBlackboxStateFromCtx, saveBlackboxState as saveBlackboxStateToCtx, extractLastUserText, isLikelyOffTopic, fetchBlackboxEnrichment, estimateContextBudget, buildControlHistoryEntry, } from "../turn-classify.js";
|
|
8
8
|
import { applyBudgetFirstMode, peekBudgetFirstMode } from "../mode-policy.js";
|
|
@@ -277,25 +277,31 @@ function compressToolOutputs(messages) {
|
|
|
277
277
|
continue;
|
|
278
278
|
const hash = createHash("sha256")
|
|
279
279
|
.update(`tool_result\n${raw}\n`).digest("hex").slice(0, 16);
|
|
280
|
-
const
|
|
280
|
+
const globalDir = join(SCRATCHPAD_ROOT, "by-hash");
|
|
281
|
+
const sessPath = join(getSessionScratchpadDir(), `${hash}.txt`);
|
|
282
|
+
const globalPath = join(globalDir, `${hash}.txt`);
|
|
281
283
|
try {
|
|
284
|
+
mkdirSync(globalDir, { recursive: true });
|
|
282
285
|
ensureSessionScratchpadDirs();
|
|
283
|
-
if (!existsSync(
|
|
284
|
-
writeFileSync(
|
|
286
|
+
if (!existsSync(globalPath)) {
|
|
287
|
+
writeFileSync(globalPath, raw);
|
|
285
288
|
indexAppend(hash, part.tool, raw.length);
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
289
|
+
// Clean up any existing session-local copy
|
|
290
|
+
if (existsSync(sessPath))
|
|
291
|
+
rmSync(sessPath, { force: true });
|
|
292
|
+
}
|
|
293
|
+
// Create pointer file for input-hash-based lookup
|
|
294
|
+
const invPart = parts.slice(0, parts.indexOf(part)).reverse().find((p) => p?.type === "tool" && p?.tool === part.tool && p?.state?.input && p?.state?.status !== "completed");
|
|
295
|
+
if (invPart?.state?.input) {
|
|
296
|
+
const toolKey = TOOL_NAME_NORMALIZE[part.tool] || part.tool;
|
|
297
|
+
const inputHash = createHash("sha256")
|
|
298
|
+
.update(`${toolKey}\n${stableJson(invPart.state.input)}\n`)
|
|
299
|
+
.digest("hex").slice(0, 16);
|
|
300
|
+
const ptrPath = join(getSessionScratchpadDir(), `${inputHash}.ptr`);
|
|
301
|
+
try {
|
|
302
|
+
writeFileSync(ptrPath, JSON.stringify({ contentHash: hash, tool: part.tool }));
|
|
298
303
|
}
|
|
304
|
+
catch { }
|
|
299
305
|
}
|
|
300
306
|
}
|
|
301
307
|
catch (err) {
|
|
@@ -305,7 +311,7 @@ function compressToolOutputs(messages) {
|
|
|
305
311
|
if (!isCold)
|
|
306
312
|
continue;
|
|
307
313
|
const summary = raw.slice(0, 200).replace(/\n+/g, " ").trim() + (raw.length > 200 ? "\u2026" : "");
|
|
308
|
-
const ref = `${COMPRESS_MARKER} [${raw.length} chars compressed -- cold storage at ${
|
|
314
|
+
const ref = `${COMPRESS_MARKER} [${raw.length} chars compressed -- cold storage at ${globalPath}] ` +
|
|
309
315
|
`[summary] ${summary}`;
|
|
310
316
|
state.output = ref;
|
|
311
317
|
compressedBytes += raw.length - ref.length;
|
|
@@ -9,6 +9,7 @@ import { scoreStress, extractFirstWordFromArgs, shouldLogWarn, isUserAskingForTe
|
|
|
9
9
|
import { saveReport } from "../reporting.js";
|
|
10
10
|
import { loadCredit } from "../credit-api.js";
|
|
11
11
|
import { remoteCall, VIBEOS_API_ENABLED } from "../api-client.js";
|
|
12
|
+
import { getCostAnomalyDetector } from "../cost-anomaly.js";
|
|
12
13
|
import { checkFlowRules } from "../../vibeOS-lib/flow-enforcer.js";
|
|
13
14
|
import { computeDifficulty, addRouteEdge, predictBestModel, hashQuery } from "../../vibeOS-lib/ml-router.js";
|
|
14
15
|
import { addCacheEntry, recordCacheStats, predictCacheHit } from "../../vibeOS-lib/smart-cache.js";
|
|
@@ -487,6 +488,25 @@ export const onToolExecuteBefore = async (input, output) => {
|
|
|
487
488
|
return;
|
|
488
489
|
}
|
|
489
490
|
}
|
|
491
|
+
// Cost anomaly detection: warn if this model's per-turn cost spikes
|
|
492
|
+
// significantly above the session rolling average.
|
|
493
|
+
const costDetector = getCostAnomalyDetector();
|
|
494
|
+
if (!costDetector.disabled && currentModel) {
|
|
495
|
+
const modelCost = modelCostPerTurn(currentModel);
|
|
496
|
+
const fullModelName = currentModel;
|
|
497
|
+
if (costDetector.checkAnomaly(fullModelName, modelCost)) {
|
|
498
|
+
const avg = costDetector.currentAnomalyMean;
|
|
499
|
+
const ratio = avg > 0 ? (modelCost / avg).toFixed(1) : "?";
|
|
500
|
+
const msg = `Cost spike: ${shortModelName(fullModelName)} at $${modelCost.toFixed(4)}/turn — ${ratio}x higher than recent avg of $${avg.toFixed(4)}. Run \`trinity cheap\` or \`trinity medium\` to save.`;
|
|
501
|
+
if (shouldLogWarn(`${t}|cost-anomaly|${fullModelName}|${modelCost.toFixed(4)}`)) {
|
|
502
|
+
console.error(`[vibeOS] [cost-anomaly] ${msg}`);
|
|
503
|
+
}
|
|
504
|
+
pendingUiNote = `🚨 ${msg}`;
|
|
505
|
+
enforcementBlocked = true;
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
costDetector.record(modelCost);
|
|
509
|
+
}
|
|
490
510
|
// Credit < 40%: non-task tool — record and nudge to step aside.
|
|
491
511
|
if (_credit < 40 && !compatibilityMode) {
|
|
492
512
|
const total = recordSaving(t, "credit<40% high-tier", _estOpus, { firstWord: _firstWord });
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Mode Router — 10 modes, 4 tiers. Full type-safe hierarchy.
|
|
2
|
+
// Branded modes: user-selected strategy + tier pipeline.
|
|
3
|
+
// Runtime modes: classifier-selected behavior per query.
|
|
4
|
+
export const TIERS = {
|
|
5
|
+
brain: { cost: 0.002, desc: "v4 Pro tier — max quality" },
|
|
6
|
+
medium: { cost: 0.000182, desc: "v4 Flash tier — balanced" },
|
|
7
|
+
cheap: { cost: 0, desc: "Chat tier — free" },
|
|
8
|
+
local: { cost: 0, desc: "Ollama local model" },
|
|
9
|
+
};
|
|
10
|
+
export const BRANDED_MODES = [
|
|
11
|
+
{
|
|
12
|
+
id: "vibeultrax", index: 1, name: "VibeUltraX", icon: "\u{1F3C6}",
|
|
13
|
+
pipeline: ["local", "medium", "brain"],
|
|
14
|
+
thinking: "full", tdd: "quality", enforcement: "strict", flow: "strict",
|
|
15
|
+
qualityVsBrain: 107, costVsBrain: 58,
|
|
16
|
+
desc: "3-model debate: local proposes, medium reviews, brain refines.",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "vibeqmax", index: 2, name: "VibeQMaX", icon: "\u{2B50}",
|
|
20
|
+
pipeline: ["brain"],
|
|
21
|
+
thinking: "full", tdd: "quality", enforcement: "strict", flow: "strict",
|
|
22
|
+
qualityVsBrain: 100, costVsBrain: 50,
|
|
23
|
+
desc: "Brain tier only. Same quality as Raw Brain at half cost.",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "vibemax", index: 3, name: "VibeMaX", icon: "\u{26A1}",
|
|
27
|
+
pipeline: ["medium"],
|
|
28
|
+
thinking: "off", tdd: "lazy", enforcement: "relaxed", flow: "audit",
|
|
29
|
+
qualityVsBrain: 75, costVsBrain: 18, default: true,
|
|
30
|
+
desc: "Default mode. Medium tier auto-escalate. Speed-first.",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
export const RUNTIME_MODES = [
|
|
34
|
+
{
|
|
35
|
+
id: "balanced", index: 4, name: "Balanced", icon: "\u{2696}\u{FE0F}",
|
|
36
|
+
pipeline: ["medium"],
|
|
37
|
+
thinking: "brief", tdd: "lazy", enforcement: "relaxed", flow: "audit",
|
|
38
|
+
qualityVsBrain: 70, costVsBrain: 30, defaultRuntime: true,
|
|
39
|
+
desc: "Default runtime. Auto-selects behavior per query.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "speed", index: 5, name: "Speed", icon: "\u{1F680}",
|
|
43
|
+
pipeline: ["medium"],
|
|
44
|
+
thinking: "off", tdd: "off", enforcement: "relaxed", flow: "off",
|
|
45
|
+
qualityVsBrain: 55, costVsBrain: 32,
|
|
46
|
+
desc: "Medium tier. Fast responses, no overhead.",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "budget", index: 6, name: "Budget", icon: "\u{1F4B8}",
|
|
50
|
+
pipeline: ["cheap"],
|
|
51
|
+
thinking: "off", tdd: "off", enforcement: "off", flow: "off",
|
|
52
|
+
qualityVsBrain: 40, costVsBrain: 100,
|
|
53
|
+
desc: "Cheap tier only. Zero cost.",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "quality", index: 7, name: "Quality", icon: "\u{1F4AF}",
|
|
57
|
+
pipeline: ["brain"],
|
|
58
|
+
thinking: "full", tdd: "quality", enforcement: "strict", flow: "strict",
|
|
59
|
+
qualityVsBrain: 100, costVsBrain: 60,
|
|
60
|
+
desc: "Brain tier with full thinking and enforcement.",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "audit", index: 8, name: "Audit", icon: "\u{1F50D}",
|
|
64
|
+
pipeline: ["brain"],
|
|
65
|
+
thinking: "full", tdd: "quality", enforcement: "strict", flow: "strict",
|
|
66
|
+
qualityVsBrain: 100, costVsBrain: 55,
|
|
67
|
+
desc: "Brain tier security audit. OWASP validation.",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "longrun", index: 9, name: "Longrun", icon: "\u{1F3C3}",
|
|
71
|
+
pipeline: ["brain"],
|
|
72
|
+
thinking: "full", tdd: "quality", enforcement: "strict", flow: "strict",
|
|
73
|
+
qualityVsBrain: 100, costVsBrain: 70,
|
|
74
|
+
desc: "Brain tier extended session. Full context.",
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
export const RAW_MODE = {
|
|
78
|
+
id: "raw", index: 10, name: "Raw Brain", icon: "\u{1F9E0}",
|
|
79
|
+
pipeline: ["brain"],
|
|
80
|
+
thinking: "full", tdd: "\u2014", enforcement: "\u2014", flow: "\u2014",
|
|
81
|
+
qualityVsBrain: 100, costVsBrain: 0,
|
|
82
|
+
desc: "Pure v4 Pro baseline. No vibeOS overhead.",
|
|
83
|
+
};
|
|
84
|
+
export const ALL_MODES = [...BRANDED_MODES, ...RUNTIME_MODES, RAW_MODE];
|
|
85
|
+
export function getMode(id) {
|
|
86
|
+
return ALL_MODES.find(m => m.id === id) ?? getDefault();
|
|
87
|
+
}
|
|
88
|
+
export function getDefault() {
|
|
89
|
+
return BRANDED_MODES.find(m => m.default);
|
|
90
|
+
}
|
|
91
|
+
export function getDefaultRuntime() {
|
|
92
|
+
return RUNTIME_MODES.find(m => m.defaultRuntime);
|
|
93
|
+
}
|
|
94
|
+
export function getBrandedModes() { return BRANDED_MODES; }
|
|
95
|
+
export function getRuntimeModes() { return RUNTIME_MODES; }
|
|
96
|
+
export function resolveTierModels(mode, tierMap) {
|
|
97
|
+
const models = mode.pipeline.map(t => tierMap[t] ?? t);
|
|
98
|
+
const costs = mode.pipeline.map(t => TIERS[t]?.cost ?? 0);
|
|
99
|
+
return { models, totalCost: costs.reduce((s, c) => s + c, 0) };
|
|
100
|
+
}
|
package/src/lib/state.js
CHANGED
|
@@ -907,7 +907,7 @@ function getScratchpadHit(toolLower, args, baseDir = null) {
|
|
|
907
907
|
const globalDir = SCRATCHPAD_GLOBAL_DIR;
|
|
908
908
|
const sessionPath = join(sessionDir, `${hash}.txt`);
|
|
909
909
|
const globalPath = join(globalDir, `${hash}.txt`);
|
|
910
|
-
let fullPath = existsSync(
|
|
910
|
+
let fullPath = existsSync(globalPath) ? globalPath : (existsSync(sessionPath) ? sessionPath : null);
|
|
911
911
|
if (!fullPath) {
|
|
912
912
|
// Try pointer files (created by compressToolOutputs mapping input hash -> content hash)
|
|
913
913
|
const ptrSessionPath = join(sessionDir, `${hash}.ptr`);
|
|
@@ -921,7 +921,7 @@ function getScratchpadHit(toolLower, args, baseDir = null) {
|
|
|
921
921
|
resolvedHash = ptrData.contentHash;
|
|
922
922
|
const rSessionPath = join(sessionDir, `${resolvedHash}.txt`);
|
|
923
923
|
const rGlobalPath = join(globalDir, `${resolvedHash}.txt`);
|
|
924
|
-
fullPath = existsSync(
|
|
924
|
+
fullPath = existsSync(rGlobalPath) ? rGlobalPath : (existsSync(rSessionPath) ? rSessionPath : null);
|
|
925
925
|
}
|
|
926
926
|
}
|
|
927
927
|
catch { }
|
|
@@ -1454,9 +1454,9 @@ function recordDelegation(tool, saveEst, meta = {}) {
|
|
|
1454
1454
|
s.sessions ??= {};
|
|
1455
1455
|
const sid = _OC_SID;
|
|
1456
1456
|
s.sessions[sid] ??= { started: now, session_started_at: now, source: "opencode", tool_counts: {}, warns: [] };
|
|
1457
|
-
if (currentProjectFingerprint)
|
|
1457
|
+
if (currentProjectFingerprint && !s.sessions[sid].project_fingerprint)
|
|
1458
1458
|
s.sessions[sid].project_fingerprint = currentProjectFingerprint;
|
|
1459
|
-
if (currentProjectName)
|
|
1459
|
+
if (currentProjectName && !s.sessions[sid].project_name)
|
|
1460
1460
|
s.sessions[sid].project_name = currentProjectName;
|
|
1461
1461
|
s.sessions[sid].total_savings_usd = roundUsd(Number(s.sessions[sid].total_savings_usd || 0) + delta);
|
|
1462
1462
|
_pruneOldSessions(s);
|
|
@@ -1474,31 +1474,37 @@ function recordCacheSaving(tool, saveEst, meta = {}) {
|
|
|
1474
1474
|
const now = new Date().toISOString();
|
|
1475
1475
|
const delta = Number(saveEst || 0);
|
|
1476
1476
|
s.lifetime ??= { warn_count: 0, total_savings_usd: 0, last_updated: "" };
|
|
1477
|
-
s.lifetime.cache_savings_usd = roundUsd(Number(s.lifetime.cache_savings_usd || 0) + delta);
|
|
1478
1477
|
s.lifetime.last_updated = now;
|
|
1479
1478
|
s.sessions ??= {};
|
|
1480
1479
|
const sid = _OC_SID;
|
|
1481
1480
|
s.sessions[sid] ??= { started: now, session_started_at: now, source: "opencode", tool_counts: {}, warns: [] };
|
|
1482
|
-
if (currentProjectFingerprint)
|
|
1481
|
+
if (currentProjectFingerprint && !s.sessions[sid].project_fingerprint)
|
|
1483
1482
|
s.sessions[sid].project_fingerprint = currentProjectFingerprint;
|
|
1484
|
-
if (currentProjectName)
|
|
1483
|
+
if (currentProjectName && !s.sessions[sid].project_name)
|
|
1485
1484
|
s.sessions[sid].project_name = currentProjectName;
|
|
1486
1485
|
s.sessions[sid].session_cache_dir = getSessionScratchpadDir();
|
|
1487
1486
|
s.sessions[sid].tool_counts[tool] = (s.sessions[sid].tool_counts[tool] || 0) + 1;
|
|
1488
|
-
s.sessions[sid].cache_savings_usd = roundUsd(Number(s.sessions[sid].cache_savings_usd || 0) + delta);
|
|
1489
1487
|
if (meta?.hash) {
|
|
1490
1488
|
s.sessions[sid].cache_hits ??= [];
|
|
1491
|
-
s.sessions[sid].cache_hits.
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
s.
|
|
1489
|
+
if (!s.sessions[sid].cache_hits.some((h) => h.hash === meta.hash)) {
|
|
1490
|
+
s.sessions[sid].cache_hits.push({
|
|
1491
|
+
at: now,
|
|
1492
|
+
tool,
|
|
1493
|
+
hash: meta.hash,
|
|
1494
|
+
est_savings_usd: roundUsd(delta),
|
|
1495
|
+
});
|
|
1496
|
+
s.sessions[sid].cache_savings_usd = roundUsd(Number(s.sessions[sid].cache_savings_usd || 0) + delta);
|
|
1497
|
+
s.lifetime.cache_savings_usd = roundUsd(Number(s.lifetime.cache_savings_usd || 0) + delta);
|
|
1498
|
+
if (s.sessions[sid].cache_hits.length > 200) {
|
|
1499
|
+
console.error(`[vibeOS] session cache_hits truncated from ${s.sessions[sid].cache_hits.length} to 200 for ${sid}`);
|
|
1500
|
+
s.sessions[sid].cache_hits = s.sessions[sid].cache_hits.slice(-200);
|
|
1501
|
+
}
|
|
1500
1502
|
}
|
|
1501
1503
|
}
|
|
1504
|
+
else {
|
|
1505
|
+
s.sessions[sid].cache_savings_usd = roundUsd(Number(s.sessions[sid].cache_savings_usd || 0) + delta);
|
|
1506
|
+
s.lifetime.cache_savings_usd = roundUsd(Number(s.lifetime.cache_savings_usd || 0) + delta);
|
|
1507
|
+
}
|
|
1502
1508
|
_pruneOldSessions(s);
|
|
1503
1509
|
return s;
|
|
1504
1510
|
});
|
package/src/lib/trinity-tool.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { LABEL_MODES, buildDeterministicTrinity, resolveExecutionIdentity } from "./pricing.js";
|
|
4
|
+
import { BRANDED_MODES, RUNTIME_MODES } from "./mode-router.js";
|
|
4
5
|
import { invalidateApiToken } from "./api-client.js";
|
|
5
6
|
export function createTrinityTool(deps) {
|
|
6
7
|
return {
|
|
@@ -24,7 +25,7 @@ export function createTrinityTool(deps) {
|
|
|
24
25
|
"Call this when the user says things like 'switch to medium', 'use cheap model', 'disable plugin', 'trinity status'.",
|
|
25
26
|
args: {
|
|
26
27
|
action: deps.tool.schema.enum(["status", "enable", "disable", "set", "mode", "thinking", "flow", "tdd", "setup", "project", "patterns", "rebuild", "diagnose", "help", "enforce", "repair-state", "blackbox", "report", "target", "guard", "api-token", "api-bootstrap-token", "todo", "todo-done", "todo-sync"]).optional(),
|
|
27
|
-
slot: deps.tool.schema.enum(["brain", "medium", "cheap", "budget", "quality", "speed", "longrun", "auto", "on", "off", "enforce", "strict", "preview", "apply", "clear", "savings"]).optional(),
|
|
28
|
+
slot: deps.tool.schema.enum(["brain", "medium", "cheap", "budget", "quality", "speed", "longrun", "auto", "vibeultrax", "on", "off", "enforce", "strict", "preview", "apply", "clear", "savings"]).optional(),
|
|
28
29
|
level: deps.tool.schema.enum(["full", "brief", "off", "on"]).optional(),
|
|
29
30
|
token: deps.tool.schema.string().optional(),
|
|
30
31
|
},
|
|
@@ -178,42 +179,33 @@ export function createTrinityTool(deps) {
|
|
|
178
179
|
return `\u2705 Switched to ${slot} slot (${result.ocModel}). Active now (no restart needed).`;
|
|
179
180
|
}
|
|
180
181
|
if (action === "mode") {
|
|
182
|
+
const builtInIds = ["budget", "quality", "speed", "longrun"];
|
|
183
|
+
const brandedIds = BRANDED_MODES.map(m => m.id);
|
|
184
|
+
const allModeIds = [...builtInIds, "auto", ...brandedIds];
|
|
181
185
|
if (!slot)
|
|
182
|
-
return `Provide mode:
|
|
186
|
+
return `Provide mode: ${builtInIds.join(" | ")} | auto | ${brandedIds.join(" | ")}`;
|
|
183
187
|
const modeAlias = { vibemax: "vibemax", vibeqmax: "quality" };
|
|
184
188
|
const resolvedSlot = modeAlias[slot] || slot;
|
|
185
|
-
if (!
|
|
186
|
-
return `Provide mode:
|
|
189
|
+
if (!allModeIds.includes(resolvedSlot)) {
|
|
190
|
+
return `Provide mode: ${builtInIds.join(" | ")} | auto | ${brandedIds.join(" | ")}`;
|
|
187
191
|
}
|
|
188
192
|
const ok = deps.saveOptimizationMode(resolvedSlot);
|
|
189
193
|
if (!ok)
|
|
190
194
|
return `Failed to write mode`;
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
deps.writeSelection("
|
|
197
|
-
deps.writeSelection("
|
|
198
|
-
deps.writeSelection("
|
|
199
|
-
deps.writeSelection("
|
|
200
|
-
deps.writeSelection("
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
deps.writeSelection("flow_enforce", true);
|
|
206
|
-
deps.writeSelection("tdd_enforce", true);
|
|
207
|
-
deps.writeSelection("thinking_level", "full");
|
|
208
|
-
}
|
|
209
|
-
else if (slot === "speed") {
|
|
210
|
-
deps.writeSelection("delegation_enforce", false);
|
|
211
|
-
deps.writeSelection("flow_enabled", false);
|
|
212
|
-
deps.writeSelection("flow_enforce", false);
|
|
213
|
-
deps.writeSelection("tdd_enforce", false);
|
|
214
|
-
deps.writeSelection("thinking_level", "off");
|
|
215
|
-
}
|
|
216
|
-
return `Mode set to ${slot.toUpperCase()}. Tier: ${tierSlot}.`;
|
|
195
|
+
const allEntries = [...BRANDED_MODES, ...RUNTIME_MODES];
|
|
196
|
+
const modeEntry = allEntries.find(e => e.id === slot);
|
|
197
|
+
if (modeEntry) {
|
|
198
|
+
const tierSlot = modeEntry.pipeline[0] || "cheap";
|
|
199
|
+
deps.writeSelection("active_slot", tierSlot);
|
|
200
|
+
deps.writeSelection("onboarding_mode", modeEntry.tdd === "quality" || modeEntry.enforcement === "strict" ? "strict" : "assist");
|
|
201
|
+
deps.writeSelection("delegation_enforce", modeEntry.enforcement === "strict" || modeEntry.enforcement === "on");
|
|
202
|
+
deps.writeSelection("flow_enabled", modeEntry.flow === "strict" || modeEntry.flow === "on" || modeEntry.flow === "audit");
|
|
203
|
+
deps.writeSelection("flow_enforce", modeEntry.flow === "strict" || modeEntry.flow === "on");
|
|
204
|
+
deps.writeSelection("tdd_enforce", modeEntry.tdd === "quality" || modeEntry.tdd === "on" || modeEntry.tdd === "strict");
|
|
205
|
+
deps.writeSelection("thinking_level", modeEntry.thinking);
|
|
206
|
+
return `Mode set to ${slot.toUpperCase()}. Tier: ${tierSlot}.`;
|
|
207
|
+
}
|
|
208
|
+
return `Mode set to ${slot.toUpperCase()}.`;
|
|
217
209
|
}
|
|
218
210
|
if (action === "thinking") {
|
|
219
211
|
if (!level || !["full", "brief", "off"].includes(level)) {
|
|
@@ -1105,6 +1097,7 @@ export function createTrinityTool(deps) {
|
|
|
1105
1097
|
" trinity enable/disable Toggle vibeOS plugin on/off",
|
|
1106
1098
|
" trinity enforce on Block brain-tier writes/edits (save $$)",
|
|
1107
1099
|
" trinity lock on/off Lock model at session start (skip auto-reconcile)",
|
|
1100
|
+
" trinity mode <profile> Set optimization profile (built-in + branded modes)",
|
|
1108
1101
|
" trinity thinking full|brief|off Set reasoning depth",
|
|
1109
1102
|
"",
|
|
1110
1103
|
"GUARDRAILS:",
|
package/src/lib/turn-classify.js
CHANGED
|
@@ -45,18 +45,20 @@ export function bootstrapOptimizationSession() {
|
|
|
45
45
|
const state = loadBlackboxState();
|
|
46
46
|
if (!state.sessions)
|
|
47
47
|
state.sessions = {};
|
|
48
|
-
if (
|
|
49
|
-
state.sessions[sid]
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
if (sid && sid !== "undefined") {
|
|
49
|
+
if (!state.sessions[sid])
|
|
50
|
+
state.sessions[sid] = {};
|
|
51
|
+
state.sessions[sid].optimization_mode = resolvedMode;
|
|
52
|
+
state.sessions[sid].active_slot = resolvedSlot;
|
|
53
|
+
state.sessions[sid].sub_regime = state.sessions[sid].sub_regime || "INIT";
|
|
54
|
+
state.sessions[sid].regime = state.sessions[sid].regime || "INIT";
|
|
55
|
+
state.sessions[sid].resolution = state.sessions[sid].resolution || "unresolved";
|
|
56
|
+
state.sessions[sid].momentum = Number(state.sessions[sid].momentum || 0);
|
|
57
|
+
state.sessions[sid].loop_count = Number(state.sessions[sid].loop_count || 0);
|
|
58
|
+
state.sessions[sid].loop_intervention_level = state.sessions[sid].loop_intervention_level || "none";
|
|
59
|
+
state.sessions[sid].loop_start_turn = Number(state.sessions[sid].loop_start_turn || 0);
|
|
60
|
+
state.sessions[sid].loop_pattern_count = Number(state.sessions[sid].loop_pattern_count || 0);
|
|
61
|
+
}
|
|
60
62
|
saveBlackboxState(state);
|
|
61
63
|
}
|
|
62
64
|
catch { }
|
|
@@ -314,11 +316,11 @@ export function getBlackboxTracker() {
|
|
|
314
316
|
if (state.enabled !== undefined)
|
|
315
317
|
_setGlobalBlackboxEnabled(state.enabled);
|
|
316
318
|
const sid = _OC_SID;
|
|
317
|
-
if (state.sessions?.[sid]?.history) {
|
|
319
|
+
if (sid && sid !== "undefined" && state.sessions?.[sid]?.history) {
|
|
318
320
|
_blackboxTracker = _BlackboxStub.deserialize(state.sessions[sid]);
|
|
319
321
|
}
|
|
320
|
-
else if (currentProjectFingerprint) {
|
|
321
|
-
const projectKeys = Object.keys(state.sessions || {}).filter(k => state.sessions[k].project_fingerprint === currentProjectFingerprint);
|
|
322
|
+
else if (currentProjectFingerprint && sid && sid !== "undefined") {
|
|
323
|
+
const projectKeys = Object.keys(state.sessions || {}).filter(k => state.sessions[k].project_fingerprint === currentProjectFingerprint && k !== "undefined" && k !== null && k.trim() !== "");
|
|
322
324
|
const latest = projectKeys.sort().slice(-1)[0];
|
|
323
325
|
if (latest && state.sessions[latest]?.history) {
|
|
324
326
|
const data = state.sessions[latest];
|
|
@@ -589,12 +591,14 @@ export function incrementTurnCounter() {
|
|
|
589
591
|
const sid = _OC_SID;
|
|
590
592
|
if (!state.sessions)
|
|
591
593
|
state.sessions = {};
|
|
592
|
-
if (
|
|
593
|
-
state.sessions[sid]
|
|
594
|
-
|
|
595
|
-
|
|
594
|
+
if (sid && sid !== "undefined") {
|
|
595
|
+
if (!state.sessions[sid])
|
|
596
|
+
state.sessions[sid] = {};
|
|
597
|
+
const next = (state.sessions[sid].turn_counter || 0) + 1;
|
|
598
|
+
state.sessions[sid].turn_counter = next;
|
|
599
|
+
}
|
|
596
600
|
saveBlackboxState(state);
|
|
597
|
-
return
|
|
601
|
+
return 0;
|
|
598
602
|
}
|
|
599
603
|
catch {
|
|
600
604
|
return 0;
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// @ts-nocheck
|
|
4
4
|
// Blackbox — theWay decision core ported to TypeScript.
|
|
5
5
|
// Barrel export for all blackbox modules.
|
|
6
|
-
export { buildAdvice, buildDecisionBlock, computeModality, humanReadableAction, compressMetrics, compressUncertainty, compressEntropy, enforceClosure, stabilityScore, shouldUseFastPath, buildCautionNote, scoreUsefulness, getFallbackPlan, getActionSuggestion, getCuriosityPrompt
|
|
7
|
-
export { classifySituation, getActions, recommendAction, getSituationTypes
|
|
6
|
+
export { buildAdvice, buildDecisionBlock, computeModality, humanReadableAction, compressMetrics, compressUncertainty, compressEntropy, enforceClosure, stabilityScore, shouldUseFastPath, buildCautionNote, scoreUsefulness, getFallbackPlan, getActionSuggestion, getCuriosityPrompt } from "./advice-layer.js";
|
|
7
|
+
export { classifySituation, getActions, recommendAction, getSituationTypes } from "./taxonomy.js";
|
|
8
8
|
export { ResolutionTracker } from "./resolution-tracker.js";
|
|
9
9
|
export { ExposureModel } from "./exposure-model.js";
|
|
10
|
-
export { ACTION_TARGET, ACTION_TYPE, FALLBACK_PLANS, ACTION_SUGGESTIONS, CURIOSITY_PROMPTS
|
|
11
|
-
export { computeControlVector, buildControlHistoryEntry, REGIME_CONTROL_TABLE
|
|
10
|
+
export { ACTION_TARGET, ACTION_TYPE, FALLBACK_PLANS, ACTION_SUGGESTIONS, CURIOSITY_PROMPTS } from "./crew-constants.js";
|
|
11
|
+
export { computeControlVector, buildControlHistoryEntry, REGIME_CONTROL_TABLE } from "./meta-controller.js";
|
|
12
12
|
export { vibemaxSelectMode, vibemaxPipeline, predictVibeMaX, trainVibeMaXModelFromTelemetry, getVibeMaXModelMeta, resetVibeMaXPipeline } from "./vibemax.js";
|
|
13
13
|
export { PivotCache } from "./pivot-cache.js";
|