vibeostheog 0.20.7 → 0.20.9
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 +8 -0
- package/README.md +188 -105
- package/package.json +1 -1
- package/src/lib/constants.js +1 -1
- package/src/lib/hooks/session-compact.js +19 -1
- package/src/lib/hooks/tool-execute.js +45 -8
- package/src/lib/pricing.js +2 -2
- package/src/vibeOS-lib/blackbox/index.js +2 -0
- package/src/vibeOS-lib/blackbox/meta-controller.js +23 -1
- package/src/vibeOS-lib/blackbox/pivot-cache.js +175 -0
- package/src/vibeOS-lib/blackbox/vibemax.js +271 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.20.9
|
|
2
|
+
- feat: hard-block console.log/debug/info in eslint (warn->error)
|
|
3
|
+
- fix: update constants.js OPUS_DISABLE to 1e-10 (dead code, matches TS source)
|
|
4
|
+
- fix: modelCostPerTurn returns FREE_MODEL_TURN_USD for unknown models
|
|
5
|
+
- docs: rename blackbox to VibeBoX and document local fallback
|
|
6
|
+
- docs: dopamine-style README reformat with OPUS->SONNET->HAIKU pricing
|
|
7
|
+
|
|
8
|
+
|
|
1
9
|
## 0.20.7
|
|
2
10
|
- fix: ship compiled OpenCode plugin bundle
|
|
3
11
|
- fix: always show model label in tool.execute.after footer, even with zero savings
|
package/README.md
CHANGED
|
@@ -1,18 +1,87 @@
|
|
|
1
1
|
# vibeOS for OpenCode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Prices validated: May 28, 2026** — verified against OpenRouter `/api/v1/models`
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Cost-aware control plane for OpenCode Desktop. Keeps expensive models on strategy, routes implementation to cheaper tiers, surfaces savings in real time.
|
|
6
6
|
|
|
7
7
|
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.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Delegation Enforcement
|
|
10
|
+
|
|
11
|
+
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
|
+
|
|
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
|
+
### Savings per Delegation
|
|
24
|
+
|
|
25
|
+
| Move | Per Turn | 10x | 100x | 1,000x |
|
|
26
|
+
|------|----------|-----|------|--------|
|
|
27
|
+
| Opus → Haiku | $0.0308 | $0.31 | $3.08 | $30.80 |
|
|
28
|
+
| Opus → Sonnet | $0.0264 | $0.26 | $2.64 | $26.40 |
|
|
29
|
+
| Sonnet → Haiku | $0.0044 | $0.04 | $0.44 | $4.40 |
|
|
30
|
+
|
|
31
|
+
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
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
| Feature | What it does |
|
|
38
|
+
|---------|-------------|
|
|
39
|
+
| **Delegation enforcement** | Blocks write/edit on brain tier. Routes to medium or cheap. |
|
|
40
|
+
| **Live savings footer** | Model, provider, cumulative savings, cache savings, stress gauge, lock/enforcement tags. |
|
|
41
|
+
| **Web dashboard** | SolidJS SPA with SSE real-time push. Model split, savings, session history, trinity controls. |
|
|
42
|
+
| **Trinity runtime** | `trinity set brain\|medium\|cheap`. Switch tiers mid-session. Change optimization mode. |
|
|
43
|
+
| **Flow enforcer** | Pattern-rule checks on write/edit. Extracts TODO/FIXME into an append-only queue. |
|
|
44
|
+
| **TDD enforcer** | Auto-creates test skeletons for changed source. Strict mode: TODO tests fail. |
|
|
45
|
+
| **Pattern learner** | Tracks recurring struggle/routine patterns per project. |
|
|
46
|
+
| **VibeBoX** | 7 sub-regimes, 11 features per turn, 4 loop intervention levels, PIVOT/SWITCH detection. Auto-mode maps regime to optimization mode. |
|
|
47
|
+
| **Stress-aware routing** | Stress gauge in footer. Stress > 1.5 escalates to quality mode. |
|
|
48
|
+
| **Cache savings** | Separate `cache_savings_usd` tracking for scratchpad cache hits. |
|
|
49
|
+
| **Report tools** | `report-save`, `report-list`, `report-read`, `research-audit`. |
|
|
50
|
+
| **MCP server** | Extended tool capabilities + dashboard serving + SSE push endpoint. |
|
|
51
|
+
| **Remote API** | Fastify server at `api.vibetheog.com`. Token auth with seat/license management. |
|
|
52
|
+
| **Session lock** | `trinity lock on\|off` — freezes model at session start. Resets on restart. |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## How It Works
|
|
57
|
+
|
|
58
|
+
8 hooks into OpenCode Desktop:
|
|
59
|
+
|
|
60
|
+
| Hook | Purpose |
|
|
61
|
+
|------|---------|
|
|
62
|
+
| `experimental.text.complete` | Appends footer to assistant responses |
|
|
63
|
+
| `experimental.chat.messages.transform` | Injects delegation protocol content |
|
|
64
|
+
| `experimental.chat.system.transform` | Injects cost optimization, stress inoculation, enforcement directives |
|
|
65
|
+
| `tool.execute.before` | Blocks write/edit on brain tier |
|
|
66
|
+
| `tool.execute.after` | Injects delegation UI notes |
|
|
67
|
+
| `message.updated` | Fallback footer for versions without text.complete |
|
|
68
|
+
| `experimental.session.compacting` | Preserves savings state |
|
|
69
|
+
| `shell.env` | Injects `OPENCODE_MODEL_TIER` and `OPENCODE_MODEL` |
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Local vs Remote
|
|
74
|
+
|
|
75
|
+
### Full Local (no token)
|
|
76
|
+
|
|
77
|
+
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.
|
|
78
|
+
|
|
79
|
+
### Requires Remote API (api-token)
|
|
80
|
+
|
|
81
|
+
Bootstrap token exchange, advanced VibeBoX with full session history, dynamic per-prompt delegation, cross-session calibration, live pricing fetch beyond static map.
|
|
82
|
+
|
|
83
|
+
---
|
|
10
84
|
|
|
11
|
-
- Model routing that matches the job to the right provider and tier
|
|
12
|
-
- Live savings visibility in chat, the footer, and the web dashboard
|
|
13
|
-
- Separate tracking for delegation savings and cache savings
|
|
14
|
-
- Runtime controls for flow, TDD, model locking, and VibeBoX mode
|
|
15
|
-
- A local fallback path if the remote API is unavailable
|
|
16
85
|
|
|
17
86
|
## Local Fallback Mode
|
|
18
87
|
|
|
@@ -30,7 +99,6 @@ Without a token, vibeOS keeps running in local-only mode with bundled algorithms
|
|
|
30
99
|
### Requires Remote API
|
|
31
100
|
|
|
32
101
|
- Bootstrap token exchange (required for initial API setup)
|
|
33
|
-
- Alpha seat issuance is currently uncapped in the admin tooling
|
|
34
102
|
- Advanced VibeBoX decision engine with full session history tracking
|
|
35
103
|
- Dynamic per-prompt delegation decisions (local fallback uses a safe "block all writes on high tier" default)
|
|
36
104
|
- Learned subagent routing patterns across projects (local fallback uses a static exploratory keyword list)
|
|
@@ -43,124 +111,139 @@ When the remote API is unreachable, the plugin degrades gracefully to rule-based
|
|
|
43
111
|
|
|
44
112
|
## Install
|
|
45
113
|
|
|
46
|
-
### OpenCode plugin
|
|
47
|
-
|
|
48
|
-
1. Register the plugin with the bundled setup command:
|
|
49
|
-
|
|
50
114
|
```bash
|
|
51
|
-
npx vibeostheog setup --project
|
|
115
|
+
npx vibeostheog setup --project # per-project
|
|
116
|
+
npx vibeostheog setup # global ~/.config/opencode/
|
|
52
117
|
```
|
|
53
118
|
|
|
54
|
-
|
|
119
|
+
Adds `vibeostheog` to `opencode.json`. Restart OpenCode Desktop.
|
|
55
120
|
|
|
56
|
-
|
|
121
|
+
Local dev checkout:
|
|
57
122
|
|
|
58
123
|
```json
|
|
59
124
|
{
|
|
60
|
-
"plugin": [
|
|
61
|
-
"vibeostheog"
|
|
62
|
-
]
|
|
125
|
+
"plugin": ["/absolute/path/to/theSaver-oc/src/index.js"]
|
|
63
126
|
}
|
|
64
127
|
```
|
|
65
128
|
|
|
66
|
-
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Commands
|
|
132
|
+
|
|
133
|
+
`trinity help` for full reference. Commands register in the TUI sidebar.
|
|
134
|
+
|
|
135
|
+
| Command | Effect |
|
|
136
|
+
|---------|--------|
|
|
137
|
+
| `trinity status` | Tier, enforcement, savings, stress, lock state |
|
|
138
|
+
| `trinity set brain\|medium\|cheap` | Switch active model tier |
|
|
139
|
+
| `trinity brain\|medium\|cheap` | Shorthand tier switch |
|
|
140
|
+
| `trinity enable\|disable` | Toggle plugin on/off |
|
|
141
|
+
| `trinity mode budget\|quality\|speed\|longrun\|auto` | Set optimization mode |
|
|
142
|
+
| `trinity thinking full\|brief\|off` | Reasoning depth |
|
|
143
|
+
| `trinity enforce on\|off` | Toggle enforcement |
|
|
144
|
+
| `trinity lock on\|off` | Freeze model for session |
|
|
145
|
+
| `trinity flow on\|off` | Toggle flow enforcer |
|
|
146
|
+
| `trinity flow enforce on\|off` | Toggle auto-extract TODOs |
|
|
147
|
+
| `trinity tdd on\|off\|strict\|quality` | Test skeleton behavior |
|
|
148
|
+
| `trinity rebuild` | Re-detect models from all providers |
|
|
149
|
+
| `trinity project` | Per-project analytics |
|
|
150
|
+
| `trinity patterns` / `trinity patterns clear` | Pattern inspection |
|
|
151
|
+
| `trinity diagnose` | Health check |
|
|
152
|
+
| `trinity VibeBoX on\|off\|status\|reset` | Decision engine control |
|
|
153
|
+
| `trinity repair-state preview\|apply` | Fix state collisions |
|
|
154
|
+
| `trinity guard` | Refresh AGENTS.md / README.md |
|
|
155
|
+
| `trinity api-token <token\|invalidate>` | Manage remote API token |
|
|
156
|
+
| `trinity api-bootstrap-token <token>` | Bootstrap token exchange |
|
|
157
|
+
|
|
158
|
+
**Report commands**: `report-save`, `report-list`, `report-read`, `research-audit`
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Live Footer
|
|
67
163
|
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
"plugin": [
|
|
71
|
-
"/absolute/path/to/theSaver-oc/src/index.js"
|
|
72
|
-
]
|
|
73
|
-
}
|
|
164
|
+
```
|
|
165
|
+
— Model: claude-sonnet-4-6 | Provider: Anthropic | $4.82 saved | $1.20 cached | ENFORCE | LOCK | Quality | VIBE —
|
|
74
166
|
```
|
|
75
167
|
|
|
76
|
-
|
|
168
|
+
Provider, model, delegation savings, cache savings, stress gauge (block chars), lock/enforcement tags, optimization mode. Persisted in `~/.claude/delegation-state.json`.
|
|
77
169
|
|
|
78
|
-
|
|
170
|
+
---
|
|
79
171
|
|
|
80
|
-
##
|
|
172
|
+
## Architecture
|
|
81
173
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
npm run build
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
174
|
+
### Plugin Source
|
|
175
|
+
|
|
176
|
+
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 + esbuild bundle + deploy script).
|
|
177
|
+
|
|
178
|
+
### State Files (`~/.claude/`)
|
|
179
|
+
|
|
180
|
+
| File | Purpose |
|
|
181
|
+
|------|---------|
|
|
182
|
+
| `delegation-state.json` | Sessions, warns, cache hits, lifetime totals |
|
|
183
|
+
| `model-tiers.json` | brain/medium/cheap model IDs |
|
|
184
|
+
| `project-states.json` | Per-project memory, analytics, report references |
|
|
185
|
+
| `reports/` | Saved report JSON files |
|
|
186
|
+
| `savings-ledger.jsonl` | Append-only savings and credit event log |
|
|
187
|
+
| `global-learning.json` | Cross-project pattern learning, pricing hints |
|
|
188
|
+
| `model-pricing-cache.json` | Cached pricing by model ID |
|
|
189
|
+
| `active-jobs.json` | In-flight delegation records |
|
|
190
|
+
| `VibeBoX-state.json` | Per-project resolution tracker, session outcomes |
|
|
191
|
+
| `.flow-todo-queue.jsonl` | Flow enforcer TODO queue |
|
|
192
|
+
| `.flow-dedup-keys.json` | Deduplication set for flow TODO |
|
|
193
|
+
| `.enforcement-cooldown.jsonl` | Per-tool cooldown for warn coalescing |
|
|
194
|
+
|
|
195
|
+
### VibeBoX Decision Engine
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
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.
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
Regime→mode mapping via `syncControlSettings()`:
|
|
202
|
+
|
|
203
|
+
| Regime | Mode | Enforce | Flow | TDD | Tier | Think |
|
|
204
|
+
|--------|------|---------|------|-----|------|-------|
|
|
205
|
+
| INIT / DIVERGENT / EXPLORING / REFINING | budget | relaxed | audit | lazy | cheap | off |
|
|
206
|
+
| CONVERGING / CLOSED | quality | strict | strict | quality | brain | full |
|
|
207
|
+
| LOOPING | speed | relaxed | audit | lazy | medium | off |
|
|
208
|
+
|
|
209
|
+
Stress > 1.5 escalates any regime to quality.
|
|
210
|
+
|
|
211
|
+
### Remote API Server
|
|
212
|
+
|
|
213
|
+
`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.
|
|
214
|
+
|
|
215
|
+
### Dashboard
|
|
216
|
+
|
|
217
|
+
SolidJS SPA at `src/dashboard/`. Build: `npm run build:dashboard` (vite). Served by MCP server or standalone. SSE `/events` for real-time push (model split, savings, session history, stress, VibeBoX state).
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Environment Variables
|
|
89
222
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- `trinity status` - show current tier, enforcement, savings, stress, and lock state
|
|
99
|
-
- `trinity set brain|medium|cheap` - switch the active tier
|
|
100
|
-
- `trinity brain|medium|cheap` - shorthand tier switch
|
|
101
|
-
- `trinity rebuild` - re-detect available models and repopulate slots
|
|
102
|
-
- `trinity enable` / `trinity disable` - toggle the plugin on or off
|
|
103
|
-
- `trinity mode budget|quality|speed|longrun|auto` - change the optimization mode
|
|
104
|
-
- `trinity thinking full|brief|off` - change reasoning depth
|
|
105
|
-
- `trinity enforce on|off` - control delegation enforcement
|
|
106
|
-
- `trinity lock on|off` - freeze the active model for the session
|
|
107
|
-
- `trinity flow on|off` and `trinity flow enforce on|off` - manage flow checks
|
|
108
|
-
- `trinity tdd on|off`, `trinity tdd strict on|off`, `trinity tdd quality on|off` - manage test skeleton behavior
|
|
109
|
-
- `trinity project` - open project analytics
|
|
110
|
-
- `trinity patterns` / `trinity patterns clear` - inspect or reset learned patterns
|
|
111
|
-
- `trinity diagnose` - run a health check
|
|
112
|
-
- `trinity repair-state preview|apply` - fix state fingerprint collisions
|
|
113
|
-
- `trinity VibeBoX on|off|status|reset` - control the decision engine
|
|
114
|
-
- `trinity guard` - refresh AGENTS.md and README.md checks
|
|
115
|
-
- `trinity api-token <token|invalidate>` - update the remote API token, or invalidate the current alpha token and disable remote API
|
|
116
|
-
- `trinity api-bootstrap-token <token>` - store an alpha bootstrap token and exchange it for a normal API token on alpha builds
|
|
117
|
-
|
|
118
|
-
Additional reporting commands:
|
|
119
|
-
|
|
120
|
-
- `report-save`
|
|
121
|
-
- `report-list`
|
|
122
|
-
- `report-read`
|
|
123
|
-
- `research-audit`
|
|
124
|
-
|
|
125
|
-
## Savings And Footer
|
|
126
|
-
|
|
127
|
-
The footer shows:
|
|
128
|
-
|
|
129
|
-
- the active provider/model in use for the current run
|
|
130
|
-
- cumulative delegation savings
|
|
131
|
-
- cache savings
|
|
132
|
-
- stress level
|
|
133
|
-
- lock and enforcement tags
|
|
134
|
-
|
|
135
|
-
Savings are persisted in `~/.claude/delegation-state.json`.
|
|
136
|
-
|
|
137
|
-
## Configuration
|
|
138
|
-
|
|
139
|
-
| Variable | Default | Purpose |
|
|
140
|
-
|---|---|---|
|
|
141
|
-
| `VIBEOS_API_URL` | `https://api.vibetheog.com` | Remote API server URL |
|
|
142
|
-
| `VIBEOS_API_TOKEN` | unset | vos_8d73804b13bb46711b9a47f036dba7b4d026fd9583d96960e663716e62815a69 |
|
|
143
|
-
| `VIBEOS_API_DISABLED` | `false` | Set to `true` to invalidate the embedded alpha token and keep remote API off until re-enabled |
|
|
144
|
-
| `VIBEOS_API_BOOTSTRAP_TOKEN` | unset | Alpha bootstrap token for initial auth exchange |
|
|
145
|
-
| `VIBEOS_API_ENABLED` | `true` | Set to `false` for local-only mode |
|
|
223
|
+
| Variable | Default | Effect |
|
|
224
|
+
|----------|---------|--------|
|
|
225
|
+
| `VIBEOS_API_URL` | `https://api.vibetheog.com` | Remote API base URL |
|
|
226
|
+
| `VIBEOS_API_TOKEN` | unset | Remote API auth |
|
|
227
|
+
| `VIBEOS_API_DISABLED` | `false` | Invalidate alpha token |
|
|
228
|
+
| `VIBEOS_API_BOOTSTRAP_TOKEN` | unset | Bootstrap exchange |
|
|
229
|
+
| `VIBEOS_API_ENABLED` | `true` | Set `false` for local-only |
|
|
146
230
|
| `CLAUDE_CREDIT_PERCENT` | `100` | Credit override |
|
|
147
|
-
| `CLAUDE_CONTEXT7_AVAILABLE` | unset |
|
|
148
|
-
| `CLAUDE_SCRATCHPAD_MAX_AGE_SEC` | `86400` | Scratchpad cache lifetime |
|
|
231
|
+
| `CLAUDE_CONTEXT7_AVAILABLE` | unset | Context7 optimization |
|
|
149
232
|
| `VIBEOS_MCP_PORT` | `3001` | MCP server port |
|
|
150
233
|
|
|
151
|
-
|
|
234
|
+
---
|
|
152
235
|
|
|
153
236
|
## Troubleshooting
|
|
154
237
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
238
|
+
| Symptom | Fix |
|
|
239
|
+
|---------|-----|
|
|
240
|
+
| Plugin not loading | Check `opencode.json` entry. Restart Desktop. |
|
|
241
|
+
| Model won't switch | `trinity rebuild` then `trinity set brain\|medium\|cheap` |
|
|
242
|
+
| Writes/edits blocked | Enforcement active — delegate to cheap tier |
|
|
243
|
+
| No footer visible | Verify plugin enabled, completions running |
|
|
244
|
+
| Dashboard blank | `npm run build` then restart |
|
|
245
|
+
| State looks wrong | `trinity diagnose` then `trinity repair-state preview` |
|
|
162
246
|
|
|
163
|
-
|
|
247
|
+
---
|
|
164
248
|
|
|
165
|
-
|
|
166
|
-
- The README stays intentionally high level so the command details can follow the code without a rewrite.
|
|
249
|
+
*`trinity help` is the canonical command reference. This README stays high-level so command details follow the code without a rewrite.*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeostheog",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.9",
|
|
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",
|
package/src/lib/constants.js
CHANGED
|
@@ -6,7 +6,7 @@ export const SAVE_EST = {
|
|
|
6
6
|
SOFT_QUOTA: 0.0001,
|
|
7
7
|
// DeepSeek cache: (0.14 - 0.0028)/1M * ~1000 tokens = 0.00014
|
|
8
8
|
CONTEXT7: 0.00014,
|
|
9
|
-
OPUS_DISABLE:
|
|
9
|
+
OPUS_DISABLE: 1e-10,
|
|
10
10
|
};
|
|
11
11
|
export const WARN_ON_DIRECT = new Set(["write", "edit", "notebookedit"]);
|
|
12
12
|
export const SOFT_QUOTA = new Set(["bash", "glob", "grep", "read", "webfetch", "websearch"]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
-
import { loadSelection, getSessionScratchpadDir, getSessionIndexPath } from "../state.js";
|
|
3
|
+
import { loadSelection, _OC_SID, updateState, getSessionScratchpadDir, getSessionIndexPath } from "../state.js";
|
|
4
4
|
import { getTurnCounter } from "../turn-classify.js";
|
|
5
5
|
export const onSessionCompacting = async (_input, output) => {
|
|
6
6
|
if (!loadSelection().enabled)
|
|
@@ -57,6 +57,24 @@ export const onSessionCompacting = async (_input, output) => {
|
|
|
57
57
|
else if (output) {
|
|
58
58
|
output.context = contextEntries;
|
|
59
59
|
}
|
|
60
|
+
// Persist last_compacted_at so telemetry reflects actual compaction
|
|
61
|
+
if (needsCompact && output) {
|
|
62
|
+
try {
|
|
63
|
+
updateState((state) => {
|
|
64
|
+
const now = new Date().toISOString();
|
|
65
|
+
state.sessions ??= {};
|
|
66
|
+
const sid = _OC_SID;
|
|
67
|
+
state.sessions[sid] ??= {};
|
|
68
|
+
state.sessions[sid].telemetry ??= {};
|
|
69
|
+
state.sessions[sid].telemetry.last_compacted_at = now;
|
|
70
|
+
state.lifetime ??= {};
|
|
71
|
+
state.lifetime.telemetry ??= {};
|
|
72
|
+
state.lifetime.telemetry.last_compacted_at = now;
|
|
73
|
+
return state;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch { }
|
|
77
|
+
}
|
|
60
78
|
}
|
|
61
79
|
catch (err) {
|
|
62
80
|
console.error(`[vibeOS] session.compacting failed: ${err.message}`);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { join, dirname, basename } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { currentTier, currentModel, setCurrentModel, setCurrentTier, _OC_SID, _modelLocked, loadSelection, readLifetimeSavings, recordCacheSaving, recordMissedContext7, getScratchpadHit, recordScratchpadObservation, recordPrivacyTelemetry, updateState, getSessionScratchpadDir, ensureSessionScratchpadDirs, SAVINGS_LEDGER_FILE, CONTEXT7_INSTALL_FLAG, SOFT_QUOTA_LIMIT, upsertTodo, ML_ENABLED, _mlGraph, _cacheDb, _mlSavePending, ML_CONFIDENCE_THRESHOLD, setMlSavePending, saveMLState, SCRATCHPAD_TOOLS, SCRATCHPAD_GLOBAL_DIR, TOOL_NAME_NORMALIZE, stableJson, applyDecadence, } from "../state.js";
|
|
5
6
|
import { classify, modelCostPerTurn, isModelFree, detectContext7, isDocsTarget, shortModelName, formatUsd, _refreshModel, readConfig, resolveDisplayModelId, TRINITY_CHEAP, TRINITY_MEDIUM, trendDisplay, modelToSlotLabel, resolveExecutionIdentity, formatProviderName, formatQualityName, } from "../pricing.js";
|
|
6
7
|
import { latestUserIntent } from "./chat-transform.js";
|
|
7
8
|
import { scoreStress, extractFirstWordFromArgs, shouldLogWarn, isUserAskingForTests, resolveEnforcementMode, getLearnedExploratoryWords, noteTaskRoutingLearning, } from "../turn-classify.js";
|
|
@@ -272,8 +273,46 @@ export const onToolExecuteBefore = async (input, output) => {
|
|
|
272
273
|
recordCacheStats(_cacheDb, t, !!hit, hit ? _cacheSave : 0);
|
|
273
274
|
if (!hit) {
|
|
274
275
|
const prediction = predictCacheHit(_cacheDb, t, promptText);
|
|
275
|
-
if (prediction.shouldWarm && prediction.confidence >= 0.6 &&
|
|
276
|
-
|
|
276
|
+
if (prediction.shouldWarm && prediction.confidence >= 0.6 && prediction.similarEntries.length > 0) {
|
|
277
|
+
try {
|
|
278
|
+
const titleCase = TOOL_NAME_NORMALIZE[t];
|
|
279
|
+
if (titleCase) {
|
|
280
|
+
const argsJson = stableJson(args ?? inArgs ?? {});
|
|
281
|
+
const curHash = createHash("sha256").update(`${titleCase}\n${argsJson}\n`).digest("hex").slice(0, 16);
|
|
282
|
+
const sessionDir = getSessionScratchpadDir();
|
|
283
|
+
const globalDir = SCRATCHPAD_GLOBAL_DIR;
|
|
284
|
+
const ptrPath = join(sessionDir, `${curHash}.ptr`);
|
|
285
|
+
if (!existsSync(ptrPath)) {
|
|
286
|
+
for (const similar of prediction.similarEntries) {
|
|
287
|
+
const targetHash = similar.entry.hash;
|
|
288
|
+
if (targetHash.length < 16)
|
|
289
|
+
continue;
|
|
290
|
+
const cachedFile = join(sessionDir, `${targetHash}.txt`);
|
|
291
|
+
const globalFile = join(globalDir, `${targetHash}.txt`);
|
|
292
|
+
if (existsSync(cachedFile) || existsSync(globalFile)) {
|
|
293
|
+
ensureSessionScratchpadDirs();
|
|
294
|
+
writeFileSync(ptrPath, JSON.stringify({
|
|
295
|
+
contentHash: targetHash,
|
|
296
|
+
tool: titleCase,
|
|
297
|
+
warmed: true,
|
|
298
|
+
at: new Date().toISOString(),
|
|
299
|
+
confidence: prediction.confidence,
|
|
300
|
+
reason: prediction.reason,
|
|
301
|
+
}));
|
|
302
|
+
if (DEBUG_INTERNALS) {
|
|
303
|
+
console.error(`[vibeOS] 🔮 Smart cache: warmed ${t} → ${targetHash.slice(0, 8)} (conf: ${(prediction.confidence * 100).toFixed(0)}%)`);
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (warmErr) {
|
|
312
|
+
if (DEBUG_INTERNALS) {
|
|
313
|
+
console.error(`[vibeOS] Smart cache warming error: ${warmErr.message}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
277
316
|
}
|
|
278
317
|
}
|
|
279
318
|
}
|
|
@@ -420,12 +459,10 @@ export const onToolExecuteBefore = async (input, output) => {
|
|
|
420
459
|
const _workerModel = TRINITY_CHEAP || TRINITY_MEDIUM || null;
|
|
421
460
|
const _workerCost = _workerModel ? (modelCostPerTurn(_workerModel) ?? 0) : 0;
|
|
422
461
|
// Keep precision high to avoid dropping tiny but real per-event savings to zero.
|
|
423
|
-
const _rawEdit = _brainCost
|
|
424
|
-
? Math.max(0, _brainCost - _workerCost)
|
|
425
|
-
: SAVE_EST.WRITE_EDIT;
|
|
462
|
+
const _rawEdit = Math.max(0, _brainCost - _workerCost);
|
|
426
463
|
const _estEdit = Math.max(_rawEdit, SAVE_EST.WRITE_EDIT * 0.1);
|
|
427
|
-
const _estOpus =
|
|
428
|
-
const _estC7 =
|
|
464
|
+
const _estOpus = Math.max(_brainCost, _estEdit);
|
|
465
|
+
const _estC7 = Math.max(_brainCost, SAVE_EST.CONTEXT7);
|
|
429
466
|
const _tierWord = currentTier === "high" ? "Brain" : currentTier === "mid" ? "Medium" : "Budget";
|
|
430
467
|
const _firstWord = extractFirstWordFromArgs(t, args || inArgs);
|
|
431
468
|
const sel = loadSelection();
|
package/src/lib/pricing.js
CHANGED
|
@@ -521,7 +521,7 @@ export function modelCostPerTurn(model) {
|
|
|
521
521
|
}
|
|
522
522
|
// Log unknown models so we can add entries
|
|
523
523
|
console.error(`[vibeOS] modelCostPerTurn: unknown model '${model}' (normalized: '${key}') — add to MODEL_USD_PER_TURN`);
|
|
524
|
-
return
|
|
524
|
+
return FREE_MODEL_TURN_USD;
|
|
525
525
|
}
|
|
526
526
|
export function isModelFree(model) {
|
|
527
527
|
if (!model || typeof model !== "string")
|
|
@@ -531,7 +531,7 @@ export function isModelFree(model) {
|
|
|
531
531
|
if (FREE_MODELS.has(normalizeModelId(model)))
|
|
532
532
|
return true;
|
|
533
533
|
const cost = modelCostPerTurn(model);
|
|
534
|
-
return cost
|
|
534
|
+
return cost <= FREE_MODEL_TURN_USD;
|
|
535
535
|
}
|
|
536
536
|
// Context7 detection — scan known config files for the string "context7".
|
|
537
537
|
// Cheap (one-time at module load); falsy → docs nudge stays dormant.
|
|
@@ -9,3 +9,5 @@ export { ResolutionTracker } from "./resolution-tracker.js";
|
|
|
9
9
|
export { ExposureModel } from "./exposure-model.js";
|
|
10
10
|
export { ACTION_TARGET, ACTION_TYPE, FALLBACK_PLANS, ACTION_SUGGESTIONS, CURIOSITY_PROMPTS, } from "./crew-constants.js";
|
|
11
11
|
export { computeControlVector, buildControlHistoryEntry, REGIME_CONTROL_TABLE, } from "./meta-controller.js";
|
|
12
|
+
export { vibemaxSelectMode, vibemaxPipeline, predictVibeMaX, trainVibeMaXModelFromTelemetry, getVibeMaXModelMeta, resetVibeMaXPipeline } from "./vibemax.js";
|
|
13
|
+
export { PivotCache } from "./pivot-cache.js";
|
|
@@ -160,6 +160,21 @@ const MODE_DELTAS = {
|
|
|
160
160
|
api_enrichment: true,
|
|
161
161
|
outcome_detection: true,
|
|
162
162
|
},
|
|
163
|
+
vibemax: {
|
|
164
|
+
tier_bias: "medium",
|
|
165
|
+
thinking_mode: "full",
|
|
166
|
+
tdd_mode: "quality",
|
|
167
|
+
tdd_focus: ["skeleton-on-write", "assertion-check", "edge-cases"],
|
|
168
|
+
flow_mode: "strict",
|
|
169
|
+
flow_focus: ["write-edit-check", "no-lgtm", "check-debug-artifacts"],
|
|
170
|
+
enforcement_mode: "strict",
|
|
171
|
+
wbp_verbosity: "normal",
|
|
172
|
+
context7_urgency: "required",
|
|
173
|
+
stress_multiplier: 1.0,
|
|
174
|
+
loop_threshold: 0.6,
|
|
175
|
+
api_enrichment: true,
|
|
176
|
+
outcome_detection: true,
|
|
177
|
+
},
|
|
163
178
|
};
|
|
164
179
|
export function autoSelectMode(subRegime, stressMultiplier) {
|
|
165
180
|
if (subRegime === "CONVERGING" || subRegime === "CLOSED")
|
|
@@ -174,10 +189,15 @@ export function computeControlVector(state, action, optimizationMode) {
|
|
|
174
189
|
const regime = state.sub_regime || "INIT";
|
|
175
190
|
const base = REGIME_CONTROL[regime] || DEFAULT_CONTROL;
|
|
176
191
|
// Determine effective mode
|
|
177
|
-
let effectiveMode = optimizationMode || "
|
|
192
|
+
let effectiveMode = optimizationMode || "vibemax";
|
|
178
193
|
if (effectiveMode === "auto") {
|
|
179
194
|
effectiveMode = autoSelectMode(regime, state.latest_stress_multiplier);
|
|
180
195
|
}
|
|
196
|
+
if (effectiveMode === "vibemax") {
|
|
197
|
+
const baseMode = autoSelectMode(regime, state.latest_stress_multiplier);
|
|
198
|
+
const vibemaxQuality = ["quality", "longrun", "audit"];
|
|
199
|
+
effectiveMode = vibemaxQuality.includes(baseMode) ? "vibemax" : "budget";
|
|
200
|
+
}
|
|
181
201
|
// Apply mode deltas on top of base (only for non-balanced modes)
|
|
182
202
|
const delta = effectiveMode !== "balanced" ? (MODE_DELTAS[effectiveMode] || {}) : {};
|
|
183
203
|
const overridden = {
|
|
@@ -211,6 +231,8 @@ function describeMode(delta) {
|
|
|
211
231
|
return "longrun mode — codebase health";
|
|
212
232
|
if (delta.tier_bias === "medium" && delta.stress_multiplier === 0)
|
|
213
233
|
return "speed mode — max response speed";
|
|
234
|
+
if (delta.tier_bias === "medium" && delta.loop_threshold === 0.6)
|
|
235
|
+
return "vibemax mode — ml-optimized budget: 97% quality at 37% cost";
|
|
214
236
|
return `${delta.tier_bias || "auto"} / ${delta.thinking_mode || "auto"}`;
|
|
215
237
|
}
|
|
216
238
|
function buildDirectives(cv, regime, state, action, optimizationMode) {
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
export class PivotCache {
|
|
5
|
+
store;
|
|
6
|
+
baseDir;
|
|
7
|
+
pivotSequence;
|
|
8
|
+
currentWorkflow;
|
|
9
|
+
lastTokens;
|
|
10
|
+
constructor(baseDir) {
|
|
11
|
+
this.baseDir = baseDir || join(homedir(), ".claude");
|
|
12
|
+
this.pivotSequence = [];
|
|
13
|
+
this.currentWorkflow = null;
|
|
14
|
+
this.lastTokens = new Set();
|
|
15
|
+
this.store = this._load();
|
|
16
|
+
}
|
|
17
|
+
_storePath() {
|
|
18
|
+
return join(this.baseDir, ".vibeos-pivot-cache.json");
|
|
19
|
+
}
|
|
20
|
+
_load() {
|
|
21
|
+
try {
|
|
22
|
+
const p = this._storePath();
|
|
23
|
+
if (existsSync(p)) {
|
|
24
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch { /* ignore */ }
|
|
28
|
+
return { pivots: {}, version: 3 };
|
|
29
|
+
}
|
|
30
|
+
save() {
|
|
31
|
+
try {
|
|
32
|
+
const p = this._storePath();
|
|
33
|
+
const dir = dirname(p);
|
|
34
|
+
if (!existsSync(dir))
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
writeFileSync(p, JSON.stringify(this.store, null, 2), "utf-8");
|
|
37
|
+
}
|
|
38
|
+
catch { /* ignore */ }
|
|
39
|
+
}
|
|
40
|
+
tokenize(text) {
|
|
41
|
+
const tl = text.toLowerCase();
|
|
42
|
+
const tokens = new Set();
|
|
43
|
+
if (/deploy|redeploy|bundle|release|npm/.test(tl))
|
|
44
|
+
tokens.add("deploy");
|
|
45
|
+
if (/(?:\bgit\b|\bcommit\b|\bpush\b|\bmerge\b|\bpr\b|\bpull\b|\brebase\b)/.test(tl))
|
|
46
|
+
tokens.add("git");
|
|
47
|
+
if (/budget|cost|price|pricing/.test(tl))
|
|
48
|
+
tokens.add("pricing");
|
|
49
|
+
if (/debug|fix|bug|error|broken/.test(tl))
|
|
50
|
+
tokens.add("debug");
|
|
51
|
+
if (/context|cache|pivot|compression/.test(tl))
|
|
52
|
+
tokens.add("caching");
|
|
53
|
+
if (/test|experiment|verify|validate/.test(tl))
|
|
54
|
+
tokens.add("test");
|
|
55
|
+
if (/config|token|api|secret|env|auth/.test(tl))
|
|
56
|
+
tokens.add("config");
|
|
57
|
+
if (/create|add|implement|build|write/.test(tl))
|
|
58
|
+
tokens.add("create");
|
|
59
|
+
if (/read|check|see|show|status|list/.test(tl))
|
|
60
|
+
tokens.add("inspect");
|
|
61
|
+
if (/refactor|clean|rename|move|restructure/.test(tl))
|
|
62
|
+
tokens.add("refactor");
|
|
63
|
+
if (tokens.size === 0)
|
|
64
|
+
tokens.add("misc");
|
|
65
|
+
return tokens;
|
|
66
|
+
}
|
|
67
|
+
detectPivot(current, previous, timeGap = 0) {
|
|
68
|
+
const cur = this.tokenize(current);
|
|
69
|
+
const prev = this.tokenize(previous);
|
|
70
|
+
const inter = new Set([...cur].filter(x => prev.has(x)));
|
|
71
|
+
const union = new Set([...cur, ...prev]);
|
|
72
|
+
const sim = union.size === 0 ? 1 : inter.size / union.size;
|
|
73
|
+
const timePenalty = Math.min(0.3, timeGap / 600);
|
|
74
|
+
const adjusted = sim - timePenalty;
|
|
75
|
+
return { isPivot: adjusted < 0.3, similarity: Math.round(adjusted * 1000) / 1000 };
|
|
76
|
+
}
|
|
77
|
+
snapshot(workflowId, context) {
|
|
78
|
+
const entry = {
|
|
79
|
+
id: workflowId,
|
|
80
|
+
captured_at: new Date().toISOString(),
|
|
81
|
+
tokens: context.tokens || [],
|
|
82
|
+
intent: context.intent || "",
|
|
83
|
+
decisions: context.decisions || [],
|
|
84
|
+
files: context.files || [],
|
|
85
|
+
code_snippets: context.code_snippets || [],
|
|
86
|
+
blockers: context.blockers || [],
|
|
87
|
+
access_count: 0,
|
|
88
|
+
useful_sections: ["decisions", "files"],
|
|
89
|
+
skip_sections: [],
|
|
90
|
+
};
|
|
91
|
+
this.store.pivots[workflowId] = entry;
|
|
92
|
+
if (!this.pivotSequence.includes(workflowId)) {
|
|
93
|
+
this.pivotSequence.push(workflowId);
|
|
94
|
+
}
|
|
95
|
+
this.save();
|
|
96
|
+
}
|
|
97
|
+
detectPivotBack(tokens, confidenceThreshold = 0.5) {
|
|
98
|
+
if (this.pivotSequence.length < 2) {
|
|
99
|
+
return { matchedId: null, confidence: 0, reason: "not_enough_pivots" };
|
|
100
|
+
}
|
|
101
|
+
const candidates = [];
|
|
102
|
+
for (let i = 0; i < this.pivotSequence.length; i++) {
|
|
103
|
+
const pid = this.pivotSequence[i];
|
|
104
|
+
if (pid === this.pivotSequence[this.pivotSequence.length - 1])
|
|
105
|
+
continue;
|
|
106
|
+
const entry = this.store.pivots[pid];
|
|
107
|
+
if (!entry)
|
|
108
|
+
continue;
|
|
109
|
+
const cached = new Set(entry.tokens);
|
|
110
|
+
if (cached.size === 0)
|
|
111
|
+
continue;
|
|
112
|
+
const inter = new Set([...tokens].filter(x => cached.has(x)));
|
|
113
|
+
const union = new Set([...tokens, ...cached]);
|
|
114
|
+
const jaccard = union.size === 0 ? 0 : inter.size / union.size;
|
|
115
|
+
const exactBonus = tokens.size === cached.size && [...tokens].every(t => cached.has(t)) ? 0.2 : 0;
|
|
116
|
+
const recency = i / Math.max(this.pivotSequence.length, 1);
|
|
117
|
+
const accessBonus = Math.min(0.1, (entry.access_count || 0) * 0.02);
|
|
118
|
+
const confidence = jaccard + exactBonus + recency * 0.1 + accessBonus;
|
|
119
|
+
candidates.push([pid, confidence, jaccard]);
|
|
120
|
+
}
|
|
121
|
+
if (candidates.length === 0) {
|
|
122
|
+
return { matchedId: null, confidence: 0, reason: "no_candidates" };
|
|
123
|
+
}
|
|
124
|
+
candidates.sort((a, b) => b[1] - a[1]);
|
|
125
|
+
const [bestId, bestConf] = candidates[0];
|
|
126
|
+
if (bestConf < confidenceThreshold) {
|
|
127
|
+
return { matchedId: null, confidence: bestConf, reason: "low_confidence" };
|
|
128
|
+
}
|
|
129
|
+
if (this.store.pivots[bestId]) {
|
|
130
|
+
this.store.pivots[bestId].access_count = (this.store.pivots[bestId].access_count || 0) + 1;
|
|
131
|
+
}
|
|
132
|
+
this.save();
|
|
133
|
+
return { matchedId: bestId, confidence: bestConf, reason: "matched" };
|
|
134
|
+
}
|
|
135
|
+
buildInjection(workflowId, maxSections = 3) {
|
|
136
|
+
const entry = this.store.pivots[workflowId];
|
|
137
|
+
if (!entry)
|
|
138
|
+
return "";
|
|
139
|
+
const parts = [];
|
|
140
|
+
const skip = new Set(entry.skip_sections);
|
|
141
|
+
if (!skip.has("decisions") && entry.decisions.length > 0) {
|
|
142
|
+
parts.push(`[${workflowId}] ${entry.decisions.slice(0, 3).join(" | ")}`);
|
|
143
|
+
}
|
|
144
|
+
if (entry.blockers.length > 0 && !skip.has("blockers")) {
|
|
145
|
+
parts.push(`[blockers] ${entry.blockers.slice(0, 2).join(" | ")}`);
|
|
146
|
+
}
|
|
147
|
+
if (entry.code_snippets.length > 0 && entry.useful_sections.includes("code") && !skip.has("code")) {
|
|
148
|
+
parts.push(`[code] ${entry.code_snippets.slice(0, 2).join(" | ")}`);
|
|
149
|
+
}
|
|
150
|
+
return parts.join("\n");
|
|
151
|
+
}
|
|
152
|
+
learn(workflowId, usedSections, unusedSections) {
|
|
153
|
+
const entry = this.store.pivots[workflowId];
|
|
154
|
+
if (!entry)
|
|
155
|
+
return;
|
|
156
|
+
for (const s of usedSections) {
|
|
157
|
+
if (!entry.useful_sections.includes(s))
|
|
158
|
+
entry.useful_sections.push(s);
|
|
159
|
+
}
|
|
160
|
+
for (const s of unusedSections) {
|
|
161
|
+
if (!entry.skip_sections.includes(s) && (entry.access_count || 0) > 3) {
|
|
162
|
+
entry.skip_sections.push(s);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.save();
|
|
166
|
+
}
|
|
167
|
+
resetSequence() {
|
|
168
|
+
this.pivotSequence = [];
|
|
169
|
+
this.currentWorkflow = null;
|
|
170
|
+
this.lastTokens = new Set();
|
|
171
|
+
}
|
|
172
|
+
getRecentPivots(n = 5) {
|
|
173
|
+
return this.pivotSequence.slice(-n);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { autoSelectMode } from "./meta-controller.js";
|
|
3
|
+
import { PivotCache } from "./pivot-cache.js";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { resolve, dirname } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const MODEL_PATH = process.env.VIBEOS_VIBEMAX_MODEL_PATH || resolve(__dirname, "..", "..", "..", "data", "vibemax-model.json");
|
|
9
|
+
const PRIORITY = { budget: 0, audit: 1, speed: 2, longrun: 3, quality: 4 };
|
|
10
|
+
function fallback(sr, text) {
|
|
11
|
+
if (sr === "LOOPING")
|
|
12
|
+
return "speed";
|
|
13
|
+
const t = String(text || "").toLowerCase();
|
|
14
|
+
if (sr === "INIT" && t.length <= 42 && !/[\.\/\\]/.test(t))
|
|
15
|
+
return "budget";
|
|
16
|
+
return "quality";
|
|
17
|
+
}
|
|
18
|
+
// PRNG
|
|
19
|
+
function rng(seed) {
|
|
20
|
+
let s = seed | 0;
|
|
21
|
+
return () => { s |= 0; s = s + 0x6D2B79F5 | 0; let t = Math.imul(s ^ s >>> 15, 1 | s); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; };
|
|
22
|
+
}
|
|
23
|
+
function gini(samples, label) {
|
|
24
|
+
return 1 - samples.filter(s => s.label === label).length ** 2 / samples.length ** 2;
|
|
25
|
+
}
|
|
26
|
+
function buildTree(samples, classes, depth, maxDepth, minLeaf, rngFn) {
|
|
27
|
+
if (samples.length <= minLeaf || depth >= maxDepth || new Set(samples.map(s => s.label)).size === 1) {
|
|
28
|
+
const counts = Object.fromEntries(classes.map(c => [c, 0]));
|
|
29
|
+
for (const s of samples)
|
|
30
|
+
counts[s.label]++;
|
|
31
|
+
const total = samples.length || 1;
|
|
32
|
+
return { prediction: classes.reduce((a, b) => counts[a] > counts[b] ? a : b), probs: classes.map(c => counts[c] / total) };
|
|
33
|
+
}
|
|
34
|
+
const nFeats = samples[0]?.features?.length || 1;
|
|
35
|
+
const featSample = Math.max(2, Math.min(nFeats, Math.floor(Math.sqrt(nFeats)) + 1));
|
|
36
|
+
const cols = new Set();
|
|
37
|
+
while (cols.size < featSample)
|
|
38
|
+
cols.add(Math.floor(rngFn() * nFeats));
|
|
39
|
+
let bestG = 0, bestC = -1, bestV = 0;
|
|
40
|
+
for (const c of cols) {
|
|
41
|
+
const vals = [...new Set(samples.map(s => s.features[c]))].sort((a, b) => a - b);
|
|
42
|
+
for (const v of vals) {
|
|
43
|
+
const l = samples.filter(s => s.features[c] <= v);
|
|
44
|
+
const r = samples.filter(s => s.features[c] > v);
|
|
45
|
+
if (l.length < minLeaf || r.length < minLeaf)
|
|
46
|
+
continue;
|
|
47
|
+
const gParent = classes.reduce((sum, cl) => sum + gini(samples, cl), 0);
|
|
48
|
+
const gChild = (l.length / samples.length) * classes.reduce((sum, cl) => sum + gini(l, cl), 0) + (r.length / samples.length) * classes.reduce((sum, cl) => sum + gini(r, cl), 0);
|
|
49
|
+
const gain = gParent - gChild;
|
|
50
|
+
if (gain > bestG) {
|
|
51
|
+
bestG = gain;
|
|
52
|
+
bestC = c;
|
|
53
|
+
bestV = v;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (bestC === -1 || bestG <= 0) {
|
|
58
|
+
const counts = Object.fromEntries(classes.map(c => [c, 0]));
|
|
59
|
+
for (const s of samples)
|
|
60
|
+
counts[s.label]++;
|
|
61
|
+
const total = samples.length || 1;
|
|
62
|
+
return { prediction: classes.reduce((a, b) => counts[a] > counts[b] ? a : b), probs: classes.map(c => counts[c] / total) };
|
|
63
|
+
}
|
|
64
|
+
return { column: bestC, value: bestV, left: buildTree(samples.filter(s => s.features[bestC] <= bestV), classes, depth + 1, maxDepth, minLeaf, rngFn), right: buildTree(samples.filter(s => s.features[bestC] > bestV), classes, depth + 1, maxDepth, minLeaf, rngFn) };
|
|
65
|
+
}
|
|
66
|
+
function predictTree(tree, features) {
|
|
67
|
+
if (tree.prediction)
|
|
68
|
+
return tree;
|
|
69
|
+
return features[tree.column] <= tree.value ? predictTree(tree.left, features) : predictTree(tree.right, features);
|
|
70
|
+
}
|
|
71
|
+
const VIBEMAX_CFG = { tier: "medium", thinking: "full", tdd: "quality", flow: "strict", enforcement: "strict", wbp: "normal", c7: "required", kp: [3, 6], tc: 0.3, amode: "plan" };
|
|
72
|
+
const BUDGET_CFG = { tier: "cheap", thinking: "off", tdd: "normal", flow: "audit", enforcement: "relaxed", wbp: "minimal", c7: "skippable", kp: [1, 3], tc: 0.1, amode: "build" };
|
|
73
|
+
const VIBEMAX_MAP = { quality: "optimized", longrun: "optimized", audit: "optimized", speed: "budget", budget: "budget" };
|
|
74
|
+
// PivotCache instance
|
|
75
|
+
let pivotCache = null;
|
|
76
|
+
function getPivotCache() {
|
|
77
|
+
if (!pivotCache)
|
|
78
|
+
pivotCache = new PivotCache();
|
|
79
|
+
return pivotCache;
|
|
80
|
+
}
|
|
81
|
+
let prevMessage = "";
|
|
82
|
+
export function resetVibeMaXPipeline() {
|
|
83
|
+
prevMessage = "";
|
|
84
|
+
if (pivotCache)
|
|
85
|
+
pivotCache.resetSequence();
|
|
86
|
+
}
|
|
87
|
+
export function vibemaxSelectMode(input = {}) {
|
|
88
|
+
const stress = Number(input.stress_multiplier || input.stress || 0);
|
|
89
|
+
const pm = autoSelectMode(input.sub_regime, stress) || fallback(input.sub_regime, input.user_text || input.prompt || "");
|
|
90
|
+
const vm = VIBEMAX_MAP[pm] || "optimized";
|
|
91
|
+
if (vm === "budget") {
|
|
92
|
+
return { mode: "budget", source: "vibemax", source_prediction: pm, confidence: 0, auto_result: null, ...BUDGET_CFG, cost: 0.1 };
|
|
93
|
+
}
|
|
94
|
+
const cfg = loadVibeMaXModel()?.config || { think: "full", wbp: "normal", kp: [3, 6] };
|
|
95
|
+
const text = input.user_text || input.prompt || "";
|
|
96
|
+
// PivotCache: detect if returning to a cached workflow
|
|
97
|
+
const pc = getPivotCache();
|
|
98
|
+
const tokens = pc.tokenize(text);
|
|
99
|
+
const pivotBack = text && tokens.size > 0 ? pc.detectPivotBack(tokens, 0.5) : { matchedId: null, confidence: 0, reason: "no_text" };
|
|
100
|
+
const isPivotBack = pivotBack.matchedId !== null;
|
|
101
|
+
const think = isPivotBack ? "brief" : (cfg.think || "full");
|
|
102
|
+
const injection = isPivotBack ? pc.buildInjection(pivotBack.matchedId) : "";
|
|
103
|
+
return {
|
|
104
|
+
mode: "vibemax", source: "vibemax", source_prediction: pm, confidence: 0,
|
|
105
|
+
auto_result: null, tier: "medium", thinking: think, tdd: "quality", flow: "strict",
|
|
106
|
+
enforcement: "strict", wbp: cfg.wbp || "normal", c7: "required", kp: cfg.kp || [3, 6],
|
|
107
|
+
tc: 0.3, amode: "plan", cost: 0.3,
|
|
108
|
+
pivot: isPivotBack ? { matchedId: pivotBack.matchedId, confidence: pivotBack.confidence, injection } : null,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function vibemaxPipeline(input = {}) {
|
|
112
|
+
const text = input.user_text || input.prompt || "";
|
|
113
|
+
const pc = getPivotCache();
|
|
114
|
+
// Detect pivot from previous message
|
|
115
|
+
const isPivot = prevMessage && text ? pc.detectPivot(text, prevMessage) : { isPivot: false, similarity: 1 };
|
|
116
|
+
// If pivot: snapshot previous workflow before switching
|
|
117
|
+
if (isPivot.isPivot && prevMessage) {
|
|
118
|
+
const prevTokens = pc.tokenize(prevMessage);
|
|
119
|
+
const prevId = "wf-" + Date.now();
|
|
120
|
+
pc.snapshot(prevId, {
|
|
121
|
+
tokens: [...prevTokens],
|
|
122
|
+
intent: prevMessage.substring(0, 60),
|
|
123
|
+
decisions: ["previous workflow captured at pivot point"],
|
|
124
|
+
files: [], code_snippets: [], blockers: [],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const result = vibemaxSelectMode(input);
|
|
128
|
+
if (text)
|
|
129
|
+
prevMessage = text;
|
|
130
|
+
return {
|
|
131
|
+
...result,
|
|
132
|
+
pivot_detected: isPivot.isPivot || false,
|
|
133
|
+
pivot_similarity: isPivot.similarity || 1,
|
|
134
|
+
pivot_back: result.pivot?.matchedId || null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function predictVibeMaX(input = {}) {
|
|
138
|
+
const r = vibemaxSelectMode(input);
|
|
139
|
+
return { label: r.mode, confidence: r.confidence, source: "vibemax", source_prediction: r.source_prediction, pivot_back: r.pivot?.matchedId || null };
|
|
140
|
+
}
|
|
141
|
+
function extractVibeMaXFeatures(text, sr) {
|
|
142
|
+
const t = (text || "").toLowerCase();
|
|
143
|
+
const words = t.split(/\s+/).filter(Boolean);
|
|
144
|
+
const f = {
|
|
145
|
+
length: text.length / 5000,
|
|
146
|
+
word_count: words.length / 500,
|
|
147
|
+
sentence_count: (text.split(/[.!?]+/).filter(s => s.trim()).length) / 50,
|
|
148
|
+
question_ratio: (text.match(/\?/g) || []).length / Math.max(text.split(/[.!?]+/).length, 1),
|
|
149
|
+
code_blocks: (text.match(/```/g) || []).length / 10,
|
|
150
|
+
urgency: /urgent|asap|immediately|critical|broken|failing|crash|error|bug/i.test(text) ? 1.0 : 0.0,
|
|
151
|
+
complexity: /complex|difficult|hard|confusing|trick|subtle|nuance/i.test(text) ? 1.0 : 0.0,
|
|
152
|
+
instruction_density: /do not|must|should|always|never|critical/i.test(text) ? 1.0 : /please|could you|maybe|perhaps/i.test(text) ? 0.3 : 0.6,
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
...Object.fromEntries(Object.entries(f).filter(([_, v]) => typeof v === "number")),
|
|
156
|
+
word_count: words.length,
|
|
157
|
+
has_question: t.includes("?") ? 1 : 0,
|
|
158
|
+
has_debug: /debug|fix|broken|error|bug/.test(t) ? 1 : 0,
|
|
159
|
+
has_explain: /explain|what|how|why|compare|review/.test(t) ? 1 : 0,
|
|
160
|
+
has_refactor: /refactor|optimize|clean|improve/.test(t) ? 1 : 0,
|
|
161
|
+
has_short: words.length <= 3 ? 1 : 0,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function extractFeatureVector(text, sr) {
|
|
165
|
+
const feats = extractVibeMaXFeatures(text, sr);
|
|
166
|
+
return Object.values(feats).filter(v => typeof v === "number" && Number.isFinite(v));
|
|
167
|
+
}
|
|
168
|
+
export function trainVibeMaXModelFromTelemetry(telemetryPath) {
|
|
169
|
+
const raw = readFileSync(telemetryPath, "utf-8").trim();
|
|
170
|
+
const entries = raw.split("\n").filter(l => l.trim()).map(l => JSON.parse(l));
|
|
171
|
+
const fbMode = { audit: "optimized", budget: "budget", quality: "optimized", speed: "budget", longrun: "optimized" };
|
|
172
|
+
const classes = ["optimized", "budget"];
|
|
173
|
+
const samples = [];
|
|
174
|
+
for (const e of entries) {
|
|
175
|
+
const t = e.telemetry || {};
|
|
176
|
+
const text = t.input?.user_text || e.text || "";
|
|
177
|
+
const sr = t.signals?.sub_regime || t.input?.sub_regime || "INIT";
|
|
178
|
+
const mode = t.selection?.optimization_mode || t.control_vector?.optimization_mode || t.mode || "";
|
|
179
|
+
if (!text || text.length < 2)
|
|
180
|
+
continue;
|
|
181
|
+
const target = fbMode[mode] || "optimized";
|
|
182
|
+
const features = extractFeatureVector(text, sr);
|
|
183
|
+
if (features.length > 0)
|
|
184
|
+
samples.push({ features, label: target, text, sr, original_mode: mode });
|
|
185
|
+
}
|
|
186
|
+
if (samples.length < 2) {
|
|
187
|
+
const boot = [
|
|
188
|
+
// Technical / coding (16)
|
|
189
|
+
{ text: "hi", sr: "INIT", label: "budget" },
|
|
190
|
+
{ text: "what time is it", sr: "INIT", label: "budget" },
|
|
191
|
+
{ text: "show current status", sr: "INIT", label: "budget" },
|
|
192
|
+
{ text: "just give me quick answer", sr: "INIT", label: "budget" },
|
|
193
|
+
{ text: "review error handling", sr: "EXPLORING", label: "optimized" },
|
|
194
|
+
{ text: "this is broken fix it immediately", sr: "REFINING", label: "optimized" },
|
|
195
|
+
{ text: "help me debug this failing test", sr: "REFINING", label: "optimized" },
|
|
196
|
+
{ text: "we are repeating the same solution", sr: "LOOPING", label: "budget" },
|
|
197
|
+
{ text: "I need complete investigation with reasoning", sr: "RESEARCH", label: "optimized" },
|
|
198
|
+
{ text: "research the right documentation", sr: "RESEARCH", label: "optimized" },
|
|
199
|
+
{ text: "implement new feature with comprehensive tests", sr: "REFINING", label: "optimized" },
|
|
200
|
+
{ text: "lets wrap up and ship final change", sr: "CONVERGING", label: "optimized" },
|
|
201
|
+
{ text: "compare Redis vs Memcached performance", sr: "EXPLORING", label: "optimized" },
|
|
202
|
+
{ text: "search for all TODO comments", sr: "EXPLORING", label: "optimized" },
|
|
203
|
+
{ text: "whats the next step", sr: "INIT", label: "budget" },
|
|
204
|
+
{ text: "why does this keep looping", sr: "LOOPING", label: "budget" },
|
|
205
|
+
// Non-technical (20)
|
|
206
|
+
{ text: "summarize this article", sr: "INIT", label: "budget" },
|
|
207
|
+
{ text: "tell me a joke", sr: "INIT", label: "budget" },
|
|
208
|
+
{ text: "translate hello to spanish", sr: "INIT", label: "budget" },
|
|
209
|
+
{ text: "whats the weather like", sr: "INIT", label: "budget" },
|
|
210
|
+
{ text: "write a quick email", sr: "INIT", label: "budget" },
|
|
211
|
+
{ text: "draft a meeting agenda", sr: "INIT", label: "audit" },
|
|
212
|
+
{ text: "analyze this spreadsheet data and find outliers", sr: "EXPLORING", label: "optimized" },
|
|
213
|
+
{ text: "compare these two products for my purchase decision", sr: "EXPLORING", label: "optimized" },
|
|
214
|
+
{ text: "review this contract for legal issues", sr: "EXPLORING", label: "optimized" },
|
|
215
|
+
{ text: "help me brainstorm marketing ideas", sr: "DIVERGENT", label: "audit" },
|
|
216
|
+
{ text: "edit this essay for grammar and clarity", sr: "REFINING", label: "audit" },
|
|
217
|
+
{ text: "improve the structure of this presentation", sr: "REFINING", label: "optimized" },
|
|
218
|
+
{ text: "proofread this resume and suggest improvements", sr: "REFINING", label: "optimized" },
|
|
219
|
+
{ text: "create a budget spreadsheet for my startup", sr: "REFINING", label: "optimized" },
|
|
220
|
+
{ text: "write a detailed business report on market trends", sr: "RESEARCH", label: "optimized" },
|
|
221
|
+
{ text: "research competitors for my business idea", sr: "RESEARCH", label: "optimized" },
|
|
222
|
+
{ text: "study this financial model and verify projections", sr: "RESEARCH", label: "optimized" },
|
|
223
|
+
{ text: "generate a social media content calendar", sr: "REFINING", label: "optimized" },
|
|
224
|
+
{ text: "we keep going in circles on this decision", sr: "LOOPING", label: "budget" },
|
|
225
|
+
{ text: "finalize the press release", sr: "CONVERGING", label: "optimized" },
|
|
226
|
+
];
|
|
227
|
+
for (const b of boot) {
|
|
228
|
+
const features = extractFeatureVector(b.text, b.sr);
|
|
229
|
+
if (features.length > 0)
|
|
230
|
+
samples.push({ features, label: b.label, text: b.text, sr: b.sr, original_mode: b.label });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const treeCount = 29, maxDepth = 5, minLeaf = 2;
|
|
234
|
+
const rngFn = rng(42);
|
|
235
|
+
const trees = [];
|
|
236
|
+
for (let i = 0; i < treeCount; i++) {
|
|
237
|
+
const bag = [];
|
|
238
|
+
for (let j = 0; j < samples.length; j++)
|
|
239
|
+
bag.push(samples[Math.floor(rngFn() * samples.length)]);
|
|
240
|
+
trees.push(buildTree(bag, classes, 0, maxDepth, minLeaf, rngFn));
|
|
241
|
+
}
|
|
242
|
+
let correct = 0;
|
|
243
|
+
for (const s of samples) {
|
|
244
|
+
const votes = {};
|
|
245
|
+
for (const t of trees) {
|
|
246
|
+
const r = predictTree(t, s.features);
|
|
247
|
+
votes[r.prediction] = (votes[r.prediction] || 0) + 1;
|
|
248
|
+
}
|
|
249
|
+
const pred = Object.entries(votes).sort((a, b) => b[1] - a[1])[0]?.[0] || classes[0];
|
|
250
|
+
if (pred === s.label)
|
|
251
|
+
correct++;
|
|
252
|
+
}
|
|
253
|
+
const model = { trees, classes, trained_at: new Date().toISOString(), samples: samples.length, metrics: { accuracy: correct / Math.max(samples.length, 1), total_samples: samples.length }, config: { think: "full", wbp: "normal", kp: [3, 6] } };
|
|
254
|
+
saveVibeMaXModel(model);
|
|
255
|
+
return model;
|
|
256
|
+
}
|
|
257
|
+
export function loadVibeMaXModel() {
|
|
258
|
+
if (existsSync(MODEL_PATH))
|
|
259
|
+
return JSON.parse(readFileSync(MODEL_PATH, "utf-8"));
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
export function saveVibeMaXModel(model) {
|
|
263
|
+
mkdirSync(dirname(MODEL_PATH), { recursive: true });
|
|
264
|
+
writeFileSync(MODEL_PATH, JSON.stringify(model, null, 2) + "\n", "utf-8");
|
|
265
|
+
}
|
|
266
|
+
export function getVibeMaXModelMeta() {
|
|
267
|
+
const m = loadVibeMaXModel();
|
|
268
|
+
if (!m)
|
|
269
|
+
return { available: false, path: MODEL_PATH, message: "not trained" };
|
|
270
|
+
return { available: true, path: MODEL_PATH, trained_at: m.trained_at, accuracy: m.metrics?.accuracy, samples: m.samples, trees: m.trees?.length, classes: m.classes };
|
|
271
|
+
}
|