vibeostheog 0.23.60 → 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.
- package/CHANGELOG.md +3 -2
- package/README.md +4 -4
- package/dist/assets/dashboard/index.html +579 -0
- package/dist/vibeOS.js +13702 -0
- package/package.json +8 -9
- package/scripts/deploy.mjs +15 -56
- package/src/flow-enforcer.js +0 -1
- package/src/index.js +0 -831
- package/src/lib/api-client.js +0 -743
- package/src/lib/classifiers.js +0 -179
- package/src/lib/constants.js +0 -18
- package/src/lib/cost-anomaly.js +0 -75
- package/src/lib/credit-api.js +0 -176
- package/src/lib/hooks/chat-transform.js +0 -851
- package/src/lib/hooks/footer.js +0 -363
- package/src/lib/hooks/session-compact.js +0 -82
- package/src/lib/hooks/shared-footer.js +0 -105
- package/src/lib/hooks/shell-env.js +0 -21
- package/src/lib/hooks/tool-execute.js +0 -1058
- package/src/lib/index-helpers.js +0 -314
- package/src/lib/mode-policy.js +0 -184
- package/src/lib/mode-router.js +0 -114
- package/src/lib/pattern-helpers.js +0 -126
- package/src/lib/pricing.js +0 -1304
- package/src/lib/reporting.js +0 -233
- package/src/lib/research-audit.js +0 -130
- package/src/lib/runtime-state.js +0 -42
- package/src/lib/runtime-surface.js +0 -188
- package/src/lib/selection-manager.js +0 -179
- package/src/lib/state.js +0 -1895
- package/src/lib/tdd-enforcer.js +0 -315
- package/src/lib/templates.js +0 -92
- package/src/lib/test-skeletons.js +0 -470
- package/src/lib/text-compress.js +0 -94
- package/src/lib/trinity-rebuild.js +0 -467
- package/src/lib/trinity-tool.js +0 -1204
- package/src/lib/turn-classify.js +0 -678
- package/src/lib/vibeos-mcp-server.js +0 -351
- package/src/utils/cost-formatter.js +0 -28
- package/src/utils/math.js +0 -10
- package/src/utils/tdd-helpers.js +0 -267
- package/src/utils/timer.js +0 -72
- package/src/vibeOS-lib/blackbox/advice-layer.js +0 -340
- package/src/vibeOS-lib/blackbox/crew-constants.js +0 -69
- package/src/vibeOS-lib/blackbox/exposure-model.js +0 -43
- package/src/vibeOS-lib/blackbox/index.js +0 -13
- package/src/vibeOS-lib/blackbox/local-stub.js +0 -167
- package/src/vibeOS-lib/blackbox/meta-controller.js +0 -404
- package/src/vibeOS-lib/blackbox/pivot-cache.js +0 -195
- package/src/vibeOS-lib/blackbox/resolution-tracker.js +0 -431
- package/src/vibeOS-lib/blackbox/taxonomy.js +0 -107
- package/src/vibeOS-lib/blackbox/vibemax.js +0 -292
- package/src/vibeOS-lib/flow-enforcer.js +0 -419
- package/src/vibeOS-lib/ml-router.js +0 -276
- package/src/vibeOS-lib/session-metrics.js +0 -138
- package/src/vibeOS-lib/smart-cache.js +0 -226
- /package/{src → dist/assets}/flow-rules.json +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
## 0.23.61
|
|
2
|
+
- chore: clean TS source warnings and bump patch
|
|
3
|
+
|
|
1
4
|
## 0.23.60
|
|
2
5
|
- fix: computeControlVector tier_bias, AUDIT/FORENSIC regex, litex slot (#118)
|
|
3
6
|
- fix: align footer with blackbox session slot
|
|
4
|
-
Merge pull request #117 from DrunkkToys/fix/audit-forensic-ml-routing
|
|
5
|
-
|
|
6
7
|
|
|
7
8
|
## 0.23.59
|
|
8
9
|
- chore: soften footer enforcement copy
|
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Cost-aware control plane for OpenCode Desktop.
|
|
4
4
|
|
|
5
|
-
> ## v0.23.
|
|
5
|
+
> ## v0.23.61 — Return
|
|
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/
|
|
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 `
|
|
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 & 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">👍 positive</button>
|
|
183
|
+
<button class="fb-btn" id="bb-fb-neutral">neutral</button>
|
|
184
|
+
<button class="fb-btn" id="bb-fb-negative">👎 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>
|