vibeostheog 0.23.59 → 0.23.62

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +4 -4
  3. package/dist/assets/dashboard/index.html +579 -0
  4. package/dist/vibeOS.js +13702 -0
  5. package/package.json +8 -9
  6. package/scripts/deploy.mjs +15 -56
  7. package/src/flow-enforcer.js +0 -1
  8. package/src/index.js +0 -814
  9. package/src/lib/api-client.js +0 -743
  10. package/src/lib/classifiers.js +0 -179
  11. package/src/lib/constants.js +0 -18
  12. package/src/lib/cost-anomaly.js +0 -75
  13. package/src/lib/credit-api.js +0 -176
  14. package/src/lib/hooks/chat-transform.js +0 -851
  15. package/src/lib/hooks/footer.js +0 -363
  16. package/src/lib/hooks/session-compact.js +0 -82
  17. package/src/lib/hooks/shared-footer.js +0 -105
  18. package/src/lib/hooks/shell-env.js +0 -21
  19. package/src/lib/hooks/tool-execute.js +0 -1058
  20. package/src/lib/index-helpers.js +0 -314
  21. package/src/lib/mode-policy.js +0 -184
  22. package/src/lib/mode-router.js +0 -114
  23. package/src/lib/pattern-helpers.js +0 -126
  24. package/src/lib/pricing.js +0 -1304
  25. package/src/lib/reporting.js +0 -233
  26. package/src/lib/research-audit.js +0 -130
  27. package/src/lib/runtime-state.js +0 -42
  28. package/src/lib/runtime-surface.js +0 -188
  29. package/src/lib/selection-manager.js +0 -179
  30. package/src/lib/state.js +0 -1895
  31. package/src/lib/tdd-enforcer.js +0 -315
  32. package/src/lib/templates.js +0 -92
  33. package/src/lib/test-skeletons.js +0 -470
  34. package/src/lib/text-compress.js +0 -94
  35. package/src/lib/trinity-rebuild.js +0 -467
  36. package/src/lib/trinity-tool.js +0 -1204
  37. package/src/lib/turn-classify.js +0 -678
  38. package/src/lib/vibeos-mcp-server.js +0 -351
  39. package/src/utils/cost-formatter.js +0 -28
  40. package/src/utils/math.js +0 -10
  41. package/src/utils/tdd-helpers.js +0 -267
  42. package/src/utils/timer.js +0 -72
  43. package/src/vibeOS-lib/blackbox/advice-layer.js +0 -340
  44. package/src/vibeOS-lib/blackbox/crew-constants.js +0 -69
  45. package/src/vibeOS-lib/blackbox/exposure-model.js +0 -43
  46. package/src/vibeOS-lib/blackbox/index.js +0 -13
  47. package/src/vibeOS-lib/blackbox/local-stub.js +0 -167
  48. package/src/vibeOS-lib/blackbox/meta-controller.js +0 -404
  49. package/src/vibeOS-lib/blackbox/pivot-cache.js +0 -195
  50. package/src/vibeOS-lib/blackbox/resolution-tracker.js +0 -431
  51. package/src/vibeOS-lib/blackbox/taxonomy.js +0 -107
  52. package/src/vibeOS-lib/blackbox/vibemax.js +0 -292
  53. package/src/vibeOS-lib/flow-enforcer.js +0 -419
  54. package/src/vibeOS-lib/ml-router.js +0 -276
  55. package/src/vibeOS-lib/session-metrics.js +0 -138
  56. package/src/vibeOS-lib/smart-cache.js +0 -226
  57. /package/{src → dist/assets}/flow-rules.json +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.23.61
2
+ - chore: clean TS source warnings and bump patch
3
+
4
+ ## 0.23.60
5
+ - fix: computeControlVector tier_bias, AUDIT/FORENSIC regex, litex slot (#118)
6
+ - fix: align footer with blackbox session slot
7
+
1
8
  ## 0.23.59
2
9
  - chore: soften footer enforcement copy
3
10
  - chore: align medium icon legend
package/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Cost-aware control plane for OpenCode Desktop.
4
4
 
5
- > ## v0.23.0Disintegration Milestone
5
+ > ## v0.23.61Return
6
6
  > Compact footer format: `🦠 brain | Deepseek | V4 Pro | $12.57 | VibeMaX ⚡ Budget`
7
7
  > VibeMaX is now the default optimization mode. Model display names cleaned up (V4 Pro, Sonnet, 2.5 Flash).
8
8
  > Install: `npx vibeostheog setup --project` or `npx vibeostheog setup`
9
- Keeps expensive models on strategy, routes implementation to cheaper tiers, surfaces savings in real time.
9
+ > Keeps expensive models on strategy, routes implementation to cheaper tiers, surfaces savings in real time.
10
10
 
11
11
  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.
12
12
 
@@ -164,7 +164,7 @@ Local dev checkout:
164
164
 
165
165
  ```json
166
166
  {
167
- "plugin": ["/absolute/path/to/theSaver-oc/src/index.js"]
167
+ "plugin": ["/absolute/path/to/theSaver-oc/dist/vibeOS.js"]
168
168
  }
169
169
  ```
170
170
 
@@ -209,7 +209,7 @@ Tier icon + lowercase quality (🦠 brain / ⚙ medium / ⚡ cheap), provider la
209
209
 
210
210
  ### Plugin Source
211
211
 
212
- 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).
212
+ Single-file runtime `dist/vibeOS.js` (generated from `src/index.ts`). TypeScript source of truth at `src/index.ts`, `src/vibeOS-lib/*.ts`, and `src/utils/*.ts`. Build: `npm run build` (tsc compile + sync-ts-build + bundle + deploy script).
213
213
 
