@agent-e/server 1.7.1 → 1.7.3
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/dist/AgentEServer-JRUR4XQJ.mjs +7 -0
- package/dist/{chunk-O2UFQVI2.mjs → chunk-AWYCK646.mjs} +930 -410
- package/dist/chunk-AWYCK646.mjs.map +1 -0
- package/dist/cli.js +929 -409
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.js +930 -410
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +3 -3
- package/dist/AgentEServer-PXMTFBHR.mjs +0 -7
- package/dist/chunk-O2UFQVI2.mjs.map +0 -1
- /package/dist/{AgentEServer-PXMTFBHR.mjs.map → AgentEServer-JRUR4XQJ.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -40,96 +40,109 @@ function getDashboardHtml() {
|
|
|
40
40
|
<title>AgentE Dashboard</title>
|
|
41
41
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
42
42
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
43
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
43
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
44
44
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
|
|
45
45
|
<style>
|
|
46
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
47
|
+
|
|
46
48
|
:root {
|
|
47
49
|
--bg-root: #09090b;
|
|
48
50
|
--bg-panel: #18181b;
|
|
49
|
-
--bg-
|
|
51
|
+
--bg-terminal: #09090b;
|
|
50
52
|
--border: #27272a;
|
|
51
|
-
--
|
|
52
|
-
--text-
|
|
53
|
-
--text-
|
|
54
|
-
--text-
|
|
55
|
-
--text-dim: #52525b;
|
|
53
|
+
--text-primary: #ffffff;
|
|
54
|
+
--text-secondary: #52525b;
|
|
55
|
+
--text-tertiary: #a1a1aa;
|
|
56
|
+
--text-value: #d4d4d8;
|
|
56
57
|
--accent: #22c55e;
|
|
57
|
-
--accent-dim: #166534;
|
|
58
58
|
--warning: #eab308;
|
|
59
|
-
--warning-dim: #854d0e;
|
|
60
59
|
--danger: #ef4444;
|
|
61
|
-
--
|
|
62
|
-
--blue: #3b82f6;
|
|
63
|
-
--font-sans: 'IBM Plex Sans', system-ui, sans-serif;
|
|
64
|
-
--font-mono: 'JetBrains Mono', monospace;
|
|
60
|
+
--info: #71717a;
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
html { scroll-behavior: smooth; }
|
|
68
64
|
|
|
69
65
|
body {
|
|
70
66
|
background: var(--bg-root);
|
|
71
67
|
color: var(--text-primary);
|
|
72
|
-
font-family:
|
|
73
|
-
font-
|
|
68
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
69
|
+
-webkit-font-smoothing: antialiased;
|
|
74
70
|
line-height: 1.5;
|
|
75
|
-
|
|
71
|
+
min-height: 100dvh;
|
|
72
|
+
overflow-y: auto;
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
/*
|
|
75
|
+
/* -- Header -- */
|
|
79
76
|
.header {
|
|
80
77
|
position: sticky;
|
|
81
78
|
top: 0;
|
|
82
|
-
z-index:
|
|
83
|
-
background: var(--bg-
|
|
79
|
+
z-index: 50;
|
|
80
|
+
background: var(--bg-panel);
|
|
84
81
|
border-bottom: 1px solid var(--border);
|
|
85
82
|
padding: 12px 24px;
|
|
86
83
|
display: flex;
|
|
87
84
|
align-items: center;
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
justify-content: space-between;
|
|
86
|
+
gap: 16px;
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
.header-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
.header-left {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 10px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header-logo {
|
|
96
|
+
font-family: 'JetBrains Mono', monospace;
|
|
97
|
+
font-size: 15px;
|
|
98
|
+
font-weight: 500;
|
|
95
99
|
color: var(--text-primary);
|
|
96
|
-
white-space: nowrap;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
.header-
|
|
102
|
+
.header-version {
|
|
103
|
+
font-family: 'JetBrains Mono', monospace;
|
|
104
|
+
font-size: 11px;
|
|
105
|
+
color: var(--text-secondary);
|
|
106
|
+
background: var(--bg-root);
|
|
107
|
+
padding: 2px 8px;
|
|
108
|
+
border-radius: 4px;
|
|
109
|
+
}
|
|
100
110
|
|
|
101
|
-
.
|
|
111
|
+
.header-right {
|
|
102
112
|
display: flex;
|
|
103
|
-
gap: 20px;
|
|
104
|
-
flex-wrap: wrap;
|
|
105
113
|
align-items: center;
|
|
106
|
-
|
|
114
|
+
gap: 20px;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
.kpi {
|
|
117
|
+
.kpi-pill {
|
|
110
118
|
display: flex;
|
|
111
119
|
align-items: center;
|
|
112
120
|
gap: 6px;
|
|
113
|
-
font-size:
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
font-family: 'JetBrains Mono', monospace;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.kpi-pill .label {
|
|
114
126
|
color: var(--text-secondary);
|
|
127
|
+
text-transform: uppercase;
|
|
128
|
+
letter-spacing: 0.05em;
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
.kpi-value {
|
|
118
|
-
|
|
119
|
-
font-
|
|
120
|
-
color: var(--text-primary);
|
|
121
|
-
font-size: 13px;
|
|
131
|
+
.kpi-pill .value {
|
|
132
|
+
color: var(--text-value);
|
|
133
|
+
font-variant-numeric: tabular-nums;
|
|
122
134
|
}
|
|
123
135
|
|
|
124
|
-
.kpi-value.health-good { color: var(--accent); }
|
|
125
|
-
.kpi-value.health-warn { color: var(--warning); }
|
|
126
|
-
.kpi-value.health-bad { color: var(--danger); }
|
|
136
|
+
.kpi-pill .value.health-good { color: var(--accent); }
|
|
137
|
+
.kpi-pill .value.health-warn { color: var(--warning); }
|
|
138
|
+
.kpi-pill .value.health-bad { color: var(--danger); }
|
|
127
139
|
|
|
128
140
|
.live-dot {
|
|
129
|
-
width:
|
|
130
|
-
height:
|
|
141
|
+
width: 6px;
|
|
142
|
+
height: 6px;
|
|
131
143
|
border-radius: 50%;
|
|
132
144
|
background: var(--accent);
|
|
145
|
+
flex-shrink: 0;
|
|
133
146
|
animation: pulse 2s ease-in-out infinite;
|
|
134
147
|
}
|
|
135
148
|
|
|
@@ -143,82 +156,159 @@ function getDashboardHtml() {
|
|
|
143
156
|
50% { opacity: 0.4; }
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
/*
|
|
147
|
-
.
|
|
159
|
+
/* -- Advisor Banner -- */
|
|
160
|
+
.advisor-banner {
|
|
161
|
+
display: none;
|
|
162
|
+
background: rgba(234,179,8,0.08);
|
|
163
|
+
border-bottom: 1px solid rgba(234,179,8,0.2);
|
|
164
|
+
padding: 8px 24px;
|
|
165
|
+
font-family: 'JetBrains Mono', monospace;
|
|
166
|
+
font-size: 11px;
|
|
167
|
+
color: var(--warning);
|
|
168
|
+
text-align: center;
|
|
169
|
+
letter-spacing: 0.03em;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.advisor-mode .advisor-banner { display: block; }
|
|
173
|
+
|
|
174
|
+
/* -- Advisor-specific: pending pill -- */
|
|
175
|
+
.pending-pill {
|
|
176
|
+
display: none;
|
|
177
|
+
background: rgba(234,179,8,0.15);
|
|
178
|
+
border-radius: 4px;
|
|
179
|
+
padding: 2px 8px;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.pending-pill:hover { background: rgba(234,179,8,0.25); }
|
|
184
|
+
|
|
185
|
+
.advisor-mode .pending-pill { display: flex; }
|
|
186
|
+
|
|
187
|
+
/* -- Mode value color switching -- */
|
|
188
|
+
.mode-value-auto { color: var(--accent); }
|
|
189
|
+
.mode-value-advisor { color: var(--warning); }
|
|
190
|
+
|
|
191
|
+
/* -- Layout -- */
|
|
192
|
+
.dashboard {
|
|
148
193
|
max-width: 1440px;
|
|
149
194
|
margin: 0 auto;
|
|
150
|
-
padding:
|
|
195
|
+
padding: 16px;
|
|
151
196
|
display: flex;
|
|
152
197
|
flex-direction: column;
|
|
153
|
-
gap:
|
|
198
|
+
gap: 12px;
|
|
154
199
|
}
|
|
155
200
|
|
|
201
|
+
/* -- Panels -- */
|
|
156
202
|
.panel {
|
|
157
203
|
background: var(--bg-panel);
|
|
158
204
|
border: 1px solid var(--border);
|
|
159
205
|
border-radius: 8px;
|
|
160
|
-
padding: 16px;
|
|
206
|
+
padding: 16px 20px;
|
|
207
|
+
min-width: 0;
|
|
208
|
+
display: flex;
|
|
209
|
+
flex-direction: column;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.panel-header {
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
justify-content: space-between;
|
|
216
|
+
margin-bottom: 12px;
|
|
217
|
+
flex-shrink: 0;
|
|
161
218
|
}
|
|
162
219
|
|
|
163
220
|
.panel-title {
|
|
164
|
-
font-size:
|
|
221
|
+
font-size: 11px;
|
|
165
222
|
font-weight: 600;
|
|
166
|
-
color: var(--text-secondary);
|
|
167
223
|
text-transform: uppercase;
|
|
168
|
-
letter-spacing: 0.
|
|
169
|
-
|
|
224
|
+
letter-spacing: 0.08em;
|
|
225
|
+
color: var(--info);
|
|
170
226
|
}
|
|
171
227
|
|
|
172
|
-
|
|
173
|
-
|
|
228
|
+
.panel-meta {
|
|
229
|
+
font-size: 11px;
|
|
230
|
+
font-family: 'JetBrains Mono', monospace;
|
|
231
|
+
color: var(--text-secondary);
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 8px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.panel-meta .live-label {
|
|
238
|
+
color: var(--accent);
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
gap: 4px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.panel-body {
|
|
245
|
+
flex: 1;
|
|
246
|
+
min-height: 0;
|
|
247
|
+
overflow: hidden;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* -- Health Charts -- */
|
|
251
|
+
.charts-panel .chart-row {
|
|
174
252
|
display: grid;
|
|
175
|
-
grid-template-columns: repeat(
|
|
253
|
+
grid-template-columns: repeat(4, 1fr);
|
|
176
254
|
gap: 16px;
|
|
177
255
|
}
|
|
178
256
|
|
|
179
|
-
.chart
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
257
|
+
.mini-chart { position: relative; }
|
|
258
|
+
.mini-chart-label {
|
|
259
|
+
font-size: 10px;
|
|
260
|
+
font-weight: 600;
|
|
261
|
+
text-transform: uppercase;
|
|
262
|
+
letter-spacing: 0.06em;
|
|
263
|
+
color: var(--text-secondary);
|
|
264
|
+
margin-bottom: 4px;
|
|
184
265
|
}
|
|
266
|
+
.mini-chart canvas { width: 100% !important; height: 64px !important; }
|
|
185
267
|
|
|
186
|
-
|
|
268
|
+
@media (max-width: 900px) {
|
|
269
|
+
.charts-panel .chart-row { grid-template-columns: 1fr 1fr; }
|
|
270
|
+
}
|
|
187
271
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
color: var(--text-muted);
|
|
191
|
-
font-weight: 500;
|
|
192
|
-
margin-bottom: 8px;
|
|
272
|
+
@media (max-width: 500px) {
|
|
273
|
+
.charts-panel .chart-row { grid-template-columns: 1fr; }
|
|
193
274
|
}
|
|
194
275
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
276
|
+
/* -- Terminal Feed -- */
|
|
277
|
+
.terminal-panel {
|
|
278
|
+
border-left: 2px solid var(--accent);
|
|
279
|
+
height: 380px;
|
|
280
|
+
overflow: hidden;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.advisor-mode .terminal-panel {
|
|
284
|
+
border-left-color: var(--warning);
|
|
201
285
|
}
|
|
202
286
|
|
|
203
|
-
/* \u2500\u2500 Terminal (Decision Feed) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
204
287
|
.terminal {
|
|
205
|
-
background: var(--bg-
|
|
206
|
-
border:
|
|
207
|
-
|
|
208
|
-
height:
|
|
288
|
+
background: var(--bg-terminal);
|
|
289
|
+
border-radius: 6px;
|
|
290
|
+
padding: 12px 16px;
|
|
291
|
+
height: 100%;
|
|
209
292
|
overflow-y: auto;
|
|
210
|
-
font-family:
|
|
211
|
-
font-size:
|
|
293
|
+
font-family: 'JetBrains Mono', monospace;
|
|
294
|
+
font-size: 12.5px;
|
|
212
295
|
line-height: 1.7;
|
|
213
|
-
|
|
296
|
+
display: flex;
|
|
297
|
+
flex-direction: column;
|
|
214
298
|
}
|
|
215
299
|
|
|
216
|
-
.terminal
|
|
300
|
+
.terminal-inner {
|
|
301
|
+
margin-top: auto;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.terminal::-webkit-scrollbar { width: 4px; }
|
|
217
305
|
.terminal::-webkit-scrollbar-track { background: transparent; }
|
|
218
|
-
.terminal::-webkit-scrollbar-thumb { background: var(--border
|
|
306
|
+
.terminal::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
219
307
|
|
|
220
308
|
.term-line {
|
|
221
309
|
white-space: nowrap;
|
|
310
|
+
overflow: hidden;
|
|
311
|
+
text-overflow: ellipsis;
|
|
222
312
|
opacity: 0;
|
|
223
313
|
transform: translateY(4px);
|
|
224
314
|
animation: termIn 0.3s ease-out forwards;
|
|
@@ -228,120 +318,197 @@ function getDashboardHtml() {
|
|
|
228
318
|
to { opacity: 1; transform: translateY(0); }
|
|
229
319
|
}
|
|
230
320
|
|
|
231
|
-
.t-tick { color: var(--text-
|
|
321
|
+
.t-tick { color: var(--text-secondary); }
|
|
232
322
|
.t-ok { color: var(--accent); }
|
|
323
|
+
.t-check { color: var(--accent); }
|
|
233
324
|
.t-skip { color: var(--warning); }
|
|
234
325
|
.t-fail { color: var(--danger); }
|
|
235
326
|
.t-principle { color: var(--text-primary); font-weight: 500; }
|
|
236
|
-
.t-param { color: var(--text-
|
|
237
|
-
.t-old { color:
|
|
238
|
-
.t-arrow { color: var(--
|
|
327
|
+
.t-param { color: var(--text-tertiary); }
|
|
328
|
+
.t-old { color: var(--text-value); font-variant-numeric: tabular-nums; }
|
|
329
|
+
.t-arrow { color: var(--info); }
|
|
239
330
|
.t-new { color: var(--accent); font-variant-numeric: tabular-nums; }
|
|
240
|
-
.t-meta { color: var(--text-
|
|
331
|
+
.t-meta { color: var(--text-secondary); }
|
|
332
|
+
.t-violation-id { color: var(--warning); }
|
|
333
|
+
.t-violation-desc { color: var(--text-tertiary); }
|
|
334
|
+
.t-status-label { color: var(--text-tertiary); }
|
|
335
|
+
.t-status-value { color: var(--accent); font-variant-numeric: tabular-nums; }
|
|
336
|
+
.t-dim { color: var(--text-secondary); }
|
|
337
|
+
.t-white { color: var(--text-primary); }
|
|
338
|
+
.t-separator { color: var(--info); }
|
|
339
|
+
.t-pending-icon { color: var(--warning); }
|
|
340
|
+
.t-pending-val { color: var(--warning); font-variant-numeric: tabular-nums; }
|
|
341
|
+
|
|
342
|
+
/* -- Advisor Inline Buttons -- */
|
|
343
|
+
.advisor-btn {
|
|
344
|
+
display: none;
|
|
345
|
+
font-family: 'JetBrains Mono', monospace;
|
|
346
|
+
font-size: 10px;
|
|
347
|
+
border-radius: 3px;
|
|
348
|
+
padding: 2px 8px;
|
|
349
|
+
cursor: pointer;
|
|
350
|
+
border: 1px solid;
|
|
351
|
+
margin-left: 6px;
|
|
352
|
+
vertical-align: middle;
|
|
353
|
+
line-height: 1.4;
|
|
354
|
+
transition: background 0.15s;
|
|
355
|
+
}
|
|
241
356
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
overflow-y: auto;
|
|
357
|
+
.advisor-mode .advisor-btn { display: inline-flex; align-items: center; }
|
|
358
|
+
|
|
359
|
+
.advisor-btn.approve-btn {
|
|
360
|
+
background: rgba(34,197,94,0.15);
|
|
361
|
+
color: var(--accent);
|
|
362
|
+
border-color: rgba(34,197,94,0.3);
|
|
249
363
|
}
|
|
364
|
+
.advisor-btn.approve-btn:hover { background: rgba(34,197,94,0.25); }
|
|
250
365
|
|
|
251
|
-
.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
padding: 12px;
|
|
256
|
-
border-radius: 6px;
|
|
257
|
-
border: 1px solid var(--border);
|
|
258
|
-
background: var(--bg-panel);
|
|
259
|
-
transition: opacity 0.3s, transform 0.3s;
|
|
366
|
+
.advisor-btn.reject-btn {
|
|
367
|
+
background: rgba(239,68,68,0.1);
|
|
368
|
+
color: var(--danger);
|
|
369
|
+
border-color: rgba(239,68,68,0.2);
|
|
260
370
|
}
|
|
371
|
+
.advisor-btn.reject-btn:hover { background: rgba(239,68,68,0.2); }
|
|
261
372
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
373
|
+
/* Approved/Rejected flash labels */
|
|
374
|
+
.action-flash {
|
|
375
|
+
font-family: 'JetBrains Mono', monospace;
|
|
376
|
+
font-size: 10px;
|
|
377
|
+
margin-left: 8px;
|
|
378
|
+
animation: flashIn 0.3s ease-out;
|
|
265
379
|
}
|
|
266
380
|
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
white-space: nowrap;
|
|
381
|
+
.action-flash.approved { color: var(--accent); }
|
|
382
|
+
.action-flash.rejected { color: var(--info); }
|
|
383
|
+
|
|
384
|
+
@keyframes flashIn {
|
|
385
|
+
from { opacity: 0; transform: translateX(-4px); }
|
|
386
|
+
to { opacity: 1; transform: translateX(0); }
|
|
274
387
|
}
|
|
275
388
|
|
|
276
|
-
|
|
277
|
-
.
|
|
278
|
-
.sev-low { background: var(--accent-dim); color: var(--accent); }
|
|
389
|
+
/* Dimmed line after rejection */
|
|
390
|
+
.term-line.rejected-line { opacity: 0.5; }
|
|
279
391
|
|
|
280
|
-
|
|
281
|
-
.alert-
|
|
282
|
-
|
|
392
|
+
/* -- Alerts -- */
|
|
393
|
+
.alert-card {
|
|
394
|
+
background: var(--bg-root);
|
|
395
|
+
border-radius: 6px;
|
|
396
|
+
padding: 10px 14px;
|
|
397
|
+
margin-bottom: 8px;
|
|
398
|
+
border-left: 3px solid transparent;
|
|
399
|
+
overflow: hidden;
|
|
400
|
+
}
|
|
283
401
|
|
|
284
|
-
|
|
285
|
-
.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
402
|
+
.alert-card.sev-high { border-left-color: var(--danger); }
|
|
403
|
+
.alert-card.sev-med { border-left-color: var(--warning); }
|
|
404
|
+
.alert-card.sev-low { border-left-color: var(--accent); }
|
|
405
|
+
|
|
406
|
+
.alert-top {
|
|
407
|
+
display: flex;
|
|
408
|
+
align-items: center;
|
|
409
|
+
gap: 8px;
|
|
410
|
+
margin-bottom: 4px;
|
|
289
411
|
}
|
|
290
412
|
|
|
291
|
-
.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
413
|
+
.sev-badge {
|
|
414
|
+
width: 20px;
|
|
415
|
+
height: 20px;
|
|
416
|
+
border-radius: 50%;
|
|
417
|
+
display: flex;
|
|
418
|
+
align-items: center;
|
|
419
|
+
justify-content: center;
|
|
420
|
+
font-size: 10px;
|
|
421
|
+
font-weight: 700;
|
|
422
|
+
font-family: 'JetBrains Mono', monospace;
|
|
423
|
+
color: var(--bg-root);
|
|
424
|
+
flex-shrink: 0;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.sev-badge.high { background: var(--danger); }
|
|
428
|
+
.sev-badge.med { background: var(--warning); }
|
|
429
|
+
.sev-badge.low { background: var(--accent); }
|
|
430
|
+
|
|
431
|
+
.alert-principle-id { color: var(--warning); font-family: 'JetBrains Mono', monospace; font-size: 11px; }
|
|
432
|
+
.alert-principle-name { color: var(--text-primary); font-size: 12px; font-weight: 500; }
|
|
433
|
+
.alert-evidence { color: var(--text-tertiary); font-size: 10px; margin-top: 2px; }
|
|
434
|
+
.alert-suggestion { color: var(--accent); font-size: 10px; margin-top: 2px; }
|
|
435
|
+
|
|
436
|
+
.alert-approve-btn {
|
|
437
|
+
display: none;
|
|
438
|
+
font-family: 'JetBrains Mono', monospace;
|
|
439
|
+
font-size: 10px;
|
|
440
|
+
background: rgba(34,197,94,0.15);
|
|
441
|
+
color: var(--accent);
|
|
442
|
+
border: 1px solid rgba(34,197,94,0.3);
|
|
443
|
+
border-radius: 3px;
|
|
444
|
+
padding: 3px 10px;
|
|
297
445
|
cursor: pointer;
|
|
298
|
-
|
|
446
|
+
margin-top: 8px;
|
|
447
|
+
transition: background 0.15s;
|
|
299
448
|
}
|
|
300
449
|
|
|
301
|
-
.
|
|
450
|
+
.alert-approve-btn:hover { background: rgba(34,197,94,0.25); }
|
|
302
451
|
|
|
303
|
-
.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
452
|
+
.alert-reject-btn {
|
|
453
|
+
display: none;
|
|
454
|
+
font-family: 'JetBrains Mono', monospace;
|
|
455
|
+
font-size: 10px;
|
|
456
|
+
background: rgba(239,68,68,0.1);
|
|
457
|
+
color: var(--danger);
|
|
458
|
+
border: 1px solid rgba(239,68,68,0.2);
|
|
459
|
+
border-radius: 3px;
|
|
460
|
+
padding: 3px 10px;
|
|
461
|
+
cursor: pointer;
|
|
462
|
+
margin-top: 8px;
|
|
463
|
+
margin-left: 6px;
|
|
464
|
+
transition: background 0.15s;
|
|
309
465
|
}
|
|
310
466
|
|
|
311
|
-
.
|
|
467
|
+
.alert-reject-btn:hover { background: rgba(239,68,68,0.2); }
|
|
312
468
|
|
|
313
|
-
|
|
314
|
-
.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
469
|
+
.advisor-mode .alert-approve-btn { display: inline-block; }
|
|
470
|
+
.advisor-mode .alert-reject-btn { display: inline-block; }
|
|
471
|
+
|
|
472
|
+
/* Alert resolved state */
|
|
473
|
+
.alert-card.resolved {
|
|
474
|
+
opacity: 0;
|
|
475
|
+
max-height: 0;
|
|
476
|
+
padding: 0 14px;
|
|
477
|
+
margin-bottom: 0;
|
|
478
|
+
overflow: hidden;
|
|
479
|
+
transition: opacity 0.4s ease-out, max-height 0.5s ease-out 0.1s, padding 0.5s ease-out 0.1s, margin 0.5s ease-out 0.1s;
|
|
318
480
|
}
|
|
319
481
|
|
|
320
|
-
|
|
321
|
-
|
|
482
|
+
.alerts-scroll {
|
|
483
|
+
overflow-y: auto;
|
|
484
|
+
max-height: 300px;
|
|
322
485
|
}
|
|
323
486
|
|
|
324
|
-
|
|
325
|
-
.
|
|
487
|
+
.alerts-scroll::-webkit-scrollbar { width: 4px; }
|
|
488
|
+
.alerts-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
489
|
+
.alerts-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
326
490
|
|
|
491
|
+
/* -- Persona Bars -- */
|
|
327
492
|
.persona-row {
|
|
328
493
|
display: flex;
|
|
329
494
|
align-items: center;
|
|
330
495
|
gap: 8px;
|
|
331
|
-
|
|
496
|
+
margin-bottom: 5px;
|
|
497
|
+
font-size: 11px;
|
|
498
|
+
font-family: 'JetBrains Mono', monospace;
|
|
332
499
|
}
|
|
333
500
|
|
|
334
501
|
.persona-label {
|
|
335
502
|
width: 100px;
|
|
336
503
|
text-align: right;
|
|
337
|
-
color: var(--text-
|
|
338
|
-
font-size: 11px;
|
|
504
|
+
color: var(--text-tertiary);
|
|
339
505
|
flex-shrink: 0;
|
|
506
|
+
font-variant-numeric: tabular-nums;
|
|
340
507
|
}
|
|
341
508
|
|
|
342
509
|
.persona-bar-track {
|
|
343
510
|
flex: 1;
|
|
344
|
-
height:
|
|
511
|
+
height: 12px;
|
|
345
512
|
background: var(--bg-root);
|
|
346
513
|
border-radius: 3px;
|
|
347
514
|
overflow: hidden;
|
|
@@ -349,166 +516,311 @@ function getDashboardHtml() {
|
|
|
349
516
|
|
|
350
517
|
.persona-bar-fill {
|
|
351
518
|
height: 100%;
|
|
352
|
-
background: var(--accent);
|
|
353
519
|
border-radius: 3px;
|
|
354
|
-
transition: width 0.
|
|
520
|
+
transition: width 0.6s ease-out;
|
|
355
521
|
}
|
|
356
522
|
|
|
357
523
|
.persona-pct {
|
|
358
|
-
width:
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
524
|
+
width: 32px;
|
|
525
|
+
text-align: right;
|
|
526
|
+
color: var(--text-secondary);
|
|
527
|
+
font-variant-numeric: tabular-nums;
|
|
362
528
|
}
|
|
363
529
|
|
|
364
|
-
/*
|
|
365
|
-
.
|
|
366
|
-
|
|
367
|
-
.registry-item {
|
|
530
|
+
/* -- Parameter Registry -- */
|
|
531
|
+
.param-row {
|
|
368
532
|
display: flex;
|
|
369
|
-
justify-content: space-between;
|
|
370
533
|
align-items: center;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
534
|
+
justify-content: space-between;
|
|
535
|
+
padding: 6px 0;
|
|
536
|
+
border-bottom: 1px solid rgba(39,39,42,0.4);
|
|
537
|
+
font-family: 'JetBrains Mono', monospace;
|
|
538
|
+
font-size: 11px;
|
|
374
539
|
}
|
|
375
540
|
|
|
376
|
-
.
|
|
377
|
-
.registry-key { color: var(--text-secondary); font-family: var(--font-mono); }
|
|
378
|
-
.registry-val { color: var(--accent); font-family: var(--font-mono); font-weight: 500; }
|
|
541
|
+
.param-row:last-child { border-bottom: none; }
|
|
379
542
|
|
|
380
|
-
|
|
381
|
-
|
|
543
|
+
.param-name {
|
|
544
|
+
color: var(--text-tertiary);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.param-val {
|
|
548
|
+
color: var(--accent);
|
|
549
|
+
font-variant-numeric: tabular-nums;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.param-changed {
|
|
553
|
+
color: var(--text-secondary);
|
|
554
|
+
font-size: 9px;
|
|
555
|
+
margin-left: 6px;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* Ghost preview for pending recommendations */
|
|
559
|
+
.param-ghost {
|
|
382
560
|
display: none;
|
|
383
|
-
background: var(--warning-dim);
|
|
384
|
-
border: 1px solid var(--warning);
|
|
385
561
|
color: var(--warning);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
font-
|
|
389
|
-
font-weight: 500;
|
|
390
|
-
align-items: center;
|
|
391
|
-
gap: 8px;
|
|
562
|
+
font-size: 9px;
|
|
563
|
+
margin-left: 4px;
|
|
564
|
+
font-variant-numeric: tabular-nums;
|
|
392
565
|
}
|
|
393
566
|
|
|
394
|
-
.advisor-mode .
|
|
567
|
+
.advisor-mode .param-ghost { display: inline; }
|
|
395
568
|
|
|
396
|
-
.pending-
|
|
397
|
-
|
|
398
|
-
color: var(--
|
|
569
|
+
.param-pending-label {
|
|
570
|
+
display: none;
|
|
571
|
+
color: var(--warning);
|
|
572
|
+
font-size: 9px;
|
|
573
|
+
margin-left: 6px;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.advisor-mode .param-pending-label { display: inline; }
|
|
577
|
+
|
|
578
|
+
.params-scroll {
|
|
579
|
+
overflow-y: auto;
|
|
580
|
+
max-height: 300px;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.params-scroll::-webkit-scrollbar { width: 4px; }
|
|
584
|
+
.params-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
585
|
+
.params-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
586
|
+
|
|
587
|
+
/* -- Violations Table -- */
|
|
588
|
+
.violations-table {
|
|
589
|
+
width: 100%;
|
|
590
|
+
border-collapse: collapse;
|
|
399
591
|
font-size: 11px;
|
|
592
|
+
font-family: 'JetBrains Mono', monospace;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.violations-table thead th {
|
|
596
|
+
text-align: left;
|
|
597
|
+
padding: 7px 8px;
|
|
598
|
+
border-bottom: 1px solid var(--border);
|
|
599
|
+
color: var(--text-secondary);
|
|
400
600
|
font-weight: 600;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
601
|
+
font-size: 10px;
|
|
602
|
+
text-transform: uppercase;
|
|
603
|
+
letter-spacing: 0.06em;
|
|
604
|
+
cursor: pointer;
|
|
605
|
+
user-select: none;
|
|
606
|
+
white-space: nowrap;
|
|
607
|
+
position: sticky;
|
|
608
|
+
top: 0;
|
|
609
|
+
background: var(--bg-panel);
|
|
404
610
|
}
|
|
405
611
|
|
|
406
|
-
.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
padding:
|
|
612
|
+
.violations-table thead th:hover { color: var(--text-tertiary); }
|
|
613
|
+
|
|
614
|
+
.violations-table tbody td {
|
|
615
|
+
padding: 6px 8px;
|
|
616
|
+
border-bottom: 1px solid rgba(39,39,42,0.4);
|
|
617
|
+
color: var(--text-value);
|
|
618
|
+
font-variant-numeric: tabular-nums;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.violations-table tbody tr:hover td { background: rgba(39,39,42,0.3); }
|
|
622
|
+
|
|
623
|
+
.table-scroll {
|
|
624
|
+
overflow-y: auto;
|
|
625
|
+
max-height: 240px;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.table-scroll::-webkit-scrollbar { width: 4px; }
|
|
629
|
+
.table-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
630
|
+
.table-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
|
631
|
+
|
|
632
|
+
.badge-applied { color: var(--accent); font-size: 10px; }
|
|
633
|
+
.badge-skipped { color: var(--text-secondary); font-size: 10px; }
|
|
634
|
+
.badge-rejected { color: var(--danger); font-size: 10px; opacity: 0.6; }
|
|
635
|
+
|
|
636
|
+
/* Pending badge in violations table (advisor mode) */
|
|
637
|
+
.badge-pending {
|
|
638
|
+
display: none;
|
|
639
|
+
color: var(--warning);
|
|
640
|
+
font-size: 10px;
|
|
641
|
+
cursor: pointer;
|
|
642
|
+
position: relative;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.advisor-mode .badge-pending { display: inline-flex; align-items: center; gap: 4px; }
|
|
646
|
+
|
|
647
|
+
.badge-pending:hover { text-decoration: underline; }
|
|
648
|
+
|
|
649
|
+
/* Pending dropdown in violations table */
|
|
650
|
+
.pending-dropdown {
|
|
651
|
+
display: none;
|
|
652
|
+
position: absolute;
|
|
653
|
+
bottom: 100%;
|
|
654
|
+
left: 0;
|
|
655
|
+
background: var(--bg-panel);
|
|
656
|
+
border: 1px solid var(--border);
|
|
410
657
|
border-radius: 4px;
|
|
411
|
-
|
|
658
|
+
padding: 4px 0;
|
|
659
|
+
z-index: 20;
|
|
660
|
+
min-width: 120px;
|
|
661
|
+
box-shadow: 0 -4px 12px rgba(0,0,0,0.4);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.pending-dropdown.open { display: block; }
|
|
665
|
+
|
|
666
|
+
.pending-dropdown-item {
|
|
667
|
+
padding: 5px 12px;
|
|
668
|
+
font-family: 'JetBrains Mono', monospace;
|
|
669
|
+
font-size: 10px;
|
|
412
670
|
cursor: pointer;
|
|
413
|
-
|
|
414
|
-
|
|
671
|
+
display: flex;
|
|
672
|
+
align-items: center;
|
|
673
|
+
gap: 6px;
|
|
674
|
+
white-space: nowrap;
|
|
415
675
|
}
|
|
416
676
|
|
|
417
|
-
.
|
|
418
|
-
.
|
|
419
|
-
.
|
|
677
|
+
.pending-dropdown-item:hover { background: rgba(39,39,42,0.5); }
|
|
678
|
+
.pending-dropdown-item.approve-item { color: var(--accent); }
|
|
679
|
+
.pending-dropdown-item.reject-item { color: var(--danger); }
|
|
420
680
|
|
|
421
|
-
|
|
422
|
-
.
|
|
681
|
+
/* -- Bottom split row -- */
|
|
682
|
+
.split-row {
|
|
683
|
+
display: grid;
|
|
684
|
+
grid-template-columns: 1fr 1fr;
|
|
685
|
+
gap: 12px;
|
|
686
|
+
}
|
|
423
687
|
|
|
424
|
-
/*
|
|
688
|
+
/* -- Empty state -- */
|
|
425
689
|
.empty-state {
|
|
426
|
-
color: var(--text-
|
|
427
|
-
font-
|
|
690
|
+
color: var(--text-secondary);
|
|
691
|
+
font-family: 'JetBrains Mono', monospace;
|
|
692
|
+
font-size: 11px;
|
|
428
693
|
text-align: center;
|
|
429
|
-
padding:
|
|
694
|
+
padding: 20px;
|
|
430
695
|
}
|
|
431
696
|
|
|
432
|
-
/*
|
|
697
|
+
/* -- Responsive -- */
|
|
698
|
+
@media (max-width: 768px) {
|
|
699
|
+
.split-row { grid-template-columns: 1fr; }
|
|
700
|
+
.header { flex-direction: column; align-items: flex-start; gap: 8px; }
|
|
701
|
+
.header-right { flex-wrap: wrap; gap: 12px; }
|
|
702
|
+
.dashboard { padding: 8px; gap: 8px; }
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* -- Reduced motion -- */
|
|
433
706
|
@media (prefers-reduced-motion: reduce) {
|
|
434
707
|
.term-line { animation: none; opacity: 1; transform: none; }
|
|
435
708
|
.live-dot { animation: none; }
|
|
436
709
|
.persona-bar-fill { transition: none; }
|
|
710
|
+
.alert-card.resolved { transition: none; }
|
|
437
711
|
}
|
|
438
712
|
</style>
|
|
439
713
|
</head>
|
|
440
714
|
<body>
|
|
441
715
|
|
|
442
716
|
<!-- Header -->
|
|
443
|
-
<
|
|
444
|
-
<div class="header-
|
|
445
|
-
|
|
446
|
-
<
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
<div class="kpi
|
|
450
|
-
|
|
451
|
-
|
|
717
|
+
<header class="header" id="header">
|
|
718
|
+
<div class="header-left">
|
|
719
|
+
<span class="header-logo">AgentE</span>
|
|
720
|
+
<span class="header-version">v1.7.2</span>
|
|
721
|
+
</div>
|
|
722
|
+
<div class="header-right">
|
|
723
|
+
<div class="kpi-pill">
|
|
724
|
+
<span class="label">Health</span>
|
|
725
|
+
<span class="value health-good" id="h-health">--</span>
|
|
726
|
+
</div>
|
|
727
|
+
<div class="kpi-pill">
|
|
728
|
+
<span class="label">Mode</span>
|
|
729
|
+
<span class="value mode-value-auto" id="h-mode">--</span>
|
|
730
|
+
</div>
|
|
731
|
+
<div class="kpi-pill">
|
|
732
|
+
<span class="label">Tick</span>
|
|
733
|
+
<span class="value" id="h-tick">0</span>
|
|
734
|
+
</div>
|
|
735
|
+
<div class="kpi-pill pending-pill" id="pending-pill">
|
|
736
|
+
<span class="label" style="color: var(--warning);">Pending</span>
|
|
737
|
+
<span class="value" style="color: var(--warning);" id="h-pending">0</span>
|
|
738
|
+
</div>
|
|
739
|
+
<div class="kpi-pill">
|
|
740
|
+
<span class="label">Uptime</span>
|
|
741
|
+
<span class="value" id="h-uptime">0s</span>
|
|
742
|
+
</div>
|
|
743
|
+
<div class="kpi-pill">
|
|
744
|
+
<div class="live-dot" id="live-dot" title="WebSocket connected"></div>
|
|
745
|
+
<span style="color: var(--accent); font-size: 11px;">LIVE</span>
|
|
746
|
+
</div>
|
|
452
747
|
</div>
|
|
748
|
+
</header>
|
|
749
|
+
|
|
750
|
+
<!-- Advisor Banner -->
|
|
751
|
+
<div class="advisor-banner" id="advisor-banner">
|
|
752
|
+
ADVISOR MODE \\u2014 AgentE is waiting for your approval before applying changes
|
|
453
753
|
</div>
|
|
454
754
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
<div class="advisor-banner" id="advisor-banner">
|
|
458
|
-
ADVISOR MODE \u2014 Recommendations require manual approval
|
|
459
|
-
<span class="pending-pill" id="pending-count">0</span> pending
|
|
460
|
-
</div>
|
|
755
|
+
<!-- Dashboard -->
|
|
756
|
+
<main class="dashboard" id="dashboard-root">
|
|
461
757
|
|
|
462
|
-
<!--
|
|
463
|
-
<div class="charts-
|
|
464
|
-
<div class="
|
|
465
|
-
<
|
|
466
|
-
<div class="chart-value" id="cv-health">--</div>
|
|
467
|
-
<canvas id="chart-health"></canvas>
|
|
468
|
-
</div>
|
|
469
|
-
<div class="chart-box">
|
|
470
|
-
<div class="chart-label">Gini Coefficient</div>
|
|
471
|
-
<div class="chart-value" id="cv-gini">--</div>
|
|
472
|
-
<canvas id="chart-gini"></canvas>
|
|
473
|
-
</div>
|
|
474
|
-
<div class="chart-box">
|
|
475
|
-
<div class="chart-label">Net Flow</div>
|
|
476
|
-
<div class="chart-value" id="cv-netflow">--</div>
|
|
477
|
-
<canvas id="chart-netflow"></canvas>
|
|
758
|
+
<!-- Health & Metrics -->
|
|
759
|
+
<div class="panel charts-panel">
|
|
760
|
+
<div class="panel-header">
|
|
761
|
+
<span class="panel-title">Health & Metrics</span>
|
|
478
762
|
</div>
|
|
479
|
-
<div class="chart-
|
|
480
|
-
<div class="chart
|
|
481
|
-
|
|
482
|
-
|
|
763
|
+
<div class="chart-row">
|
|
764
|
+
<div class="mini-chart">
|
|
765
|
+
<div class="mini-chart-label">Health Score</div>
|
|
766
|
+
<canvas id="chart-health"></canvas>
|
|
767
|
+
</div>
|
|
768
|
+
<div class="mini-chart">
|
|
769
|
+
<div class="mini-chart-label">Gini Coefficient</div>
|
|
770
|
+
<canvas id="chart-gini"></canvas>
|
|
771
|
+
</div>
|
|
772
|
+
<div class="mini-chart">
|
|
773
|
+
<div class="mini-chart-label">Net Flow</div>
|
|
774
|
+
<canvas id="chart-flow"></canvas>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="mini-chart">
|
|
777
|
+
<div class="mini-chart-label">Avg Satisfaction</div>
|
|
778
|
+
<canvas id="chart-satisfaction"></canvas>
|
|
779
|
+
</div>
|
|
483
780
|
</div>
|
|
484
781
|
</div>
|
|
485
782
|
|
|
486
783
|
<!-- Decision Feed -->
|
|
487
|
-
<div class="panel">
|
|
488
|
-
<div class="panel-
|
|
489
|
-
|
|
784
|
+
<div class="panel terminal-panel">
|
|
785
|
+
<div class="panel-header">
|
|
786
|
+
<span class="panel-title">Decision Feed</span>
|
|
787
|
+
<div class="panel-meta">
|
|
788
|
+
<span id="decision-count">0 decisions</span>
|
|
789
|
+
<span class="live-label"><div class="live-dot" style="width:5px;height:5px;"></div> LIVE</span>
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
<div class="panel-body">
|
|
793
|
+
<div class="terminal" id="terminal">
|
|
794
|
+
<div id="terminal-inner"></div>
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
490
797
|
</div>
|
|
491
798
|
|
|
492
799
|
<!-- Active Alerts -->
|
|
493
|
-
<div class="panel">
|
|
494
|
-
<div class="panel-
|
|
495
|
-
|
|
496
|
-
<
|
|
800
|
+
<div class="panel alerts-panel">
|
|
801
|
+
<div class="panel-header">
|
|
802
|
+
<span class="panel-title">Active Alerts</span>
|
|
803
|
+
<span class="panel-meta" id="alerts-count">All clear</span>
|
|
497
804
|
</div>
|
|
805
|
+
<div class="alerts-scroll" id="alerts"></div>
|
|
498
806
|
</div>
|
|
499
807
|
|
|
500
808
|
<!-- Violation History -->
|
|
501
809
|
<div class="panel">
|
|
502
|
-
<div class="panel-
|
|
503
|
-
|
|
810
|
+
<div class="panel-header">
|
|
811
|
+
<span class="panel-title">Violation History</span>
|
|
812
|
+
<span class="panel-meta">Last 100 decisions</span>
|
|
813
|
+
</div>
|
|
814
|
+
<div class="table-scroll">
|
|
504
815
|
<table class="violations-table" id="violations-table">
|
|
505
816
|
<thead>
|
|
506
817
|
<tr>
|
|
507
818
|
<th data-sort="tick">Tick</th>
|
|
508
819
|
<th data-sort="principle">Principle</th>
|
|
509
|
-
<th data-sort="severity">
|
|
820
|
+
<th data-sort="severity">Sev</th>
|
|
510
821
|
<th data-sort="parameter">Parameter</th>
|
|
511
|
-
<th data-sort="result">
|
|
822
|
+
<th data-sort="result">Action</th>
|
|
823
|
+
<th>Change</th>
|
|
512
824
|
</tr>
|
|
513
825
|
</thead>
|
|
514
826
|
<tbody id="violations-body"></tbody>
|
|
@@ -516,69 +828,88 @@ function getDashboardHtml() {
|
|
|
516
828
|
</div>
|
|
517
829
|
</div>
|
|
518
830
|
|
|
519
|
-
<!--
|
|
831
|
+
<!-- Persona Distribution + Parameters -->
|
|
520
832
|
<div class="split-row">
|
|
521
|
-
<div class="panel">
|
|
522
|
-
<div class="panel-
|
|
523
|
-
|
|
833
|
+
<div class="panel persona-panel">
|
|
834
|
+
<div class="panel-header">
|
|
835
|
+
<span class="panel-title">Persona Distribution</span>
|
|
836
|
+
<span class="panel-meta" id="persona-count"></span>
|
|
837
|
+
</div>
|
|
838
|
+
<div id="persona-bars">
|
|
524
839
|
<div class="empty-state">No persona data yet</div>
|
|
525
840
|
</div>
|
|
526
841
|
</div>
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
<div class="
|
|
842
|
+
|
|
843
|
+
<div class="panel params-panel">
|
|
844
|
+
<div class="panel-header">
|
|
845
|
+
<span class="panel-title">Parameters</span>
|
|
846
|
+
<span class="panel-meta" id="params-count"></span>
|
|
847
|
+
</div>
|
|
848
|
+
<div class="params-scroll" id="params-list">
|
|
530
849
|
<div class="empty-state">No parameters registered</div>
|
|
531
850
|
</div>
|
|
532
851
|
</div>
|
|
533
852
|
</div>
|
|
534
|
-
|
|
853
|
+
|
|
854
|
+
</main>
|
|
535
855
|
|
|
536
856
|
<script>
|
|
537
857
|
(function() {
|
|
538
858
|
'use strict';
|
|
539
859
|
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
860
|
+
// -- State --
|
|
861
|
+
var ws = null;
|
|
862
|
+
var reconnectDelay = 1000;
|
|
863
|
+
var MAX_RECONNECT = 30000;
|
|
864
|
+
var isAdvisor = false;
|
|
865
|
+
var pendingDecisions = [];
|
|
866
|
+
var MAX_TERMINAL_LINES = 80;
|
|
867
|
+
var MAX_VIOLATIONS = 100;
|
|
868
|
+
var violationSortKey = 'tick';
|
|
869
|
+
var violationSortAsc = false;
|
|
870
|
+
var violations = [];
|
|
871
|
+
var decisionCount = 0;
|
|
551
872
|
|
|
552
873
|
// Chart instances
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
874
|
+
var chartHealth, chartGini, chartFlow, chartSatisfaction;
|
|
875
|
+
|
|
876
|
+
// Param state for registry display
|
|
877
|
+
var paramRegistry = [];
|
|
878
|
+
var paramValues = {};
|
|
879
|
+
var paramLastTick = {};
|
|
880
|
+
var paramPending = {};
|
|
881
|
+
var currentTick = 0;
|
|
882
|
+
|
|
883
|
+
// -- DOM refs --
|
|
884
|
+
var $hHealth = document.getElementById('h-health');
|
|
885
|
+
var $hMode = document.getElementById('h-mode');
|
|
886
|
+
var $hTick = document.getElementById('h-tick');
|
|
887
|
+
var $hUptime = document.getElementById('h-uptime');
|
|
888
|
+
var $hPending = document.getElementById('h-pending');
|
|
889
|
+
var $liveDot = document.getElementById('live-dot');
|
|
890
|
+
var $terminal = document.getElementById('terminal');
|
|
891
|
+
var $terminalInner = document.getElementById('terminal-inner');
|
|
892
|
+
var $alerts = document.getElementById('alerts');
|
|
893
|
+
var $alertsCount = document.getElementById('alerts-count');
|
|
894
|
+
var $violationsBody = document.getElementById('violations-body');
|
|
895
|
+
var $personaBars = document.getElementById('persona-bars');
|
|
896
|
+
var $paramsList = document.getElementById('params-list');
|
|
897
|
+
var $paramsCount = document.getElementById('params-count');
|
|
898
|
+
var $personaCount = document.getElementById('persona-count');
|
|
899
|
+
var $decisionCount = document.getElementById('decision-count');
|
|
900
|
+
var $dashboardRoot = document.getElementById('dashboard-root');
|
|
901
|
+
|
|
902
|
+
// -- Helpers --
|
|
572
903
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''').replace(/\\\\/g,'\'); }
|
|
573
904
|
function pad(n, w) { return String(n).padStart(w || 4, ' '); }
|
|
574
|
-
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '
|
|
575
|
-
function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '
|
|
905
|
+
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\\u2014'; }
|
|
906
|
+
function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '\\u2014'; }
|
|
576
907
|
|
|
577
908
|
function formatUptime(ms) {
|
|
578
|
-
|
|
909
|
+
var s = Math.floor(ms / 1000);
|
|
579
910
|
if (s < 60) return s + 's';
|
|
580
911
|
if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';
|
|
581
|
-
|
|
912
|
+
var h = Math.floor(s / 3600);
|
|
582
913
|
return h + 'h ' + Math.floor((s % 3600) / 60) + 'm';
|
|
583
914
|
}
|
|
584
915
|
|
|
@@ -589,34 +920,63 @@ function getDashboardHtml() {
|
|
|
589
920
|
}
|
|
590
921
|
|
|
591
922
|
function sevClass(s) {
|
|
923
|
+
if (s >= 7) return 'high';
|
|
924
|
+
if (s >= 4) return 'med';
|
|
925
|
+
return 'low';
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function sevCardClass(s) {
|
|
592
929
|
if (s >= 7) return 'sev-high';
|
|
593
930
|
if (s >= 4) return 'sev-med';
|
|
594
931
|
return 'sev-low';
|
|
595
932
|
}
|
|
596
933
|
|
|
597
|
-
|
|
598
|
-
|
|
934
|
+
function personaColor(name) {
|
|
935
|
+
var n = (name || '').toLowerCase();
|
|
936
|
+
if (n === 'atrisk' || n === 'at_risk' || n === 'dormant') return 'var(--danger)';
|
|
937
|
+
if (n === 'spender' || n === 'newentrant' || n === 'new_entrant' || n === 'passive') return 'var(--warning)';
|
|
938
|
+
return 'var(--accent)';
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// -- Chart setup --
|
|
942
|
+
var chartOpts = {
|
|
599
943
|
responsive: true,
|
|
600
944
|
maintainAspectRatio: false,
|
|
601
|
-
animation:
|
|
602
|
-
plugins: {
|
|
945
|
+
animation: false,
|
|
946
|
+
plugins: {
|
|
947
|
+
legend: { display: false },
|
|
948
|
+
tooltip: {
|
|
949
|
+
backgroundColor: '#18181b',
|
|
950
|
+
titleColor: '#a1a1aa',
|
|
951
|
+
bodyColor: '#d4d4d8',
|
|
952
|
+
titleFont: { family: 'JetBrains Mono', size: 10 },
|
|
953
|
+
bodyFont: { family: 'JetBrains Mono', size: 11 },
|
|
954
|
+
borderColor: '#27272a',
|
|
955
|
+
borderWidth: 1,
|
|
956
|
+
padding: 8,
|
|
957
|
+
}
|
|
958
|
+
},
|
|
603
959
|
scales: {
|
|
604
960
|
x: { display: false },
|
|
605
961
|
y: {
|
|
606
|
-
|
|
607
|
-
|
|
962
|
+
grid: { color: 'rgba(39,39,42,0.5)', drawBorder: false },
|
|
963
|
+
ticks: {
|
|
964
|
+
color: '#52525b',
|
|
965
|
+
font: { family: 'JetBrains Mono', size: 9 },
|
|
966
|
+
maxTicksLimit: 3,
|
|
967
|
+
},
|
|
608
968
|
border: { display: false },
|
|
609
969
|
}
|
|
610
970
|
},
|
|
611
971
|
elements: {
|
|
612
|
-
point: { radius: 0 },
|
|
972
|
+
point: { radius: 0, hoverRadius: 3, backgroundColor: '#22c55e' },
|
|
613
973
|
line: { borderWidth: 1.5, tension: 0.3 },
|
|
614
974
|
}
|
|
615
975
|
};
|
|
616
976
|
|
|
617
977
|
function makeChart(id, color, minY, maxY) {
|
|
618
|
-
|
|
619
|
-
|
|
978
|
+
var ctx = document.getElementById(id).getContext('2d');
|
|
979
|
+
var opts = JSON.parse(JSON.stringify(chartOpts));
|
|
620
980
|
if (minY !== undefined) opts.scales.y.min = minY;
|
|
621
981
|
if (maxY !== undefined) opts.scales.y.max = maxY;
|
|
622
982
|
return new Chart(ctx, {
|
|
@@ -637,7 +997,7 @@ function getDashboardHtml() {
|
|
|
637
997
|
function initCharts() {
|
|
638
998
|
chartHealth = makeChart('chart-health', '#22c55e', 0, 100);
|
|
639
999
|
chartGini = makeChart('chart-gini', '#eab308', 0, 1);
|
|
640
|
-
|
|
1000
|
+
chartFlow = makeChart('chart-flow', '#3b82f6');
|
|
641
1001
|
chartSatisfaction = makeChart('chart-satisfaction', '#22c55e', 0, 100);
|
|
642
1002
|
}
|
|
643
1003
|
|
|
@@ -647,178 +1007,278 @@ function getDashboardHtml() {
|
|
|
647
1007
|
chart.update('none');
|
|
648
1008
|
}
|
|
649
1009
|
|
|
650
|
-
//
|
|
1010
|
+
// -- Terminal --
|
|
651
1011
|
function addTerminalLine(html) {
|
|
652
|
-
|
|
1012
|
+
var el = document.createElement('div');
|
|
653
1013
|
el.className = 'term-line';
|
|
654
1014
|
el.innerHTML = html;
|
|
655
|
-
$
|
|
656
|
-
while ($
|
|
657
|
-
$
|
|
1015
|
+
$terminalInner.appendChild(el);
|
|
1016
|
+
while ($terminalInner.children.length > MAX_TERMINAL_LINES) {
|
|
1017
|
+
$terminalInner.removeChild($terminalInner.firstChild);
|
|
658
1018
|
}
|
|
659
1019
|
$terminal.scrollTop = $terminal.scrollHeight;
|
|
660
1020
|
}
|
|
661
1021
|
|
|
662
1022
|
function decisionToTerminal(d) {
|
|
663
|
-
|
|
664
|
-
? '<span class="t-
|
|
1023
|
+
var resultIcon = d.result === 'applied'
|
|
1024
|
+
? '<span class="t-check">\\u2705 </span>'
|
|
665
1025
|
: d.result === 'rejected'
|
|
666
1026
|
? '<span class="t-fail">\\u274c </span>'
|
|
667
|
-
:
|
|
1027
|
+
: d.result === 'skipped_override'
|
|
1028
|
+
? '<span class="t-pending-icon">\\u23f3 </span>'
|
|
1029
|
+
: '<span class="t-skip">\\u23f8 </span>';
|
|
668
1030
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1031
|
+
var principle = d.diagnosis?.principle || {};
|
|
1032
|
+
var plan = d.plan || {};
|
|
1033
|
+
var severity = d.diagnosis?.violation?.severity ?? '?';
|
|
1034
|
+
var confidence = d.diagnosis?.violation?.confidence;
|
|
1035
|
+
var confStr = confidence != null ? (confidence * 100).toFixed(0) + '%' : '?';
|
|
674
1036
|
|
|
675
|
-
|
|
1037
|
+
var advisorBtns = '';
|
|
676
1038
|
if (isAdvisor && d.result === 'skipped_override') {
|
|
677
|
-
advisorBtns = '<span class="advisor-
|
|
678
|
-
+ '<button class="advisor-btn approve"
|
|
679
|
-
+ '<button class="advisor-btn reject"
|
|
1039
|
+
advisorBtns = '<span class="advisor-btn-group" data-id="' + esc(d.id) + '">'
|
|
1040
|
+
+ '<button class="advisor-btn approve-btn" data-action="approve" data-id="' + esc(d.id) + '">✓ Approve</button>'
|
|
1041
|
+
+ '<button class="advisor-btn reject-btn" data-action="reject" data-id="' + esc(d.id) + '">✕ Reject</button>'
|
|
680
1042
|
+ '</span>';
|
|
681
1043
|
}
|
|
682
1044
|
|
|
683
1045
|
return '<span class="t-tick">[Tick ' + pad(d.tick) + ']</span> '
|
|
684
1046
|
+ resultIcon
|
|
685
|
-
+ '<span class="t-principle">
|
|
686
|
-
+ '<span class="t-param">' + esc(plan.parameter || '
|
|
1047
|
+
+ '<span class="t-principle">' + esc(principle.name || '') + ':</span> '
|
|
1048
|
+
+ '<span class="t-param">' + esc(plan.parameter || '\\u2014') + ' </span>'
|
|
687
1049
|
+ '<span class="t-old">' + fmt(plan.currentValue) + '</span>'
|
|
688
1050
|
+ '<span class="t-arrow"> \\u2192 </span>'
|
|
689
|
-
+
|
|
690
|
-
|
|
1051
|
+
+ (d.result === 'skipped_override'
|
|
1052
|
+
? '<span class="t-pending-val">' + fmt(plan.targetValue) + '</span>'
|
|
1053
|
+
: '<span class="t-new">' + fmt(plan.targetValue) + '</span>')
|
|
1054
|
+
+ '<span class="t-meta"> sev ' + severity + ', conf ' + confStr + '</span>'
|
|
691
1055
|
+ advisorBtns;
|
|
692
1056
|
}
|
|
693
1057
|
|
|
694
|
-
//
|
|
1058
|
+
// -- Alerts --
|
|
695
1059
|
function renderAlerts(alerts) {
|
|
696
1060
|
if (!alerts || alerts.length === 0) {
|
|
697
|
-
$
|
|
1061
|
+
$alerts.innerHTML = '<div class="empty-state">No active violations. Economy is healthy.</div>';
|
|
1062
|
+
$alertsCount.textContent = 'All clear';
|
|
698
1063
|
return;
|
|
699
1064
|
}
|
|
700
|
-
|
|
701
|
-
$
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1065
|
+
var sorted = alerts.slice().sort(function(a, b) { return (b.severity || 0) - (a.severity || 0); });
|
|
1066
|
+
$alertsCount.textContent = sorted.length + ' violation' + (sorted.length !== 1 ? 's' : '');
|
|
1067
|
+
|
|
1068
|
+
$alerts.innerHTML = sorted.map(function(a) {
|
|
1069
|
+
var sev = a.severity || a.violation?.severity || 0;
|
|
1070
|
+
var sc = sevClass(sev);
|
|
1071
|
+
var cardCls = sevCardClass(sev);
|
|
1072
|
+
var name = a.principleName || a.principle?.name || '?';
|
|
1073
|
+
var pid = a.principleId || a.principle?.id || '?';
|
|
1074
|
+
var reason = a.reasoning || a.violation?.suggestedAction?.reasoning || '';
|
|
1075
|
+
var suggestion = a.suggestion || '';
|
|
1076
|
+
|
|
1077
|
+
var hasPending = isAdvisor && pendingDecisions.some(function(pd) {
|
|
1078
|
+
return pd.principleId === pid || (pd.diagnosis?.principle?.id === pid);
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
var btns = '';
|
|
1082
|
+
if (hasPending) {
|
|
1083
|
+
btns = '<button class="alert-approve-btn" data-action="approve-alert" data-principle="' + esc(pid) + '">✓ Approve Fix</button>'
|
|
1084
|
+
+ '<button class="alert-reject-btn" data-action="reject-alert" data-principle="' + esc(pid) + '">✗ Reject</button>';
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return '<div class="alert-card ' + cardCls + '" data-principle-id="' + esc(pid) + '">'
|
|
1088
|
+
+ '<div class="alert-top">'
|
|
1089
|
+
+ '<span class="sev-badge ' + sc + '">' + sev + '</span>'
|
|
1090
|
+
+ '<span class="alert-principle-name">[' + esc(pid) + '] ' + esc(name) + '</span>'
|
|
1091
|
+
+ '</div>'
|
|
1092
|
+
+ (reason ? '<div class="alert-evidence">' + esc(reason) + '</div>' : '')
|
|
1093
|
+
+ (suggestion ? '<div class="alert-suggestion">Suggested: ' + esc(suggestion) + '</div>' : '')
|
|
1094
|
+
+ btns
|
|
1095
|
+
+ '</div>';
|
|
713
1096
|
}).join('');
|
|
714
1097
|
}
|
|
715
1098
|
|
|
716
|
-
//
|
|
1099
|
+
// -- Violations table --
|
|
717
1100
|
function addViolation(d) {
|
|
1101
|
+
var plan = d.plan || {};
|
|
718
1102
|
violations.push({
|
|
719
1103
|
tick: d.tick,
|
|
720
1104
|
principle: (d.diagnosis?.principle?.id || '?') + ' ' + (d.diagnosis?.principle?.name || ''),
|
|
721
1105
|
severity: d.diagnosis?.violation?.severity || 0,
|
|
722
|
-
parameter:
|
|
1106
|
+
parameter: plan.parameter || '\\u2014',
|
|
723
1107
|
result: d.result,
|
|
1108
|
+
currentValue: plan.currentValue,
|
|
1109
|
+
targetValue: plan.targetValue,
|
|
1110
|
+
decisionId: d.id,
|
|
724
1111
|
});
|
|
725
1112
|
if (violations.length > MAX_VIOLATIONS) violations.shift();
|
|
1113
|
+
decisionCount = violations.length;
|
|
1114
|
+
$decisionCount.textContent = decisionCount + ' decisions';
|
|
726
1115
|
renderViolations();
|
|
727
1116
|
}
|
|
728
1117
|
|
|
729
1118
|
function renderViolations() {
|
|
730
|
-
|
|
731
|
-
|
|
1119
|
+
var sorted = violations.slice().sort(function(a, b) {
|
|
1120
|
+
var va = a[violationSortKey], vb = b[violationSortKey];
|
|
732
1121
|
if (va < vb) return violationSortAsc ? -1 : 1;
|
|
733
1122
|
if (va > vb) return violationSortAsc ? 1 : -1;
|
|
734
1123
|
return 0;
|
|
735
1124
|
});
|
|
736
1125
|
$violationsBody.innerHTML = sorted.map(function(v) {
|
|
1126
|
+
var isPending = v.result === 'skipped_override';
|
|
1127
|
+
|
|
1128
|
+
var actionHtml;
|
|
1129
|
+
if (isPending && isAdvisor) {
|
|
1130
|
+
actionHtml = '<span class="badge-pending" data-action="toggle-pending" data-id="' + esc(v.decisionId || '') + '">'
|
|
1131
|
+
+ 'Pending \\u25BE'
|
|
1132
|
+
+ '<div class="pending-dropdown">'
|
|
1133
|
+
+ '<div class="pending-dropdown-item approve-item" data-action="approve" data-id="' + esc(v.decisionId || '') + '">✓ Approve</div>'
|
|
1134
|
+
+ '<div class="pending-dropdown-item reject-item" data-action="reject" data-id="' + esc(v.decisionId || '') + '">✗ Reject</div>'
|
|
1135
|
+
+ '</div>'
|
|
1136
|
+
+ '</span>';
|
|
1137
|
+
} else if (v.result === 'applied') {
|
|
1138
|
+
actionHtml = '<span class="badge-applied">Applied</span>';
|
|
1139
|
+
} else if (v.result === 'rejected') {
|
|
1140
|
+
actionHtml = '<span class="badge-rejected">Rejected</span>';
|
|
1141
|
+
} else {
|
|
1142
|
+
actionHtml = '<span class="badge-skipped">' + esc(v.result || 'Skipped') + '</span>';
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
var changeHtml = '';
|
|
1146
|
+
if (v.currentValue != null && v.targetValue != null) {
|
|
1147
|
+
var valColor = isPending && isAdvisor ? 'var(--warning)' : 'var(--accent)';
|
|
1148
|
+
changeHtml = '<span style="color:var(--text-value)">' + fmt(v.currentValue) + '</span>'
|
|
1149
|
+
+ '<span style="color:var(--info)"> \\u2192 </span>'
|
|
1150
|
+
+ '<span style="color:' + valColor + '">' + fmt(v.targetValue) + '</span>';
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
var sevColor = v.severity >= 7 ? 'var(--danger)' : v.severity >= 4 ? 'var(--warning)' : 'var(--accent)';
|
|
1154
|
+
|
|
737
1155
|
return '<tr>'
|
|
738
|
-
+ '<td>' + v.tick + '</td>'
|
|
739
|
-
+ '<td style="color:var(--text-
|
|
740
|
-
+ '<td
|
|
741
|
-
+ '<td>' + esc(v.parameter) + '</td>'
|
|
742
|
-
+ '<td>' +
|
|
1156
|
+
+ '<td style="color:var(--text-secondary)">' + v.tick + '</td>'
|
|
1157
|
+
+ '<td style="color:var(--text-value)">' + esc(v.principle) + '</td>'
|
|
1158
|
+
+ '<td style="color:' + sevColor + '">' + v.severity + '</td>'
|
|
1159
|
+
+ '<td style="color:var(--text-tertiary)">' + esc(v.parameter) + '</td>'
|
|
1160
|
+
+ '<td class="action-cell">' + actionHtml + '</td>'
|
|
1161
|
+
+ '<td>' + changeHtml + '</td>'
|
|
743
1162
|
+ '</tr>';
|
|
744
1163
|
}).join('');
|
|
745
1164
|
}
|
|
746
1165
|
|
|
747
1166
|
// Table sorting
|
|
748
|
-
document.querySelectorAll('.violations-table th').forEach(function(th) {
|
|
1167
|
+
document.querySelectorAll('.violations-table th[data-sort]').forEach(function(th) {
|
|
749
1168
|
th.addEventListener('click', function() {
|
|
750
|
-
|
|
1169
|
+
var key = th.dataset.sort;
|
|
751
1170
|
if (violationSortKey === key) violationSortAsc = !violationSortAsc;
|
|
752
1171
|
else { violationSortKey = key; violationSortAsc = true; }
|
|
753
1172
|
renderViolations();
|
|
754
1173
|
});
|
|
755
1174
|
});
|
|
756
1175
|
|
|
757
|
-
//
|
|
1176
|
+
// -- Personas --
|
|
758
1177
|
function renderPersonas(dist) {
|
|
759
1178
|
if (!dist || Object.keys(dist).length === 0) {
|
|
760
1179
|
$personaBars.innerHTML = '<div class="empty-state">No persona data yet</div>';
|
|
1180
|
+
$personaCount.textContent = '';
|
|
761
1181
|
return;
|
|
762
1182
|
}
|
|
763
|
-
|
|
764
|
-
|
|
1183
|
+
var total = Object.values(dist).reduce(function(s, v) { return s + v; }, 0);
|
|
1184
|
+
$personaCount.textContent = total + ' agents';
|
|
1185
|
+
var entries = Object.entries(dist).sort(function(a, b) { return b[1] - a[1]; });
|
|
765
1186
|
$personaBars.innerHTML = entries.map(function(e) {
|
|
766
|
-
|
|
1187
|
+
var pctVal = total > 0 ? (e[1] / total * 100) : 0;
|
|
1188
|
+
var color = personaColor(e[0]);
|
|
767
1189
|
return '<div class="persona-row">'
|
|
768
|
-
+ '<
|
|
769
|
-
+ '<div class="persona-bar-track"><div class="persona-bar-fill" style="width:' + pctVal + '
|
|
770
|
-
+ '<
|
|
1190
|
+
+ '<span class="persona-label">' + esc(e[0]) + '</span>'
|
|
1191
|
+
+ '<div class="persona-bar-track"><div class="persona-bar-fill" style="width:' + pctVal.toFixed(0) + '%;background:' + color + ';"></div></div>'
|
|
1192
|
+
+ '<span class="persona-pct">' + pctVal.toFixed(0) + '%</span>'
|
|
771
1193
|
+ '</div>';
|
|
772
1194
|
}).join('');
|
|
773
1195
|
}
|
|
774
1196
|
|
|
775
|
-
//
|
|
776
|
-
function
|
|
777
|
-
if (!principles || principles.length === 0) {
|
|
778
|
-
$
|
|
1197
|
+
// -- Parameters --
|
|
1198
|
+
function renderParams(principles, registryValues) {
|
|
1199
|
+
if ((!principles || principles.length === 0) && Object.keys(paramValues).length === 0) {
|
|
1200
|
+
$paramsList.innerHTML = '<div class="empty-state">No parameters registered</div>';
|
|
1201
|
+
$paramsCount.textContent = '';
|
|
779
1202
|
return;
|
|
780
1203
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1204
|
+
|
|
1205
|
+
// If we have actual parameter values from API, show those
|
|
1206
|
+
if (registryValues && Object.keys(registryValues).length > 0) {
|
|
1207
|
+
var entries = Object.entries(registryValues);
|
|
1208
|
+
$paramsCount.textContent = entries.length + ' tracked';
|
|
1209
|
+
$paramsList.innerHTML = entries.map(function(e) {
|
|
1210
|
+
var key = e[0];
|
|
1211
|
+
var val = e[1];
|
|
1212
|
+
var ticksAgo = currentTick - (paramLastTick[key] || 0);
|
|
1213
|
+
var agoText = ticksAgo <= 0 ? '' : ticksAgo <= 5 ? 'just now' : ticksAgo + ' ticks ago';
|
|
1214
|
+
var pending = paramPending[key];
|
|
1215
|
+
|
|
1216
|
+
if (pending && isAdvisor) {
|
|
1217
|
+
return '<div class="param-row">'
|
|
1218
|
+
+ '<span class="param-name">' + esc(key) + '</span>'
|
|
1219
|
+
+ '<span>'
|
|
1220
|
+
+ '<span class="param-val">' + fmt(val) + '</span>'
|
|
1221
|
+
+ '<span class="param-ghost" style="display:inline;"> \\u2192 ' + fmt(pending.proposedVal) + '?</span>'
|
|
1222
|
+
+ '<span class="param-pending-label" style="display:inline;">pending</span>'
|
|
1223
|
+
+ '</span>'
|
|
1224
|
+
+ '</div>';
|
|
1225
|
+
}
|
|
1226
|
+
return '<div class="param-row">'
|
|
1227
|
+
+ '<span class="param-name">' + esc(key) + '</span>'
|
|
1228
|
+
+ '<span><span class="param-val">' + fmt(val) + '</span>'
|
|
1229
|
+
+ (agoText ? '<span class="param-changed">' + agoText + '</span>' : '')
|
|
1230
|
+
+ '</span>'
|
|
1231
|
+
+ '</div>';
|
|
1232
|
+
}).join('');
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Fallback: show principle names (legacy behavior)
|
|
1237
|
+
if (principles && principles.length > 0) {
|
|
1238
|
+
$paramsCount.textContent = principles.length + ' registered';
|
|
1239
|
+
$paramsList.innerHTML = principles.slice(0, 30).map(function(p) {
|
|
1240
|
+
return '<div class="param-row">'
|
|
1241
|
+
+ '<span class="param-name">[' + esc(p.id) + ']</span>'
|
|
1242
|
+
+ '<span class="param-val">' + esc(p.name) + '</span>'
|
|
1243
|
+
+ '</div>';
|
|
1244
|
+
}).join('');
|
|
1245
|
+
}
|
|
787
1246
|
}
|
|
788
1247
|
|
|
789
|
-
//
|
|
1248
|
+
// -- KPI update --
|
|
790
1249
|
function updateKPIs(data) {
|
|
791
1250
|
if (data.health != null) {
|
|
792
|
-
$
|
|
793
|
-
$
|
|
794
|
-
document.getElementById('cv-health').textContent = data.health + '/100';
|
|
1251
|
+
$hHealth.textContent = data.health + '/100';
|
|
1252
|
+
$hHealth.className = 'value ' + healthClass(data.health);
|
|
795
1253
|
}
|
|
796
1254
|
if (data.mode != null) {
|
|
797
|
-
$
|
|
1255
|
+
$hMode.textContent = data.mode;
|
|
798
1256
|
isAdvisor = data.mode === 'advisor';
|
|
799
|
-
$
|
|
1257
|
+
$hMode.className = 'value ' + (isAdvisor ? 'mode-value-advisor' : 'mode-value-auto');
|
|
1258
|
+
document.body.classList.toggle('advisor-mode', isAdvisor);
|
|
1259
|
+
}
|
|
1260
|
+
if (data.tick != null) {
|
|
1261
|
+
$hTick.textContent = data.tick;
|
|
1262
|
+
currentTick = data.tick;
|
|
1263
|
+
}
|
|
1264
|
+
if (data.uptime != null) $hUptime.textContent = formatUptime(data.uptime);
|
|
1265
|
+
if (data.pendingCount != null || data.activePlans != null) {
|
|
1266
|
+
var count = data.pendingCount || data.activePlans || 0;
|
|
1267
|
+
$hPending.textContent = count;
|
|
800
1268
|
}
|
|
801
|
-
if (data.tick != null) $kpiTick.textContent = data.tick;
|
|
802
|
-
if (data.uptime != null) $kpiUptime.textContent = formatUptime(data.uptime);
|
|
803
|
-
if (data.activePlans != null) $kpiPlans.textContent = data.activePlans;
|
|
804
1269
|
}
|
|
805
1270
|
|
|
806
|
-
//
|
|
1271
|
+
// -- Metrics history --
|
|
807
1272
|
function updateChartsFromHistory(history) {
|
|
808
1273
|
if (!history || history.length === 0) return;
|
|
809
|
-
|
|
1274
|
+
var ticks = history.map(function(h) { return h.tick; });
|
|
810
1275
|
updateChart(chartHealth, ticks, history.map(function(h) { return h.health; }));
|
|
811
1276
|
updateChart(chartGini, ticks, history.map(function(h) { return h.giniCoefficient; }));
|
|
812
|
-
updateChart(
|
|
1277
|
+
updateChart(chartFlow, ticks, history.map(function(h) { return h.netFlow; }));
|
|
813
1278
|
updateChart(chartSatisfaction, ticks, history.map(function(h) { return h.avgSatisfaction; }));
|
|
814
|
-
|
|
815
|
-
const last = history[history.length - 1];
|
|
816
|
-
document.getElementById('cv-gini').textContent = last.giniCoefficient.toFixed(3);
|
|
817
|
-
document.getElementById('cv-netflow').textContent = last.netFlow.toFixed(1);
|
|
818
|
-
document.getElementById('cv-satisfaction').textContent = last.avgSatisfaction.toFixed(0) + '/100';
|
|
819
1279
|
}
|
|
820
1280
|
|
|
821
|
-
//
|
|
1281
|
+
// -- API calls --
|
|
822
1282
|
function fetchJSON(path) {
|
|
823
1283
|
return fetch(path).then(function(r) { return r.json(); });
|
|
824
1284
|
}
|
|
@@ -853,19 +1313,22 @@ function getDashboardHtml() {
|
|
|
853
1313
|
}).catch(function() {});
|
|
854
1314
|
|
|
855
1315
|
fetchJSON('/principles').then(function(data) {
|
|
856
|
-
if (data.principles)
|
|
1316
|
+
if (data.principles) {
|
|
1317
|
+
paramRegistry = data.principles;
|
|
1318
|
+
renderParams(data.principles, paramValues);
|
|
1319
|
+
}
|
|
857
1320
|
}).catch(function() {});
|
|
858
1321
|
|
|
859
1322
|
fetchJSON('/pending').then(function(data) {
|
|
860
1323
|
if (data.pending) {
|
|
861
1324
|
pendingDecisions = data.pending;
|
|
862
|
-
$
|
|
1325
|
+
$hPending.textContent = data.count || 0;
|
|
863
1326
|
}
|
|
864
1327
|
}).catch(function() {});
|
|
865
1328
|
}
|
|
866
1329
|
|
|
867
|
-
//
|
|
868
|
-
|
|
1330
|
+
// -- Polling fallback --
|
|
1331
|
+
var pollInterval = null;
|
|
869
1332
|
|
|
870
1333
|
function startPolling() {
|
|
871
1334
|
if (pollInterval) return;
|
|
@@ -882,9 +1345,9 @@ function getDashboardHtml() {
|
|
|
882
1345
|
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
|
|
883
1346
|
}
|
|
884
1347
|
|
|
885
|
-
//
|
|
1348
|
+
// -- WebSocket --
|
|
886
1349
|
function connectWS() {
|
|
887
|
-
|
|
1350
|
+
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
888
1351
|
ws = new WebSocket(proto + '//' + location.host);
|
|
889
1352
|
|
|
890
1353
|
ws.onopen = function() {
|
|
@@ -892,13 +1355,12 @@ function getDashboardHtml() {
|
|
|
892
1355
|
$liveDot.classList.remove('disconnected');
|
|
893
1356
|
$liveDot.title = 'WebSocket connected';
|
|
894
1357
|
stopPolling();
|
|
895
|
-
// Request fresh health
|
|
896
1358
|
ws.send(JSON.stringify({ type: 'health' }));
|
|
897
1359
|
};
|
|
898
1360
|
|
|
899
1361
|
ws.onclose = function() {
|
|
900
1362
|
$liveDot.classList.add('disconnected');
|
|
901
|
-
$liveDot.title = 'WebSocket disconnected
|
|
1363
|
+
$liveDot.title = 'WebSocket disconnected \\u2014 reconnecting...';
|
|
902
1364
|
startPolling();
|
|
903
1365
|
setTimeout(connectWS, reconnectDelay);
|
|
904
1366
|
reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT);
|
|
@@ -914,7 +1376,6 @@ function getDashboardHtml() {
|
|
|
914
1376
|
case 'tick_result':
|
|
915
1377
|
updateKPIs({ health: msg.health, tick: msg.tick });
|
|
916
1378
|
if (msg.alerts) renderAlerts(msg.alerts);
|
|
917
|
-
// Refresh charts
|
|
918
1379
|
fetchJSON('/metrics').then(function(data) {
|
|
919
1380
|
if (data.history) updateChartsFromHistory(data.history);
|
|
920
1381
|
if (data.latest) renderPersonas(data.latest.personaDistribution);
|
|
@@ -930,32 +1391,90 @@ function getDashboardHtml() {
|
|
|
930
1391
|
pendingDecisions = pendingDecisions.filter(function(d) {
|
|
931
1392
|
return d.id !== msg.decisionId;
|
|
932
1393
|
});
|
|
933
|
-
$
|
|
1394
|
+
$hPending.textContent = pendingDecisions.length;
|
|
934
1395
|
}
|
|
935
1396
|
break;
|
|
936
1397
|
}
|
|
937
1398
|
};
|
|
938
1399
|
}
|
|
939
1400
|
|
|
940
|
-
//
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1401
|
+
// -- Advisor actions (event delegation) --
|
|
1402
|
+
document.addEventListener('click', function(e) {
|
|
1403
|
+
var btn = e.target.closest('[data-action]');
|
|
1404
|
+
if (!btn) return;
|
|
1405
|
+
var action = btn.getAttribute('data-action');
|
|
1406
|
+
var id = btn.getAttribute('data-id');
|
|
1407
|
+
var principleId = btn.getAttribute('data-principle');
|
|
1408
|
+
|
|
1409
|
+
// Toggle pending dropdown
|
|
1410
|
+
if (action === 'toggle-pending') {
|
|
1411
|
+
var dropdown = btn.querySelector('.pending-dropdown');
|
|
1412
|
+
if (!dropdown) return;
|
|
1413
|
+
document.querySelectorAll('.pending-dropdown.open').forEach(function(d) {
|
|
1414
|
+
if (d !== dropdown) d.classList.remove('open');
|
|
1415
|
+
});
|
|
1416
|
+
dropdown.classList.toggle('open');
|
|
1417
|
+
e.stopPropagation();
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Approve from alert card
|
|
1422
|
+
if (action === 'approve-alert' && principleId) {
|
|
1423
|
+
var pd = pendingDecisions.find(function(d) {
|
|
1424
|
+
return d.principleId === principleId || (d.diagnosis?.principle?.id === principleId);
|
|
1425
|
+
});
|
|
1426
|
+
if (pd) {
|
|
1427
|
+
postJSON('/approve', { decisionId: pd.id }).then(function(data) {
|
|
1428
|
+
if (data.ok) {
|
|
1429
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-check">\\u2705 Approved ' + esc(pd.id) + '</span>');
|
|
1430
|
+
}
|
|
1431
|
+
}).catch(function() {});
|
|
945
1432
|
}
|
|
946
|
-
|
|
947
|
-
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
948
1435
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1436
|
+
// Reject from alert card
|
|
1437
|
+
if (action === 'reject-alert' && principleId) {
|
|
1438
|
+
var pd2 = pendingDecisions.find(function(d) {
|
|
1439
|
+
return d.principleId === principleId || (d.diagnosis?.principle?.id === principleId);
|
|
1440
|
+
});
|
|
1441
|
+
if (pd2) {
|
|
1442
|
+
var reason = prompt('Rejection reason (optional):');
|
|
1443
|
+
postJSON('/reject', { decisionId: pd2.id, reason: reason || undefined }).then(function(data) {
|
|
1444
|
+
if (data.ok) {
|
|
1445
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-fail">\\u274c Rejected ' + esc(pd2.id) + '</span>');
|
|
1446
|
+
}
|
|
1447
|
+
}).catch(function() {});
|
|
954
1448
|
}
|
|
955
|
-
|
|
956
|
-
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
if (!id) return;
|
|
1453
|
+
|
|
1454
|
+
if (action === 'approve') {
|
|
1455
|
+
postJSON('/approve', { decisionId: id }).then(function(data) {
|
|
1456
|
+
if (data.ok) {
|
|
1457
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-check">\\u2705 Approved ' + esc(id) + '</span>');
|
|
1458
|
+
}
|
|
1459
|
+
}).catch(function() {});
|
|
1460
|
+
} else if (action === 'reject') {
|
|
1461
|
+
var reason2 = prompt('Rejection reason (optional):');
|
|
1462
|
+
postJSON('/reject', { decisionId: id, reason: reason2 || undefined }).then(function(data) {
|
|
1463
|
+
if (data.ok) {
|
|
1464
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-fail">\\u274c Rejected ' + esc(id) + '</span>');
|
|
1465
|
+
}
|
|
1466
|
+
}).catch(function() {});
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
// Close dropdowns on outside click
|
|
1471
|
+
document.addEventListener('click', function(e) {
|
|
1472
|
+
if (!e.target.closest('.badge-pending')) {
|
|
1473
|
+
document.querySelectorAll('.pending-dropdown.open').forEach(function(d) { d.classList.remove('open'); });
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
957
1476
|
|
|
958
|
-
//
|
|
1477
|
+
// -- Init --
|
|
959
1478
|
initCharts();
|
|
960
1479
|
loadInitialData();
|
|
961
1480
|
connectWS();
|
|
@@ -1404,7 +1923,7 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1404
1923
|
if (server.apiKey) {
|
|
1405
1924
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
1406
1925
|
const token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");
|
|
1407
|
-
if (token !== server.apiKey) {
|
|
1926
|
+
if (!token || token.length !== server.apiKey.length || !(0, import_node_crypto2.timingSafeEqual)(Buffer.from(token), Buffer.from(server.apiKey))) {
|
|
1408
1927
|
ws.close(1008, "Unauthorized");
|
|
1409
1928
|
return;
|
|
1410
1929
|
}
|
|
@@ -1537,10 +2056,11 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
1537
2056
|
broadcast
|
|
1538
2057
|
};
|
|
1539
2058
|
}
|
|
1540
|
-
var import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS, MIN_TICK_INTERVAL_MS;
|
|
2059
|
+
var import_node_crypto2, import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS, MIN_TICK_INTERVAL_MS;
|
|
1541
2060
|
var init_websocket = __esm({
|
|
1542
2061
|
"src/websocket.ts"() {
|
|
1543
2062
|
"use strict";
|
|
2063
|
+
import_node_crypto2 = require("crypto");
|
|
1544
2064
|
import_ws = require("ws");
|
|
1545
2065
|
import_core2 = require("@agent-e/core");
|
|
1546
2066
|
MAX_WS_PAYLOAD = 1048576;
|