214
214
  ### State Files (~/.claude/)
215
215
 
@@ -0,0 +1,579 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>vibeOS Dashboard</title>
7
+ <style>
8
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
9
+ :root{--bg:#0d1117;--surface:#161b22;--surface2:#21262d;--border:#30363d;--text:#e6edf3;--text2:#8b949e;--accent:#58a6ff;--green:#3fb950;--red:#f85149;--yellow:#d29922;--font:system-ui,-apple-system,sans-serif}
10
+ body{background:var(--bg);color:var(--text);font-family:var(--font);font-size:14px;line-height:1.5;padding:20px;max-width:1200px;margin:0 auto}
11
+ h1{font-size:20px;font-weight:600;display:flex;align-items:center;gap:10px}
12
+ h1 span{font-size:12px;color:var(--text2);font-weight:400}
13
+ header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--border)}
14
+ .header-right{display:flex;gap:10px;align-items:center}
15
+ .indicator{padding:4px 10px;border-radius:12px;font-size:11px;font-weight:600;text-transform:uppercase}
16
+ .indicator.online{background:#1a3a1f;color:var(--green)}
17
+ .indicator.offline{background:#3a1a1f;color:var(--red)}
18
+ .indicator.disabled{background:#3a2a1a;color:var(--yellow)}
19
+ nav{display:flex;gap:4px;margin-bottom:20px;flex-wrap:wrap}
20
+ .tab{padding:8px 16px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text2);cursor:pointer;font-size:13px}
21
+ .tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}
22
+ .tab:hover:not(.active){background:var(--surface2)}
23
+ .panel{display:none}
24
+ .panel.active{display:block}
25
+ .card{background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:16px;margin-bottom:12px}
26
+ .card h3{font-size:13px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px}
27
+ .row{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--surface2)}
28
+ .row:last-child{border:0}
29
+ .label{color:var(--text2)}
30
+ .value{font-weight:600;font-family:ui-monospace,monospace}
31
+ .value.green{color:var(--green)}
32
+ .value.red{color:var(--red)}
33
+ .value.yellow{color:var(--yellow)}
34
+ .grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
35
+ .full{grid-column:1/-1}
36
+ .savings-bar{display:flex;gap:4px;margin-top:8px;height:24px;border-radius:4px;overflow:hidden;font-size:0}
37
+ .savings-bar div{display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;color:#fff;transition:width .3s}
38
+ .savings-bar .delegation{background:var(--accent)}
39
+ .savings-bar .cache{background:var(--green)}
40
+ table{width:100%;border-collapse:collapse;font-size:13px}
41
+ th,td{text-align:left;padding:8px 4px;border-bottom:1px solid var(--surface2)}
42
+ th{color:var(--text2);font-weight:600;font-size:11px;text-transform:uppercase}
43
+ .last-update{color:var(--text2);font-size:11px;margin-top:12px;text-align:right}
44
+ .error{color:var(--red)}
45
+ .loading{opacity:.5}
46
+ button.action{padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--surface2);color:var(--text);cursor:pointer;font-size:12px}
47
+ button.action:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
48
+ select,textarea{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:6px 8px;font-size:13px;font-family:inherit;width:100%;margin-bottom:8px}
49
+ textarea{min-height:80px;resize:vertical}
50
+ .form-row{display:flex;gap:8px;margin-bottom:8px}
51
+ .form-row>*{flex:1}
52
+
53
+ .signal-row, .feature-row { display: flex; justify-content: space-between; padding: 2px 0; }
54
+ .signal-key, .feature-key { color: #8b949e; font-size: 12px; }
55
+ .signal-val, .feature-val { color: #e6edf3; font-family: monospace; font-size: 12px; }
56
+ .fb-btn { margin: 0 4px; padding: 4px 10px; border: 1px solid #30363d; border-radius: 4px; background: #161b22; color: #e6edf3; cursor: pointer; }
57
+ .fb-btn.active { border-color: #58a6ff; background: #1f2937; }
58
+ #bb-momentum-bar { height: 6px; border-radius: 3px; transition: width 0.3s; }
59
+ #bb-stress-gauge { font-size: 24px; letter-spacing: 2px; }
60
+
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <header>
65
+ <h1>vibeOS <span id="version"></span></h1>
66
+ <div class="header-right">
67
+ <span class="indicator offline" id="connIndicator">connecting</span>
68
+ <span class="indicator disabled" id="enableIndicator" style="display:none">OFF</span>
69
+ <button class="action" onclick="refreshAll()">↻ Refresh</button>
70
+ </div>
71
+ </header>
72
+
73
+ <nav id="tabs">
74
+ <button class="tab active" data-tab="status">Status</button>
75
+ <button class="tab" data-tab="savings">Savings</button>
76
+ <button class="tab" data-tab="sessions">Sessions</button>
77
+ <button class="tab" data-tab="reports">Reports</button>
78
+ <button class="tab" data-tab="controls">Controls</button>
79
+ <button class="tab" data-tab="blackbox-panel">Blackbox</button>
80
+ </nav>
81
+
82
+ <div id="panel-status" class="panel active">
83
+ <div class="grid">
84
+ <div class="card"><h3>Model</h3><div id="modelInfo"></div></div>
85
+ <div class="card"><h3>Session</h3><div id="sessionInfo"></div></div>
86
+ <div class="card full"><h3>Backend Connection</h3><div id="backendInfo"></div></div>
87
+ </div>
88
+ </div>
89
+
90
+ <div id="panel-savings" class="panel">
91
+ <div class="grid">
92
+ <div class="card"><h3>Lifetime Savings</h3><div id="lifetimeSavings"></div></div>
93
+ <div class="card"><h3>This Session</h3><div id="sessionSavings"></div></div>
94
+ <div class="card full"><h3>Savings Distribution</h3><div id="savingsBar"></div><div id="savingsBreakdown"></div></div>
95
+ </div>
96
+ </div>
97
+
98
+ <div id="panel-sessions" class="panel">
99
+ <div class="card"><h3>Session History</h3><div id="sessionList"><p class="loading">Loading...</p></div></div>
100
+ </div>
101
+
102
+ <div id="panel-reports" class="panel">
103
+ <div class="card full">
104
+ <h3>Reports <button class="action" style="float:right" onclick="openNewReport()">+ New</button></h3>
105
+ <div class="form-row"><input type="text" id="reportFilter" placeholder="Filter by summary..." oninput="loadReports()" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:6px 8px;font-size:13px;flex:1"></div>
106
+ <div id="reportList"><p class="loading">Loading...</p></div>
107
+ </div>
108
+ </div>
109
+
110
+ <div id="panel-controls" class="panel">
111
+ <div class="card">
112
+ <h3>Trinity Commands</h3>
113
+ <div class="form-row">
114
+ <select id="trinityAction">
115
+ <option value="status">status</option>
116
+ <option value="enable">enable</option>
117
+ <option value="disable">disable</option>
118
+ <option value="rebuild">rebuild</option>
119
+ <option value="diagnose">diagnose</option>
120
+ <option value="flow on">flow on</option>
121
+ <option value="flow off">flow off</option>
122
+ <option value="tdd on">tdd on</option>
123
+ <option value="tdd off">tdd off</option>
124
+ </select>
125
+ <button class="action" onclick="runTrinity()">Execute</button>
126
+ </div>
127
+ <pre id="trinityOutput" style="background:var(--surface2);padding:12px;border-radius:4px;margin-top:8px;font-size:12px;white-space:pre-wrap;color:var(--text2);max-height:300px;overflow:auto"></pre>
128
+ </div>
129
+ <div class="card full">
130
+ <h3>Diagnose</h3>
131
+ <pre id="diagnoseOutput" style="background:var(--surface2);padding:12px;border-radius:4px;font-size:12px;white-space:pre-wrap;color:var(--text2);min-height:60px"></pre>
132
+ </div>
133
+ <div class="card full">
134
+ <h3>Runtime Surface</h3>
135
+ <pre id="projectOutput" style="background:var(--surface2);padding:12px;border-radius:4px;font-size:12px;white-space:pre-wrap;color:var(--text2);min-height:60px"></pre>
136
+ </div>
137
+ </div>
138
+ <div id="blackbox-panel" class="panel content">
139
+ <div class="grid">
140
+ <div class="card">
141
+ <h3>Regime &amp; Resolution</h3>
142
+ <div class="row"><span class="label">Sub-Regime</span><span class="value"><span id="bb-regime-badge" style="padding:2px 8px;border-radius:10px;font-size:11px;color:#fff">INIT</span></span></div>
143
+ <div class="row"><span class="label">Resolution</span><span class="value" id="bb-resolution">INIT</span></div>
144
+ <div class="row"><span class="label">Continuity</span><span class="value" id="bb-continuity">-</span></div>
145
+ <div style="margin-top:8px"><span class="label">Momentum</span>
146
+ <div style="background:var(--surface2);height:6px;border-radius:3px;margin-top:4px;position:relative">
147
+ <div id="bb-momentum-bar" style="width:0%;height:6px;border-radius:3px;background:#198754;transition:width 0.3s;margin-left:50%"></div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ <div class="card">
152
+ <h3>Loop Detection</h3>
153
+ <div class="row"><span class="label">Status</span><span class="value" id="bb-loop-active">inactive</span></div>
154
+ <div class="row"><span class="label">Intervention</span><span class="value" id="bb-loop-level">Level 0</span></div>
155
+ <div class="row"><span class="label">Consecutive</span><span class="value" id="bb-loop-count">consecutive: 0</span></div>
156
+ <div class="row"><span class="label">Message</span><span class="value" id="bb-loop-msg" style="font-size:12px">-</span></div>
157
+ </div>
158
+ <div class="card">
159
+ <h3>Pivot Detection</h3>
160
+ <div class="row"><span class="label">Status</span><span class="value" id="bb-pivot-detected">none</span></div>
161
+ <div class="row"><span class="label">Message</span><span class="value" id="bb-pivot-msg" style="font-size:12px">-</span></div>
162
+ </div>
163
+ <div class="card">
164
+ <h3>Stress Gauge</h3>
165
+ <div style="text-align:center">
166
+ <div id="bb-stress-gauge" style="font-size:24px;letter-spacing:2px"></div>
167
+ <div class="value" id="bb-stress-level" style="font-size:18px">0</div>
168
+ </div>
169
+ </div>
170
+ <div class="card full">
171
+ <h3>Signals</h3>
172
+ <div id="bb-signals"><p class="loading">No data</p></div>
173
+ </div>
174
+ <div class="card full">
175
+ <h3>Features</h3>
176
+ <div id="bb-features"><p class="loading">No data</p></div>
177
+ </div>
178
+ <div class="card full">
179
+ <h3>Feedback</h3>
180
+ <div class="form-row">
181
+ <span class="label" style="line-height:2">Satisfaction:</span>
182
+ <button class="fb-btn" id="bb-fb-positive">&#x1f44d; positive</button>
183
+ <button class="fb-btn" id="bb-fb-neutral">neutral</button>
184
+ <button class="fb-btn" id="bb-fb-negative">&#x1f44e; negative</button>
185
+ </div>
186
+ <div class="form-row">
187
+ <span class="label">Stress Level:</span>
188
+ <select id="bb-fb-stress">
189
+ <option value="0">-</option>
190
+ <option value="1">1</option>
191
+ <option value="2">2</option>
192
+ <option value="3">3</option>
193
+ <option value="4">4</option>
194
+ <option value="5">5</option>
195
+ </select>
196
+ </div>
197
+ <div class="form-row">
198
+ <label style="display:flex;align-items:center;gap:4px">
199
+ <input type="checkbox" id="bb-fb-loop"> Stuck in a loop?
200
+ </label>
201
+ </div>
202
+ <textarea id="bb-fb-notes" placeholder="Notes..."></textarea>
203
+ <button class="action" id="bb-fb-submit" onclick="sendBlackboxFeedback()">Send Feedback</button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ <div class="last-update" id="lastUpdate"></div>
209
+
210
+ <script>
211
+ const BASE = '';
212
+ let lastStatus = null;
213
+ let lastSavings = null;
214
+
215
+ // Tab switching
216
+ document.querySelectorAll('[data-tab]').forEach(btn => {
217
+ btn.addEventListener('click', () => {
218
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
219
+ btn.classList.add('active');
220
+ document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
221
+ document.getElementById('panel-' + btn.dataset.tab).classList.add('active');
222
+ const tab = btn.dataset.tab;
223
+ if (tab === 'reports') loadReports();
224
+ if (tab === 'controls') { loadDiagnose(); loadProject(); }
225
+ if (tab === 'sessions') loadSessions();
226
+ if (tab === 'blackbox-panel') fetchBlackbox();
227
+ });
228
+ });
229
+
230
+ async function api(method, path, body) {
231
+ const opts = { method, headers: {} };
232
+ if (body) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body); }
233
+ const res = await fetch(BASE + path, opts);
234
+ const text = await res.text();
235
+ try { return JSON.parse(text); } catch { return text; }
236
+ }
237
+
238
+ function fmt(n, d = 6) { return typeof n === 'number' ? '$' + n.toFixed(d) : String(n ?? '-'); }
239
+
240
+ async function refreshAll() {
241
+ setIndicator('offline', 'connecting...');
242
+ try {
243
+ const [status, savings] = await Promise.all([
244
+ api('GET', '/status'),
245
+ api('GET', '/savings')
246
+ ]);
247
+ lastStatus = status; lastSavings = savings;
248
+ renderStatus(status);
249
+ renderSavings(savings);
250
+ renderModelInfo(status);
251
+ setConnection(status);
252
+ document.getElementById('lastUpdate').textContent = 'Last updated: ' + new Date().toLocaleTimeString();
253
+ } catch (e) {
254
+ setIndicator('offline', 'disconnected');
255
+ console.error(e);
256
+ }
257
+ }
258
+
259
+ function setConnection(s) {
260
+ if (s.enabled === false) {
261
+ setIndicator('disabled', 'OFF');
262
+ document.getElementById('enableIndicator').style.display = 'inline';
263
+ } else {
264
+ document.getElementById('enableIndicator').style.display = 'none';
265
+ setIndicator(s.backend_connected ? 'online' : 'offline', s.backend_connected ? 'online' : 'disconnected');
266
+ }
267
+ if (s.version) document.getElementById('version').textContent = 'v' + s.version;
268
+ }
269
+
270
+ function setIndicator(cls, text) {
271
+ const el = document.getElementById('connIndicator');
272
+ el.className = 'indicator ' + cls;
273
+ el.textContent = text;
274
+ }
275
+
276
+ function renderStatus(s) {
277
+ const rows = [
278
+ ['Active Slot', s.active_slot || '-'],
279
+ ['Model', s.current_model || '-'],
280
+ ['Credit', s.credit_percent != null ? s.credit_percent + '%' : '-'],
281
+ ['Tier', s.current_tier || '-'],
282
+ ['Locked', s.model_locked ? `${s.locked_model} (${s.locked_slot})` : 'No'],
283
+ ['Mode', s.mode || '-'],
284
+ ['Enabled', String(s.enabled ?? '-')],
285
+ ];
286
+ document.getElementById('sessionInfo').innerHTML = rows.map(([l,v]) =>
287
+ `<div class="row"><span class="label">${l}</span><span class="value">${v}</span></div>`
288
+ ).join('');
289
+ }
290
+
291
+ function renderModelInfo(s) {
292
+ const el = document.getElementById('modelInfo');
293
+ el.innerHTML = `
294
+ <div class="row"><span class="label">Active Slot</span><span class="value">${s.active_slot || '-'}</span></div>
295
+ <div class="row"><span class="label">Current Tier</span><span class="value">${s.current_tier || '-'}</span></div>
296
+ <div class="row"><span class="label">Provider</span><span class="value">${s.current_provider || '-'}</span></div>
297
+ <div class="row"><span class="label">Labels</span><span class="value">${Array.isArray(s.label_modes) ? s.label_modes.join(', ') : '-'}</span></div>
298
+ <div class="row"><span class="label">Model Locked</span><span class="value ${s.model_locked ? 'green' : ''}">${s.model_locked ? s.locked_model : 'No'}</span></div>
299
+ <div class="row"><span class="label">Credit</span><span class="value ${(s.credit_percent ?? 100) < 30 ? 'red' : (s.credit_percent ?? 100) < 60 ? 'yellow' : 'green'}">${s.credit_percent ?? '-'}%</span></div>
300
+ <div class="row"><span class="label">Mode</span><span class="value">${s.mode || '-'}</span></div>
301
+ `;
302
+ }
303
+
304
+ function renderBackendInfo(s) {
305
+ const el = document.getElementById('backendInfo');
306
+ el.innerHTML = `
307
+ <div class="row"><span class="label">Connected</span><span class="value ${s.backend_connected ? 'green' : 'red'}">${s.backend_connected ? 'Yes' : 'No'}</span></div>
308
+ <div class="row"><span class="label">Health URL</span><span class="value" style="font-size:12px">${s.backend_health_url || '-'}</span></div>
309
+ `;
310
+ }
311
+
312
+ function renderSavings(sv) {
313
+ const lt = sv.lifetime || {};
314
+ const ss = sv.current_session || {};
315
+ const ltTotal = (lt.delegation_usd || 0) + (lt.cache_usd || 0);
316
+ const ssTotal = (ss.delegation_usd || 0) + (ss.cache_usd || 0);
317
+
318
+ document.getElementById('lifetimeSavings').innerHTML = `
319
+ <div class="row"><span class="label">Delegation</span><span class="value green">${fmt(lt.delegation_usd, 4)}</span></div>
320
+ <div class="row"><span class="label">Cache</span><span class="value green">${fmt(lt.cache_usd, 4)}</span></div>
321
+ <div class="row"><span class="label">Total</span><span class="value green" style="font-size:16px">${fmt(ltTotal, 4)}</span></div>
322
+ `;
323
+ document.getElementById('sessionSavings').innerHTML = `
324
+ <div class="row"><span class="label">Delegation</span><span class="value green">${fmt(ss.delegation_usd, 4)}</span></div>
325
+ <div class="row"><span class="label">Cache</span><span class="value green">${fmt(ss.cache_usd, 4)}</span></div>
326
+ <div class="row"><span class="label">Total</span><span class="value green" style="font-size:16px">${fmt(ssTotal, 4)}</span></div>
327
+ `;
328
+
329
+ if (ltTotal > 0) {
330
+ const dPct = ((lt.delegation_usd || 0) / ltTotal * 100).toFixed(1);
331
+ const cPct = ((lt.cache_usd || 0) / ltTotal * 100).toFixed(1);
332
+ document.getElementById('savingsBar').innerHTML = `
333
+ <div class="savings-bar">
334
+ ${lt.delegation_usd > 0 ? `<div class="delegation" style="width:${dPct}%">${dPct}%</div>` : ''}
335
+ ${lt.cache_usd > 0 ? `<div class="cache" style="width:${cPct}%">${cPct}%</div>` : ''}
336
+ </div>
337
+ `;
338
+ document.getElementById('savingsBreakdown').innerHTML = `
339
+ <div class="row"><span class="label">🟦 Delegation</span><span class="value green">${fmt(lt.delegation_usd, 4)} (${dPct}%)</span></div>
340
+ <div class="row"><span class="label">🟩 Cache</span><span class="value green">${fmt(lt.cache_usd, 4)} (${cPct}%)</span></div>
341
+ `;
342
+ } else {
343
+ document.getElementById('savingsBar').innerHTML = '<p>No savings data yet</p>';
344
+ document.getElementById('savingsBreakdown').innerHTML = '';
345
+ }
346
+ }
347
+
348
+ async function loadSessions() {
349
+ try {
350
+ const data = await api('GET', '/sessions');
351
+ const el = document.getElementById('sessionList');
352
+ if (!data.sessions || data.sessions.length === 0) {
353
+ el.innerHTML = '<p>No sessions</p>'; return;
354
+ }
355
+ el.innerHTML = `<table><thead><tr><th>ID</th><th>Started</th><th>Cost</th><th>Delegation Savings</th><th>Cache Savings</th></tr></thead>
356
+ <tbody>${data.sessions.map(s => `<tr>
357
+ <td style="font-family:monospace;font-size:11px">${(s.id || '').slice(0, 12)}...</td>
358
+ <td>${s.started ? new Date(s.started).toLocaleDateString() : '-'}</td>
359
+ <td>${fmt(s.cost_usd, 4)}</td>
360
+ <td class="value green">${fmt(s.delegation_savings_usd, 4)}</td>
361
+ <td class="value green">${fmt(s.cache_savings_usd, 4)}</td>
362
+ </tr>`).join('')}</tbody></table>
363
+ <p style="margin-top:8px;color:var(--text2);font-size:12px">Total sessions: ${data.total_sessions}</p>`;
364
+ } catch (e) {
365
+ document.getElementById('sessionList').innerHTML = '<p class="error">Failed to load sessions</p>';
366
+ }
367
+ }
368
+
369
+ async function loadReports() {
370
+ try {
371
+ const filter = document.getElementById('reportFilter').value;
372
+ const data = await api('GET', '/reports' + (filter ? '?type=' + encodeURIComponent(filter) : ''));
373
+ const el = document.getElementById('reportList');
374
+ if (!Array.isArray(data) || data.length === 0) {
375
+ el.innerHTML = '<p>No reports</p>'; return;
376
+ }
377
+ el.innerHTML = data.slice(0, 50).map(r => `<div class="row" style="cursor:pointer" onclick="viewReport('${r.id}')">
378
+ <span class="label">${r.summary || '(no summary)'}</span>
379
+ <span class="value" style="font-size:12px">${r.created_at ? new Date(r.created_at).toLocaleDateString() : '-'}</span>
380
+ </div>`).join('');
381
+ } catch (e) {
382
+ document.getElementById('reportList').innerHTML = '<p class="error">Failed to load reports</p>';
383
+ }
384
+ }
385
+
386
+ async function viewReport(id) {
387
+ try {
388
+ const r = await api('GET', '/reports/' + encodeURIComponent(id));
389
+ const el = document.getElementById('reportList');
390
+ el.innerHTML = `
391
+ <div style="margin-bottom:8px"><button class="action" onclick="loadReports()">← Back</button></div>
392
+ <h4 style="margin:8px 0">${r.summary || 'Report'}</h4>
393
+ <pre style="background:var(--surface2);padding:12px;border-radius:4px;font-size:12px;white-space:pre-wrap;max-height:400px;overflow:auto">${JSON.stringify(r, null, 2)}</pre>`;
394
+ } catch { loadReports(); }
395
+ }
396
+
397
+ function openNewReport() {
398
+ const el = document.getElementById('reportList');
399
+ el.innerHTML = `
400
+ <div style="margin-bottom:8px"><button class="action" onclick="loadReports()">← Back</button></div>
401
+ <input type="text" id="newSummary" placeholder="Summary" style="background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:6px 8px;font-size:13px;width:100%;margin-bottom:8px">
402
+ <textarea id="newNarrative" placeholder="Narrative"></textarea>
403
+ <button class="action" onclick="submitReport()">Save Report</button>
404
+ <div id="reportResult" style="margin-top:8px"></div>`;
405
+ }
406
+
407
+ async function submitReport() {
408
+ const summary = document.getElementById('newSummary').value;
409
+ const narrative = document.getElementById('newNarrative').value;
410
+ try {
411
+ const result = await api('POST', '/reports', { summary, narrative, findings: [], metrics: {}, tags: [] });
412
+ document.getElementById('reportResult').innerHTML = result.ok
413
+ ? `<span class="value green">Report saved: ${result.id}</span>`
414
+ : `<span class="value red">Failed: ${result.error}</span>`;
415
+ } catch { document.getElementById('reportResult').innerHTML = '<span class="value red">Error saving report</span>'; }
416
+ }
417
+
418
+ async function runTrinity() {
419
+ const val = document.getElementById('trinityAction').value;
420
+ const [action, slot] = val.includes(' ') ? [val.split(' ')[0], val.split(' ')[1]] : [val, undefined];
421
+ const el = document.getElementById('trinityOutput');
422
+ el.textContent = 'Executing...';
423
+ try {
424
+ const result = await api('POST', '/trinity', { action, slot });
425
+ el.textContent = typeof result === 'object' ? (result.result || JSON.stringify(result, null, 2)) : result;
426
+ refreshAll();
427
+ } catch (e) {
428
+ el.textContent = 'Error: ' + e.message;
429
+ }
430
+ }
431
+
432
+ async function loadDiagnose() {
433
+ try {
434
+ const d = await api('GET', '/diagnose');
435
+ document.getElementById('diagnoseOutput').textContent = JSON.stringify(d, null, 2);
436
+ } catch { document.getElementById('diagnoseOutput').textContent = 'Failed to load'; }
437
+ }
438
+
439
+ async function loadProject() {
440
+ try {
441
+ const p = await api('GET', '/project');
442
+ document.getElementById('projectOutput').textContent = JSON.stringify(p, null, 2);
443
+ } catch { document.getElementById('projectOutput').textContent = 'Failed to load'; }
444
+ }
445
+
446
+ async function loadSessions() {
447
+ try {
448
+ const data = await api('GET', '/sessions');
449
+ const el = document.getElementById('sessionList');
450
+ if (!data.sessions || data.sessions.length === 0) {
451
+ el.innerHTML = '<p>No sessions</p>'; return;
452
+ }
453
+ el.innerHTML = `<table><thead><tr><th>ID</th><th>Started</th><th>Cost</th><th>Delegation Savings</th><th>Cache Savings</th></tr></thead>
454
+ <tbody>${data.sessions.map(s => `<tr>
455
+ <td style="font-family:monospace;font-size:11px">${(s.id || '').slice(0, 12)}...</td>
456
+ <td>${s.started ? new Date(s.started).toLocaleDateString() : '-'}</td>
457
+ <td>${fmt(s.cost_usd, 4)}</td>
458
+ <td class="value green">${fmt(s.delegation_savings_usd, 4)}</td>
459
+ <td class="value green">${fmt(s.cache_savings_usd, 4)}</td>
460
+ </tr>`).join('')}</tbody></table>
461
+ <p style="margin-top:8px;color:var(--text2);font-size:12px">Total sessions: ${data.total_sessions}</p>`;
462
+ } catch (e) {
463
+ document.getElementById('sessionList').innerHTML = '<p class="error">Failed to load sessions</p>';
464
+ }
465
+ }
466
+
467
+ // Initial load
468
+ refreshAll();
469
+ setInterval(refreshAll, 10000);
470
+ setInterval(fetchBlackbox, 15000);
471
+ async function fetchBlackbox() {
472
+ try {
473
+ const resp = await fetch('/blackbox');
474
+ if (!resp.ok) return;
475
+ const data = await resp.json();
476
+ updateBlackboxPanel(data);
477
+ } catch (_) {}
478
+ }
479
+
480
+ function updateBlackboxPanel(bb) {
481
+ if (!bb) return;
482
+ const el = (id) => document.getElementById(id);
483
+
484
+ const r = bb.sub_regime || 'INIT';
485
+ const b = el('bb-regime-badge');
486
+ if (b) {
487
+ b.textContent = r;
488
+ const colors = { INIT: '#6c757d', EXPLORING: '#0d6efd', DIVERGENT: '#0d6efd', REFINING: '#0dcaf0', CONVERGING: '#198754', CLOSED: '#198754', LOOPING: '#dc3545' };
489
+ b.style.background = colors[r] || '#6c757d';
490
+ }
491
+
492
+ const res = el('bb-resolution');
493
+ if (res) res.textContent = bb.resolution || 'INIT';
494
+
495
+ const mom = el('bb-momentum-bar');
496
+ if (mom) {
497
+ const v = Math.max(-1, Math.min(1, bb.momentum ?? 0));
498
+ mom.style.width = Math.abs(v * 100) + '%';
499
+ mom.style.background = v >= 0 ? '#198754' : '#dc3545';
500
+ mom.style.marginLeft = v >= 0 ? '50%' : (50 - Math.abs(v * 50)) + '%';
501
+ }
502
+
503
+ updateById('bb-continuity', bb.continuity_state || '-');
504
+
505
+ const loop = bb.loop || {};
506
+ updateById('bb-loop-active', loop.active ? 'ACTIVE' : 'inactive');
507
+ updateById('bb-loop-level', 'Level ' + (loop.intervention_level || 0));
508
+ updateById('bb-loop-count', 'consecutive: ' + (loop.consecutive_loops || 0));
509
+ updateById('bb-loop-msg', loop.message || '-');
510
+
511
+ const pivot = bb.pivot || {};
512
+ updateById('bb-pivot-detected', pivot.detected ? 'PIVOT DETECTED' : 'none');
513
+ updateById('bb-pivot-msg', pivot.message || '-');
514
+
515
+ const stress = bb.stress_level ?? 0;
516
+ updateById('bb-stress-level', String(stress));
517
+ const gauge = el('bb-stress-gauge');
518
+ if (gauge) {
519
+ const blocks = ['', '\u2581', '\u2582', '\u2583', '\u2585', '\u2586', '\u2588'];
520
+ gauge.textContent = blocks[Math.min(Math.round(stress * 2), 5)] || '';
521
+ }
522
+
523
+ const signals = el('bb-signals');
524
+ if (signals && bb.signals) {
525
+ signals.innerHTML = Object.entries(bb.signals).map(([k, v]) => {
526
+ const val = typeof v === 'number' ? v.toFixed(3) : String(v);
527
+ return '<div class="signal-row"><span class="signal-key">' + k + '</span><span class="signal-val">' + val + '</span></div>';
528
+ }).join('');
529
+ }
530
+
531
+ const features = el('bb-features');
532
+ if (features && bb.features) {
533
+ features.innerHTML = Object.entries(bb.features).filter(([k]) => typeof k === 'string' && !k.startsWith('_')).slice(0, 12).map(([k, v]) => {
534
+ const val = typeof v === 'number' ? v.toFixed(3) : String(v);
535
+ return '<div class="feature-row"><span class="feature-key">' + k + '</span><span class="feature-val">' + val + '</span></div>';
536
+ }).join('');
537
+ }
538
+ }
539
+
540
+ function updateById(id, text) {
541
+ const e = document.getElementById(id);
542
+ if (e) e.textContent = text;
543
+ }
544
+
545
+ async function sendBlackboxFeedback() {
546
+ const el = (id) => document.getElementById(id);
547
+ const satisfaction = el('bb-fb-positive')?.classList.contains('active') ? 'positive'
548
+ : el('bb-fb-negative')?.classList.contains('active') ? 'negative' : 'neutral';
549
+ const stress = parseInt(el('bb-fb-stress')?.value || '0');
550
+ const isLooping = el('bb-fb-loop')?.checked || false;
551
+ const notes = el('bb-fb-notes')?.value || '';
552
+
553
+ const vector = { type: 'dashboard_feedback', satisfaction, stress_level: stress, is_looping: isLooping, notes };
554
+ const outcome = { satisfaction };
555
+
556
+ try {
557
+ await Promise.all([
558
+ fetch('/blackbox/vector', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(vector) }),
559
+ fetch('/blackbox/outcome', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(outcome) }),
560
+ ]);
561
+ if (el('bb-fb-submit')) el('bb-fb-submit').textContent = 'Sent!';
562
+ setTimeout(() => { if (el('bb-fb-submit')) el('bb-fb-submit').textContent = 'Send Feedback'; }, 2000);
563
+ } catch (_) {
564
+ if (el('bb-fb-submit')) el('bb-fb-submit').textContent = 'Error';
565
+ }
566
+ }
567
+
568
+ // Blackbox satisfaction button toggle
569
+ ['positive', 'negative'].forEach(type => {
570
+ const btn = document.getElementById('bb-fb-' + type);
571
+ if (btn) btn.addEventListener('click', () => {
572
+ document.querySelectorAll('.fb-btn').forEach(b => b.classList.remove('active'));
573
+ btn.classList.add('active');
574
+ });
575
+ });
576
+
577
+ </script>
578
+ </body>
579
+ </html>