@agent-e/server 1.5.13 → 1.6.2
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/README.md +87 -0
- package/dist/AgentEServer-DT6ETPR6.mjs +7 -0
- package/dist/chunk-53EPMEWX.mjs +1598 -0
- package/dist/chunk-53EPMEWX.mjs.map +1 -0
- package/dist/cli.js +1105 -22
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1110 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
- package/dist/AgentEServer-CBG5JMN4.mjs +0 -7
- package/dist/chunk-OXHIY4RU.mjs +0 -515
- package/dist/chunk-OXHIY4RU.mjs.map +0 -1
- /package/dist/{AgentEServer-CBG5JMN4.mjs.map → AgentEServer-DT6ETPR6.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -30,8 +30,955 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// src/dashboard.ts
|
|
34
|
+
function getDashboardHtml() {
|
|
35
|
+
return `<!DOCTYPE html>
|
|
36
|
+
<html lang="en">
|
|
37
|
+
<head>
|
|
38
|
+
<meta charset="UTF-8">
|
|
39
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
40
|
+
<title>AgentE Dashboard</title>
|
|
41
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
42
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
43
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
44
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
|
|
45
|
+
<style>
|
|
46
|
+
:root {
|
|
47
|
+
--bg-root: #09090b;
|
|
48
|
+
--bg-panel: #18181b;
|
|
49
|
+
--bg-panel-hover: #1f1f23;
|
|
50
|
+
--border: #27272a;
|
|
51
|
+
--border-light: #3f3f46;
|
|
52
|
+
--text-primary: #f4f4f5;
|
|
53
|
+
--text-secondary: #a1a1aa;
|
|
54
|
+
--text-muted: #71717a;
|
|
55
|
+
--text-dim: #52525b;
|
|
56
|
+
--accent: #22c55e;
|
|
57
|
+
--accent-dim: #166534;
|
|
58
|
+
--warning: #eab308;
|
|
59
|
+
--warning-dim: #854d0e;
|
|
60
|
+
--danger: #ef4444;
|
|
61
|
+
--danger-dim: #991b1b;
|
|
62
|
+
--blue: #3b82f6;
|
|
63
|
+
--font-sans: 'IBM Plex Sans', system-ui, sans-serif;
|
|
64
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
68
|
+
|
|
69
|
+
body {
|
|
70
|
+
background: var(--bg-root);
|
|
71
|
+
color: var(--text-primary);
|
|
72
|
+
font-family: var(--font-sans);
|
|
73
|
+
font-size: 14px;
|
|
74
|
+
line-height: 1.5;
|
|
75
|
+
overflow-x: hidden;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
79
|
+
.header {
|
|
80
|
+
position: sticky;
|
|
81
|
+
top: 0;
|
|
82
|
+
z-index: 100;
|
|
83
|
+
background: var(--bg-root);
|
|
84
|
+
border-bottom: 1px solid var(--border);
|
|
85
|
+
padding: 12px 24px;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 24px;
|
|
89
|
+
backdrop-filter: blur(8px);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.header-brand {
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
font-size: 16px;
|
|
95
|
+
color: var(--text-primary);
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.header-brand span { color: var(--accent); }
|
|
100
|
+
|
|
101
|
+
.kpi-row {
|
|
102
|
+
display: flex;
|
|
103
|
+
gap: 20px;
|
|
104
|
+
flex-wrap: wrap;
|
|
105
|
+
align-items: center;
|
|
106
|
+
margin-left: auto;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.kpi {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 6px;
|
|
113
|
+
font-size: 13px;
|
|
114
|
+
color: var(--text-secondary);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.kpi-value {
|
|
118
|
+
font-family: var(--font-mono);
|
|
119
|
+
font-weight: 500;
|
|
120
|
+
color: var(--text-primary);
|
|
121
|
+
font-size: 13px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.kpi-value.health-good { color: var(--accent); }
|
|
125
|
+
.kpi-value.health-warn { color: var(--warning); }
|
|
126
|
+
.kpi-value.health-bad { color: var(--danger); }
|
|
127
|
+
|
|
128
|
+
.live-dot {
|
|
129
|
+
width: 8px;
|
|
130
|
+
height: 8px;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
background: var(--accent);
|
|
133
|
+
animation: pulse 2s ease-in-out infinite;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.live-dot.disconnected {
|
|
137
|
+
background: var(--danger);
|
|
138
|
+
animation: none;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@keyframes pulse {
|
|
142
|
+
0%, 100% { opacity: 1; }
|
|
143
|
+
50% { opacity: 0.4; }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* \u2500\u2500 Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
147
|
+
.container {
|
|
148
|
+
max-width: 1440px;
|
|
149
|
+
margin: 0 auto;
|
|
150
|
+
padding: 20px 24px;
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
gap: 16px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.panel {
|
|
157
|
+
background: var(--bg-panel);
|
|
158
|
+
border: 1px solid var(--border);
|
|
159
|
+
border-radius: 8px;
|
|
160
|
+
padding: 16px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.panel-title {
|
|
164
|
+
font-size: 13px;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
color: var(--text-secondary);
|
|
167
|
+
text-transform: uppercase;
|
|
168
|
+
letter-spacing: 0.05em;
|
|
169
|
+
margin-bottom: 12px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* \u2500\u2500 Charts grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
173
|
+
.charts-grid {
|
|
174
|
+
display: grid;
|
|
175
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
176
|
+
gap: 16px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.chart-box {
|
|
180
|
+
background: var(--bg-panel);
|
|
181
|
+
border: 1px solid var(--border);
|
|
182
|
+
border-radius: 8px;
|
|
183
|
+
padding: 16px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.chart-box canvas { width: 100% !important; height: 160px !important; }
|
|
187
|
+
|
|
188
|
+
.chart-label {
|
|
189
|
+
font-size: 12px;
|
|
190
|
+
color: var(--text-muted);
|
|
191
|
+
font-weight: 500;
|
|
192
|
+
margin-bottom: 8px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.chart-value {
|
|
196
|
+
font-family: var(--font-mono);
|
|
197
|
+
font-size: 22px;
|
|
198
|
+
font-weight: 500;
|
|
199
|
+
color: var(--text-primary);
|
|
200
|
+
margin-bottom: 8px;
|
|
201
|
+
}
|
|
202
|
+
|
|
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
|
+
.terminal {
|
|
205
|
+
background: var(--bg-root);
|
|
206
|
+
border: 1px solid var(--border);
|
|
207
|
+
border-radius: 8px;
|
|
208
|
+
height: 380px;
|
|
209
|
+
overflow-y: auto;
|
|
210
|
+
font-family: var(--font-mono);
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
line-height: 1.7;
|
|
213
|
+
padding: 12px 16px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.terminal::-webkit-scrollbar { width: 6px; }
|
|
217
|
+
.terminal::-webkit-scrollbar-track { background: transparent; }
|
|
218
|
+
.terminal::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 3px; }
|
|
219
|
+
|
|
220
|
+
.term-line {
|
|
221
|
+
white-space: nowrap;
|
|
222
|
+
opacity: 0;
|
|
223
|
+
transform: translateY(4px);
|
|
224
|
+
animation: termIn 0.3s ease-out forwards;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@keyframes termIn {
|
|
228
|
+
to { opacity: 1; transform: translateY(0); }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.t-tick { color: var(--text-dim); }
|
|
232
|
+
.t-ok { color: var(--accent); }
|
|
233
|
+
.t-skip { color: var(--warning); }
|
|
234
|
+
.t-fail { color: var(--danger); }
|
|
235
|
+
.t-principle { color: var(--text-primary); font-weight: 500; }
|
|
236
|
+
.t-param { color: var(--text-secondary); }
|
|
237
|
+
.t-old { color: #d4d4d8; font-variant-numeric: tabular-nums; }
|
|
238
|
+
.t-arrow { color: var(--text-dim); }
|
|
239
|
+
.t-new { color: var(--accent); font-variant-numeric: tabular-nums; }
|
|
240
|
+
.t-meta { color: var(--text-dim); }
|
|
241
|
+
|
|
242
|
+
/* \u2500\u2500 Alerts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
243
|
+
.alerts-container {
|
|
244
|
+
display: flex;
|
|
245
|
+
flex-direction: column;
|
|
246
|
+
gap: 8px;
|
|
247
|
+
max-height: 320px;
|
|
248
|
+
overflow-y: auto;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.alert-card {
|
|
252
|
+
display: flex;
|
|
253
|
+
align-items: flex-start;
|
|
254
|
+
gap: 12px;
|
|
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;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.alert-card.fade-out {
|
|
263
|
+
opacity: 0;
|
|
264
|
+
transform: translateX(20px);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.alert-severity {
|
|
268
|
+
font-family: var(--font-mono);
|
|
269
|
+
font-weight: 600;
|
|
270
|
+
font-size: 13px;
|
|
271
|
+
padding: 2px 8px;
|
|
272
|
+
border-radius: 4px;
|
|
273
|
+
white-space: nowrap;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.sev-high { background: var(--danger-dim); color: var(--danger); }
|
|
277
|
+
.sev-med { background: var(--warning-dim); color: var(--warning); }
|
|
278
|
+
.sev-low { background: var(--accent-dim); color: var(--accent); }
|
|
279
|
+
|
|
280
|
+
.alert-body { flex: 1; }
|
|
281
|
+
.alert-principle { font-weight: 500; font-size: 13px; }
|
|
282
|
+
.alert-reason { color: var(--text-secondary); font-size: 12px; margin-top: 2px; }
|
|
283
|
+
|
|
284
|
+
/* \u2500\u2500 Violations table \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
285
|
+
.violations-table {
|
|
286
|
+
width: 100%;
|
|
287
|
+
border-collapse: collapse;
|
|
288
|
+
font-size: 12px;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.violations-table th {
|
|
292
|
+
text-align: left;
|
|
293
|
+
color: var(--text-muted);
|
|
294
|
+
font-weight: 500;
|
|
295
|
+
padding: 6px 10px;
|
|
296
|
+
border-bottom: 1px solid var(--border);
|
|
297
|
+
cursor: pointer;
|
|
298
|
+
user-select: none;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.violations-table th:hover { color: var(--text-secondary); }
|
|
302
|
+
|
|
303
|
+
.violations-table td {
|
|
304
|
+
padding: 6px 10px;
|
|
305
|
+
border-bottom: 1px solid var(--border);
|
|
306
|
+
color: var(--text-secondary);
|
|
307
|
+
font-family: var(--font-mono);
|
|
308
|
+
font-size: 11px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.violations-table tr:hover td { background: var(--bg-panel-hover); }
|
|
312
|
+
|
|
313
|
+
/* \u2500\u2500 Split row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
314
|
+
.split-row {
|
|
315
|
+
display: grid;
|
|
316
|
+
grid-template-columns: 1fr 1fr;
|
|
317
|
+
gap: 16px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
@media (max-width: 800px) {
|
|
321
|
+
.split-row { grid-template-columns: 1fr; }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* \u2500\u2500 Persona bar chart \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
325
|
+
.persona-bars { display: flex; flex-direction: column; gap: 6px; }
|
|
326
|
+
|
|
327
|
+
.persona-row {
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 8px;
|
|
331
|
+
font-size: 12px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.persona-label {
|
|
335
|
+
width: 100px;
|
|
336
|
+
text-align: right;
|
|
337
|
+
color: var(--text-secondary);
|
|
338
|
+
font-size: 11px;
|
|
339
|
+
flex-shrink: 0;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.persona-bar-track {
|
|
343
|
+
flex: 1;
|
|
344
|
+
height: 16px;
|
|
345
|
+
background: var(--bg-root);
|
|
346
|
+
border-radius: 3px;
|
|
347
|
+
overflow: hidden;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.persona-bar-fill {
|
|
351
|
+
height: 100%;
|
|
352
|
+
background: var(--accent);
|
|
353
|
+
border-radius: 3px;
|
|
354
|
+
transition: width 0.5s ease;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.persona-pct {
|
|
358
|
+
width: 40px;
|
|
359
|
+
font-family: var(--font-mono);
|
|
360
|
+
font-size: 11px;
|
|
361
|
+
color: var(--text-muted);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* \u2500\u2500 Registry list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
365
|
+
.registry-list { display: flex; flex-direction: column; gap: 4px; }
|
|
366
|
+
|
|
367
|
+
.registry-item {
|
|
368
|
+
display: flex;
|
|
369
|
+
justify-content: space-between;
|
|
370
|
+
align-items: center;
|
|
371
|
+
padding: 6px 10px;
|
|
372
|
+
border-radius: 4px;
|
|
373
|
+
font-size: 12px;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.registry-item:nth-child(odd) { background: rgba(255,255,255,0.02); }
|
|
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; }
|
|
379
|
+
|
|
380
|
+
/* \u2500\u2500 Advisor mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
381
|
+
.advisor-banner {
|
|
382
|
+
display: none;
|
|
383
|
+
background: var(--warning-dim);
|
|
384
|
+
border: 1px solid var(--warning);
|
|
385
|
+
color: var(--warning);
|
|
386
|
+
padding: 8px 16px;
|
|
387
|
+
border-radius: 6px;
|
|
388
|
+
font-size: 13px;
|
|
389
|
+
font-weight: 500;
|
|
390
|
+
align-items: center;
|
|
391
|
+
gap: 8px;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.advisor-mode .advisor-banner { display: flex; }
|
|
395
|
+
|
|
396
|
+
.pending-pill {
|
|
397
|
+
background: var(--warning);
|
|
398
|
+
color: var(--bg-root);
|
|
399
|
+
font-size: 11px;
|
|
400
|
+
font-weight: 600;
|
|
401
|
+
padding: 1px 8px;
|
|
402
|
+
border-radius: 10px;
|
|
403
|
+
font-family: var(--font-mono);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.advisor-btn {
|
|
407
|
+
font-family: var(--font-mono);
|
|
408
|
+
font-size: 11px;
|
|
409
|
+
padding: 2px 10px;
|
|
410
|
+
border-radius: 4px;
|
|
411
|
+
border: none;
|
|
412
|
+
cursor: pointer;
|
|
413
|
+
font-weight: 500;
|
|
414
|
+
transition: opacity 0.15s;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.advisor-btn:hover { opacity: 0.85; }
|
|
418
|
+
.advisor-btn.approve { background: var(--accent); color: var(--bg-root); }
|
|
419
|
+
.advisor-btn.reject { background: var(--danger); color: #fff; }
|
|
420
|
+
|
|
421
|
+
.advisor-actions { display: none; gap: 6px; margin-left: 8px; }
|
|
422
|
+
.advisor-mode .advisor-actions { display: inline-flex; }
|
|
423
|
+
|
|
424
|
+
/* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
425
|
+
.empty-state {
|
|
426
|
+
color: var(--text-dim);
|
|
427
|
+
font-size: 13px;
|
|
428
|
+
text-align: center;
|
|
429
|
+
padding: 40px 20px;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* \u2500\u2500 Reduced motion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
433
|
+
@media (prefers-reduced-motion: reduce) {
|
|
434
|
+
.term-line { animation: none; opacity: 1; transform: none; }
|
|
435
|
+
.live-dot { animation: none; }
|
|
436
|
+
.persona-bar-fill { transition: none; }
|
|
437
|
+
}
|
|
438
|
+
</style>
|
|
439
|
+
</head>
|
|
440
|
+
<body>
|
|
441
|
+
|
|
442
|
+
<!-- Header -->
|
|
443
|
+
<div class="header" id="header">
|
|
444
|
+
<div class="header-brand">Agent<span>E</span> v1.6</div>
|
|
445
|
+
<div class="kpi-row">
|
|
446
|
+
<div class="kpi">Health <span class="kpi-value health-good" id="kpi-health">--</span></div>
|
|
447
|
+
<div class="kpi">Mode <span class="kpi-value" id="kpi-mode">--</span></div>
|
|
448
|
+
<div class="kpi">Tick <span class="kpi-value" id="kpi-tick">0</span></div>
|
|
449
|
+
<div class="kpi">Uptime <span class="kpi-value" id="kpi-uptime">0s</span></div>
|
|
450
|
+
<div class="kpi">Plans <span class="kpi-value" id="kpi-plans">0</span></div>
|
|
451
|
+
<div class="live-dot" id="live-dot" title="WebSocket connected"></div>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div class="container" id="app">
|
|
456
|
+
<!-- Advisor banner -->
|
|
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>
|
|
461
|
+
|
|
462
|
+
<!-- Charts -->
|
|
463
|
+
<div class="charts-grid">
|
|
464
|
+
<div class="chart-box">
|
|
465
|
+
<div class="chart-label">Economy Health</div>
|
|
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>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="chart-box">
|
|
480
|
+
<div class="chart-label">Avg Satisfaction</div>
|
|
481
|
+
<div class="chart-value" id="cv-satisfaction">--</div>
|
|
482
|
+
<canvas id="chart-satisfaction"></canvas>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<!-- Decision Feed -->
|
|
487
|
+
<div class="panel">
|
|
488
|
+
<div class="panel-title">Decision Feed</div>
|
|
489
|
+
<div class="terminal" id="terminal"></div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<!-- Active Alerts -->
|
|
493
|
+
<div class="panel">
|
|
494
|
+
<div class="panel-title">Active Alerts</div>
|
|
495
|
+
<div class="alerts-container" id="alerts-container">
|
|
496
|
+
<div class="empty-state" id="alerts-empty">No active violations</div>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
|
|
500
|
+
<!-- Violation History -->
|
|
501
|
+
<div class="panel">
|
|
502
|
+
<div class="panel-title">Violation History</div>
|
|
503
|
+
<div style="max-height:320px;overflow-y:auto">
|
|
504
|
+
<table class="violations-table" id="violations-table">
|
|
505
|
+
<thead>
|
|
506
|
+
<tr>
|
|
507
|
+
<th data-sort="tick">Tick</th>
|
|
508
|
+
<th data-sort="principle">Principle</th>
|
|
509
|
+
<th data-sort="severity">Severity</th>
|
|
510
|
+
<th data-sort="parameter">Parameter</th>
|
|
511
|
+
<th data-sort="result">Result</th>
|
|
512
|
+
</tr>
|
|
513
|
+
</thead>
|
|
514
|
+
<tbody id="violations-body"></tbody>
|
|
515
|
+
</table>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<!-- Split: Personas + Registry -->
|
|
520
|
+
<div class="split-row">
|
|
521
|
+
<div class="panel">
|
|
522
|
+
<div class="panel-title">Persona Distribution</div>
|
|
523
|
+
<div class="persona-bars" id="persona-bars">
|
|
524
|
+
<div class="empty-state">No persona data yet</div>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="panel">
|
|
528
|
+
<div class="panel-title">Parameter Registry</div>
|
|
529
|
+
<div class="registry-list" id="registry-list">
|
|
530
|
+
<div class="empty-state">No parameters registered</div>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
|
|
536
|
+
<script>
|
|
537
|
+
(function() {
|
|
538
|
+
'use strict';
|
|
539
|
+
|
|
540
|
+
// \u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
541
|
+
let ws = null;
|
|
542
|
+
let reconnectDelay = 1000;
|
|
543
|
+
const MAX_RECONNECT = 30000;
|
|
544
|
+
let isAdvisor = false;
|
|
545
|
+
let pendingDecisions = [];
|
|
546
|
+
const MAX_TERMINAL_LINES = 80;
|
|
547
|
+
const MAX_VIOLATIONS = 100;
|
|
548
|
+
let violationSortKey = 'tick';
|
|
549
|
+
let violationSortAsc = false;
|
|
550
|
+
let violations = [];
|
|
551
|
+
|
|
552
|
+
// Chart instances
|
|
553
|
+
let chartHealth, chartGini, chartNetflow, chartSatisfaction;
|
|
554
|
+
|
|
555
|
+
// \u2500\u2500 DOM refs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
556
|
+
const $kpiHealth = document.getElementById('kpi-health');
|
|
557
|
+
const $kpiMode = document.getElementById('kpi-mode');
|
|
558
|
+
const $kpiTick = document.getElementById('kpi-tick');
|
|
559
|
+
const $kpiUptime = document.getElementById('kpi-uptime');
|
|
560
|
+
const $kpiPlans = document.getElementById('kpi-plans');
|
|
561
|
+
const $liveDot = document.getElementById('live-dot');
|
|
562
|
+
const $terminal = document.getElementById('terminal');
|
|
563
|
+
const $alertsContainer = document.getElementById('alerts-container');
|
|
564
|
+
const $alertsEmpty = document.getElementById('alerts-empty');
|
|
565
|
+
const $violationsBody = document.getElementById('violations-body');
|
|
566
|
+
const $personaBars = document.getElementById('persona-bars');
|
|
567
|
+
const $registryList = document.getElementById('registry-list');
|
|
568
|
+
const $pendingCount = document.getElementById('pending-count');
|
|
569
|
+
const $app = document.getElementById('app');
|
|
570
|
+
|
|
571
|
+
// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
572
|
+
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
|
|
573
|
+
function pad(n, w) { return String(n).padStart(w || 4, ' '); }
|
|
574
|
+
function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\u2014'; }
|
|
575
|
+
function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '\u2014'; }
|
|
576
|
+
|
|
577
|
+
function formatUptime(ms) {
|
|
578
|
+
const s = Math.floor(ms / 1000);
|
|
579
|
+
if (s < 60) return s + 's';
|
|
580
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';
|
|
581
|
+
const h = Math.floor(s / 3600);
|
|
582
|
+
return h + 'h ' + Math.floor((s % 3600) / 60) + 'm';
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function healthClass(h) {
|
|
586
|
+
if (h >= 70) return 'health-good';
|
|
587
|
+
if (h >= 40) return 'health-warn';
|
|
588
|
+
return 'health-bad';
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function sevClass(s) {
|
|
592
|
+
if (s >= 7) return 'sev-high';
|
|
593
|
+
if (s >= 4) return 'sev-med';
|
|
594
|
+
return 'sev-low';
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// \u2500\u2500 Chart setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
598
|
+
const chartOpts = {
|
|
599
|
+
responsive: true,
|
|
600
|
+
maintainAspectRatio: false,
|
|
601
|
+
animation: { duration: 300 },
|
|
602
|
+
plugins: { legend: { display: false } },
|
|
603
|
+
scales: {
|
|
604
|
+
x: { display: false },
|
|
605
|
+
y: {
|
|
606
|
+
ticks: { color: '#71717a', font: { family: "'JetBrains Mono'", size: 10 } },
|
|
607
|
+
grid: { color: 'rgba(63,63,70,0.3)' },
|
|
608
|
+
border: { display: false },
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
elements: {
|
|
612
|
+
point: { radius: 0 },
|
|
613
|
+
line: { borderWidth: 1.5, tension: 0.3 },
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
function makeChart(id, color, minY, maxY) {
|
|
618
|
+
const ctx = document.getElementById(id).getContext('2d');
|
|
619
|
+
const opts = JSON.parse(JSON.stringify(chartOpts));
|
|
620
|
+
if (minY !== undefined) opts.scales.y.min = minY;
|
|
621
|
+
if (maxY !== undefined) opts.scales.y.max = maxY;
|
|
622
|
+
return new Chart(ctx, {
|
|
623
|
+
type: 'line',
|
|
624
|
+
data: {
|
|
625
|
+
labels: [],
|
|
626
|
+
datasets: [{
|
|
627
|
+
data: [],
|
|
628
|
+
borderColor: color,
|
|
629
|
+
backgroundColor: color + '18',
|
|
630
|
+
fill: true,
|
|
631
|
+
}]
|
|
632
|
+
},
|
|
633
|
+
options: opts,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function initCharts() {
|
|
638
|
+
chartHealth = makeChart('chart-health', '#22c55e', 0, 100);
|
|
639
|
+
chartGini = makeChart('chart-gini', '#eab308', 0, 1);
|
|
640
|
+
chartNetflow = makeChart('chart-netflow', '#3b82f6');
|
|
641
|
+
chartSatisfaction = makeChart('chart-satisfaction', '#22c55e', 0, 100);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function updateChart(chart, labels, data) {
|
|
645
|
+
chart.data.labels = labels;
|
|
646
|
+
chart.data.datasets[0].data = data;
|
|
647
|
+
chart.update('none');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// \u2500\u2500 Terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
651
|
+
function addTerminalLine(html) {
|
|
652
|
+
const el = document.createElement('div');
|
|
653
|
+
el.className = 'term-line';
|
|
654
|
+
el.innerHTML = html;
|
|
655
|
+
$terminal.appendChild(el);
|
|
656
|
+
while ($terminal.children.length > MAX_TERMINAL_LINES) {
|
|
657
|
+
$terminal.removeChild($terminal.firstChild);
|
|
658
|
+
}
|
|
659
|
+
$terminal.scrollTop = $terminal.scrollHeight;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function decisionToTerminal(d) {
|
|
663
|
+
const resultIcon = d.result === 'applied'
|
|
664
|
+
? '<span class="t-ok">\\u2705 </span>'
|
|
665
|
+
: d.result === 'rejected'
|
|
666
|
+
? '<span class="t-fail">\\u274c </span>'
|
|
667
|
+
: '<span class="t-skip">\\u23f8 </span>';
|
|
668
|
+
|
|
669
|
+
const principle = d.diagnosis?.principle || {};
|
|
670
|
+
const plan = d.plan || {};
|
|
671
|
+
const severity = d.diagnosis?.violation?.severity ?? '?';
|
|
672
|
+
const confidence = d.diagnosis?.violation?.confidence;
|
|
673
|
+
const confStr = confidence != null ? (confidence * 100).toFixed(0) + '%' : '?';
|
|
674
|
+
|
|
675
|
+
let advisorBtns = '';
|
|
676
|
+
if (isAdvisor && d.result === 'skipped_override') {
|
|
677
|
+
advisorBtns = '<span class="advisor-actions">'
|
|
678
|
+
+ '<button class="advisor-btn approve" onclick="window._approve(\\'' + esc(d.id) + '\\')">[Approve]</button>'
|
|
679
|
+
+ '<button class="advisor-btn reject" onclick="window._reject(\\'' + esc(d.id) + '\\')">[Reject]</button>'
|
|
680
|
+
+ '</span>';
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return '<span class="t-tick">[Tick ' + pad(d.tick) + ']</span> '
|
|
684
|
+
+ resultIcon
|
|
685
|
+
+ '<span class="t-principle">[' + esc(principle.id || '?') + '] ' + esc(principle.name || '') + ':</span> '
|
|
686
|
+
+ '<span class="t-param">' + esc(plan.parameter || '\u2014') + ' </span>'
|
|
687
|
+
+ '<span class="t-old">' + fmt(plan.currentValue) + '</span>'
|
|
688
|
+
+ '<span class="t-arrow"> \\u2192 </span>'
|
|
689
|
+
+ '<span class="t-new">' + fmt(plan.targetValue) + '</span>'
|
|
690
|
+
+ '<span class="t-meta"> severity ' + severity + '/10, confidence ' + confStr + '</span>'
|
|
691
|
+
+ advisorBtns;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// \u2500\u2500 Alerts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
695
|
+
function renderAlerts(alerts) {
|
|
696
|
+
if (!alerts || alerts.length === 0) {
|
|
697
|
+
$alertsContainer.innerHTML = '<div class="empty-state">No active violations</div>';
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const sorted = [...alerts].sort((a, b) => (b.severity || 0) - (a.severity || 0));
|
|
701
|
+
$alertsContainer.innerHTML = sorted.map(function(a) {
|
|
702
|
+
const sev = a.severity || a.violation?.severity || 0;
|
|
703
|
+
const sc = sevClass(sev);
|
|
704
|
+
const name = a.principleName || a.principle?.name || '?';
|
|
705
|
+
const pid = a.principleId || a.principle?.id || '?';
|
|
706
|
+
const reason = a.reasoning || a.violation?.suggestedAction?.reasoning || '';
|
|
707
|
+
return '<div class="alert-card">'
|
|
708
|
+
+ '<span class="alert-severity ' + sc + '">' + sev + '/10</span>'
|
|
709
|
+
+ '<div class="alert-body">'
|
|
710
|
+
+ '<div class="alert-principle">[' + esc(pid) + '] ' + esc(name) + '</div>'
|
|
711
|
+
+ '<div class="alert-reason">' + esc(reason) + '</div>'
|
|
712
|
+
+ '</div></div>';
|
|
713
|
+
}).join('');
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// \u2500\u2500 Violations table \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
717
|
+
function addViolation(d) {
|
|
718
|
+
violations.push({
|
|
719
|
+
tick: d.tick,
|
|
720
|
+
principle: (d.diagnosis?.principle?.id || '?') + ' ' + (d.diagnosis?.principle?.name || ''),
|
|
721
|
+
severity: d.diagnosis?.violation?.severity || 0,
|
|
722
|
+
parameter: d.plan?.parameter || '\u2014',
|
|
723
|
+
result: d.result,
|
|
724
|
+
});
|
|
725
|
+
if (violations.length > MAX_VIOLATIONS) violations.shift();
|
|
726
|
+
renderViolations();
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function renderViolations() {
|
|
730
|
+
const sorted = [...violations].sort(function(a, b) {
|
|
731
|
+
const va = a[violationSortKey], vb = b[violationSortKey];
|
|
732
|
+
if (va < vb) return violationSortAsc ? -1 : 1;
|
|
733
|
+
if (va > vb) return violationSortAsc ? 1 : -1;
|
|
734
|
+
return 0;
|
|
735
|
+
});
|
|
736
|
+
$violationsBody.innerHTML = sorted.map(function(v) {
|
|
737
|
+
return '<tr>'
|
|
738
|
+
+ '<td>' + v.tick + '</td>'
|
|
739
|
+
+ '<td style="color:var(--text-primary);font-family:var(--font-sans)">' + esc(v.principle) + '</td>'
|
|
740
|
+
+ '<td><span class="alert-severity ' + sevClass(v.severity) + '">' + v.severity + '</span></td>'
|
|
741
|
+
+ '<td>' + esc(v.parameter) + '</td>'
|
|
742
|
+
+ '<td>' + esc(v.result) + '</td>'
|
|
743
|
+
+ '</tr>';
|
|
744
|
+
}).join('');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Table sorting
|
|
748
|
+
document.querySelectorAll('.violations-table th').forEach(function(th) {
|
|
749
|
+
th.addEventListener('click', function() {
|
|
750
|
+
const key = th.dataset.sort;
|
|
751
|
+
if (violationSortKey === key) violationSortAsc = !violationSortAsc;
|
|
752
|
+
else { violationSortKey = key; violationSortAsc = true; }
|
|
753
|
+
renderViolations();
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
// \u2500\u2500 Personas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
758
|
+
function renderPersonas(dist) {
|
|
759
|
+
if (!dist || Object.keys(dist).length === 0) {
|
|
760
|
+
$personaBars.innerHTML = '<div class="empty-state">No persona data yet</div>';
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const total = Object.values(dist).reduce(function(s, v) { return s + v; }, 0);
|
|
764
|
+
const entries = Object.entries(dist).sort(function(a, b) { return b[1] - a[1]; });
|
|
765
|
+
$personaBars.innerHTML = entries.map(function(e) {
|
|
766
|
+
const pctVal = total > 0 ? (e[1] / total * 100) : 0;
|
|
767
|
+
return '<div class="persona-row">'
|
|
768
|
+
+ '<div class="persona-label">' + esc(e[0]) + '</div>'
|
|
769
|
+
+ '<div class="persona-bar-track"><div class="persona-bar-fill" style="width:' + pctVal + '%"></div></div>'
|
|
770
|
+
+ '<div class="persona-pct">' + pctVal.toFixed(0) + '%</div>'
|
|
771
|
+
+ '</div>';
|
|
772
|
+
}).join('');
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// \u2500\u2500 Registry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
776
|
+
function renderRegistry(principles) {
|
|
777
|
+
if (!principles || principles.length === 0) {
|
|
778
|
+
$registryList.innerHTML = '<div class="empty-state">No parameters registered</div>';
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
$registryList.innerHTML = principles.slice(0, 30).map(function(p) {
|
|
782
|
+
return '<div class="registry-item">'
|
|
783
|
+
+ '<span class="registry-key">[' + esc(p.id) + ']</span>'
|
|
784
|
+
+ '<span class="registry-val">' + esc(p.name) + '</span>'
|
|
785
|
+
+ '</div>';
|
|
786
|
+
}).join('');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// \u2500\u2500 KPI update \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
790
|
+
function updateKPIs(data) {
|
|
791
|
+
if (data.health != null) {
|
|
792
|
+
$kpiHealth.textContent = data.health + '/100';
|
|
793
|
+
$kpiHealth.className = 'kpi-value ' + healthClass(data.health);
|
|
794
|
+
document.getElementById('cv-health').textContent = data.health + '/100';
|
|
795
|
+
}
|
|
796
|
+
if (data.mode != null) {
|
|
797
|
+
$kpiMode.textContent = data.mode;
|
|
798
|
+
isAdvisor = data.mode === 'advisor';
|
|
799
|
+
$app.classList.toggle('advisor-mode', isAdvisor);
|
|
800
|
+
}
|
|
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
|
+
}
|
|
805
|
+
|
|
806
|
+
// \u2500\u2500 Metrics history \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
807
|
+
function updateChartsFromHistory(history) {
|
|
808
|
+
if (!history || history.length === 0) return;
|
|
809
|
+
const ticks = history.map(function(h) { return h.tick; });
|
|
810
|
+
updateChart(chartHealth, ticks, history.map(function(h) { return h.health; }));
|
|
811
|
+
updateChart(chartGini, ticks, history.map(function(h) { return h.giniCoefficient; }));
|
|
812
|
+
updateChart(chartNetflow, ticks, history.map(function(h) { return h.netFlow; }));
|
|
813
|
+
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
|
+
}
|
|
820
|
+
|
|
821
|
+
// \u2500\u2500 API calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
822
|
+
function fetchJSON(path) {
|
|
823
|
+
return fetch(path).then(function(r) { return r.json(); });
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function postJSON(path, body) {
|
|
827
|
+
return fetch(path, {
|
|
828
|
+
method: 'POST',
|
|
829
|
+
headers: { 'Content-Type': 'application/json' },
|
|
830
|
+
body: JSON.stringify(body),
|
|
831
|
+
}).then(function(r) { return r.json(); });
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function loadInitialData() {
|
|
835
|
+
fetchJSON('/health').then(function(data) {
|
|
836
|
+
updateKPIs(data);
|
|
837
|
+
}).catch(function() {});
|
|
838
|
+
|
|
839
|
+
fetchJSON('/decisions?limit=50').then(function(data) {
|
|
840
|
+
if (data.decisions) {
|
|
841
|
+
data.decisions.reverse().forEach(function(d) {
|
|
842
|
+
addTerminalLine(decisionToTerminal(d));
|
|
843
|
+
addViolation(d);
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}).catch(function() {});
|
|
847
|
+
|
|
848
|
+
fetchJSON('/metrics').then(function(data) {
|
|
849
|
+
if (data.history) updateChartsFromHistory(data.history);
|
|
850
|
+
if (data.latest) {
|
|
851
|
+
renderPersonas(data.latest.personaDistribution);
|
|
852
|
+
}
|
|
853
|
+
}).catch(function() {});
|
|
854
|
+
|
|
855
|
+
fetchJSON('/principles').then(function(data) {
|
|
856
|
+
if (data.principles) renderRegistry(data.principles);
|
|
857
|
+
}).catch(function() {});
|
|
858
|
+
|
|
859
|
+
fetchJSON('/pending').then(function(data) {
|
|
860
|
+
if (data.pending) {
|
|
861
|
+
pendingDecisions = data.pending;
|
|
862
|
+
$pendingCount.textContent = data.count || 0;
|
|
863
|
+
}
|
|
864
|
+
}).catch(function() {});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// \u2500\u2500 Polling fallback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
868
|
+
let pollInterval = null;
|
|
869
|
+
|
|
870
|
+
function startPolling() {
|
|
871
|
+
if (pollInterval) return;
|
|
872
|
+
pollInterval = setInterval(function() {
|
|
873
|
+
fetchJSON('/health').then(updateKPIs).catch(function() {});
|
|
874
|
+
fetchJSON('/metrics').then(function(data) {
|
|
875
|
+
if (data.history) updateChartsFromHistory(data.history);
|
|
876
|
+
if (data.latest) renderPersonas(data.latest.personaDistribution);
|
|
877
|
+
}).catch(function() {});
|
|
878
|
+
}, 5000);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function stopPolling() {
|
|
882
|
+
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// \u2500\u2500 WebSocket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
886
|
+
function connectWS() {
|
|
887
|
+
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
888
|
+
ws = new WebSocket(proto + '//' + location.host);
|
|
889
|
+
|
|
890
|
+
ws.onopen = function() {
|
|
891
|
+
reconnectDelay = 1000;
|
|
892
|
+
$liveDot.classList.remove('disconnected');
|
|
893
|
+
$liveDot.title = 'WebSocket connected';
|
|
894
|
+
stopPolling();
|
|
895
|
+
// Request fresh health
|
|
896
|
+
ws.send(JSON.stringify({ type: 'health' }));
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
ws.onclose = function() {
|
|
900
|
+
$liveDot.classList.add('disconnected');
|
|
901
|
+
$liveDot.title = 'WebSocket disconnected \u2014 reconnecting...';
|
|
902
|
+
startPolling();
|
|
903
|
+
setTimeout(connectWS, reconnectDelay);
|
|
904
|
+
reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT);
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
ws.onerror = function() { ws.close(); };
|
|
908
|
+
|
|
909
|
+
ws.onmessage = function(ev) {
|
|
910
|
+
var msg;
|
|
911
|
+
try { msg = JSON.parse(ev.data); } catch(e) { return; }
|
|
912
|
+
|
|
913
|
+
switch (msg.type) {
|
|
914
|
+
case 'tick_result':
|
|
915
|
+
updateKPIs({ health: msg.health, tick: msg.tick });
|
|
916
|
+
if (msg.alerts) renderAlerts(msg.alerts);
|
|
917
|
+
// Refresh charts
|
|
918
|
+
fetchJSON('/metrics').then(function(data) {
|
|
919
|
+
if (data.history) updateChartsFromHistory(data.history);
|
|
920
|
+
if (data.latest) renderPersonas(data.latest.personaDistribution);
|
|
921
|
+
}).catch(function() {});
|
|
922
|
+
break;
|
|
923
|
+
|
|
924
|
+
case 'health_result':
|
|
925
|
+
updateKPIs(msg);
|
|
926
|
+
break;
|
|
927
|
+
|
|
928
|
+
case 'advisor_action':
|
|
929
|
+
if (msg.action === 'approved' || msg.action === 'rejected') {
|
|
930
|
+
pendingDecisions = pendingDecisions.filter(function(d) {
|
|
931
|
+
return d.id !== msg.decisionId;
|
|
932
|
+
});
|
|
933
|
+
$pendingCount.textContent = pendingDecisions.length;
|
|
934
|
+
}
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// \u2500\u2500 Advisor actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
941
|
+
window._approve = function(id) {
|
|
942
|
+
postJSON('/approve', { decisionId: id }).then(function(data) {
|
|
943
|
+
if (data.ok) {
|
|
944
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-ok">\\u2705 Approved ' + id + '</span>');
|
|
945
|
+
}
|
|
946
|
+
}).catch(function() {});
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
window._reject = function(id) {
|
|
950
|
+
var reason = prompt('Rejection reason (optional):');
|
|
951
|
+
postJSON('/reject', { decisionId: id, reason: reason || undefined }).then(function(data) {
|
|
952
|
+
if (data.ok) {
|
|
953
|
+
addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-fail">\\u274c Rejected ' + id + '</span>');
|
|
954
|
+
}
|
|
955
|
+
}).catch(function() {});
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
959
|
+
initCharts();
|
|
960
|
+
loadInitialData();
|
|
961
|
+
connectWS();
|
|
962
|
+
|
|
963
|
+
})();
|
|
964
|
+
</script>
|
|
965
|
+
</body>
|
|
966
|
+
</html>`;
|
|
967
|
+
}
|
|
968
|
+
var init_dashboard = __esm({
|
|
969
|
+
"src/dashboard.ts"() {
|
|
970
|
+
"use strict";
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
33
974
|
// src/routes.ts
|
|
975
|
+
function setSecurityHeaders(res) {
|
|
976
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
977
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
978
|
+
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
979
|
+
}
|
|
34
980
|
function setCorsHeaders(res, origin) {
|
|
981
|
+
setSecurityHeaders(res);
|
|
35
982
|
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
36
983
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
37
984
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
@@ -87,21 +1034,19 @@ function createRouteHandler(server) {
|
|
|
87
1034
|
const payload = parsed;
|
|
88
1035
|
const state = payload["state"] ?? parsed;
|
|
89
1036
|
const events = payload["events"];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
1037
|
+
const validation = server.validateState ? (0, import_core.validateEconomyState)(state) : null;
|
|
1038
|
+
if (validation && !validation.valid) {
|
|
1039
|
+
json(res, 400, {
|
|
1040
|
+
error: "invalid_state",
|
|
1041
|
+
validationErrors: validation.errors
|
|
1042
|
+
}, cors);
|
|
1043
|
+
return;
|
|
99
1044
|
}
|
|
100
1045
|
const result = await server.processTick(
|
|
101
1046
|
state,
|
|
102
1047
|
Array.isArray(events) ? events : void 0
|
|
103
1048
|
);
|
|
104
|
-
const warnings =
|
|
1049
|
+
const warnings = validation?.warnings ?? [];
|
|
105
1050
|
json(res, 200, {
|
|
106
1051
|
adjustments: result.adjustments,
|
|
107
1052
|
alerts: result.alerts.map((a) => ({
|
|
@@ -129,12 +1074,18 @@ function createRouteHandler(server) {
|
|
|
129
1074
|
return;
|
|
130
1075
|
}
|
|
131
1076
|
if (path === "/decisions" && method === "GET") {
|
|
132
|
-
const
|
|
133
|
-
const
|
|
1077
|
+
const rawLimit = parseInt(url.searchParams.get("limit") ?? "100", 10);
|
|
1078
|
+
const limit = Math.min(Math.max(isNaN(rawLimit) ? 100 : rawLimit, 1), 1e3);
|
|
1079
|
+
const sinceParam = url.searchParams.get("since");
|
|
134
1080
|
const agentE = server.getAgentE();
|
|
135
1081
|
let decisions;
|
|
136
|
-
if (
|
|
137
|
-
|
|
1082
|
+
if (sinceParam) {
|
|
1083
|
+
const since = parseInt(sinceParam, 10);
|
|
1084
|
+
if (isNaN(since)) {
|
|
1085
|
+
json(res, 400, { error: 'Invalid "since" parameter \u2014 must be a number' }, cors);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
decisions = agentE.getDecisions({ since });
|
|
138
1089
|
} else {
|
|
139
1090
|
decisions = agentE.log.latest(limit);
|
|
140
1091
|
}
|
|
@@ -165,6 +1116,14 @@ function createRouteHandler(server) {
|
|
|
165
1116
|
for (const c of config["constrain"]) {
|
|
166
1117
|
if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
|
|
167
1118
|
const constraint = c;
|
|
1119
|
+
if (!isFinite(constraint.min) || !isFinite(constraint.max)) {
|
|
1120
|
+
json(res, 400, { error: "Constraint bounds must be finite numbers" }, cors);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
if (constraint.min > constraint.max) {
|
|
1124
|
+
json(res, 400, { error: "Constraint min cannot exceed max" }, cors);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
168
1127
|
server.constrain(constraint.param, { min: constraint.min, max: constraint.max });
|
|
169
1128
|
}
|
|
170
1129
|
}
|
|
@@ -219,6 +1178,112 @@ function createRouteHandler(server) {
|
|
|
219
1178
|
}, cors);
|
|
220
1179
|
return;
|
|
221
1180
|
}
|
|
1181
|
+
if (path === "/" && method === "GET" && server.serveDashboard) {
|
|
1182
|
+
setCorsHeaders(res, cors);
|
|
1183
|
+
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self' ws: wss:; img-src 'self' data:");
|
|
1184
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1185
|
+
res.end(getDashboardHtml());
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (path === "/metrics" && method === "GET") {
|
|
1189
|
+
const agentE = server.getAgentE();
|
|
1190
|
+
const latest = agentE.store.latest();
|
|
1191
|
+
const history = agentE.store.recentHistory(100);
|
|
1192
|
+
json(res, 200, { latest, history }, cors);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (path === "/metrics/personas" && method === "GET") {
|
|
1196
|
+
const agentE = server.getAgentE();
|
|
1197
|
+
const latest = agentE.store.latest();
|
|
1198
|
+
const dist = latest.personaDistribution || {};
|
|
1199
|
+
const total = Object.values(dist).reduce((s, v) => s + v, 0);
|
|
1200
|
+
json(res, 200, { distribution: dist, total }, cors);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (path === "/approve" && method === "POST") {
|
|
1204
|
+
const body = await readBody(req);
|
|
1205
|
+
let parsed;
|
|
1206
|
+
try {
|
|
1207
|
+
parsed = JSON.parse(body);
|
|
1208
|
+
} catch {
|
|
1209
|
+
json(res, 400, { error: "Invalid JSON" }, cors);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const payload = parsed;
|
|
1213
|
+
const decisionId = payload["decisionId"];
|
|
1214
|
+
if (!decisionId) {
|
|
1215
|
+
json(res, 400, { error: "missing_decision_id" }, cors);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
const agentE = server.getAgentE();
|
|
1219
|
+
if (agentE.getMode() !== "advisor") {
|
|
1220
|
+
json(res, 400, { error: "not_in_advisor_mode" }, cors);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
const entry = agentE.log.getById(decisionId);
|
|
1224
|
+
if (!entry) {
|
|
1225
|
+
json(res, 404, { error: "decision_not_found" }, cors);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
if (entry.result !== "skipped_override") {
|
|
1229
|
+
json(res, 409, { error: "decision_not_pending", currentResult: entry.result }, cors);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
await agentE.apply(entry.plan);
|
|
1233
|
+
agentE.log.updateResult(decisionId, "applied");
|
|
1234
|
+
server.broadcast({ type: "advisor_action", action: "approved", decisionId });
|
|
1235
|
+
json(res, 200, {
|
|
1236
|
+
ok: true,
|
|
1237
|
+
parameter: entry.plan.parameter,
|
|
1238
|
+
value: entry.plan.targetValue
|
|
1239
|
+
}, cors);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (path === "/reject" && method === "POST") {
|
|
1243
|
+
const body = await readBody(req);
|
|
1244
|
+
let parsed;
|
|
1245
|
+
try {
|
|
1246
|
+
parsed = JSON.parse(body);
|
|
1247
|
+
} catch {
|
|
1248
|
+
json(res, 400, { error: "Invalid JSON" }, cors);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const payload = parsed;
|
|
1252
|
+
const decisionId = payload["decisionId"];
|
|
1253
|
+
const reason = payload["reason"] || void 0;
|
|
1254
|
+
if (!decisionId) {
|
|
1255
|
+
json(res, 400, { error: "missing_decision_id" }, cors);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
const agentE = server.getAgentE();
|
|
1259
|
+
if (agentE.getMode() !== "advisor") {
|
|
1260
|
+
json(res, 400, { error: "not_in_advisor_mode" }, cors);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const entry = agentE.log.getById(decisionId);
|
|
1264
|
+
if (!entry) {
|
|
1265
|
+
json(res, 404, { error: "decision_not_found" }, cors);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (entry.result !== "skipped_override") {
|
|
1269
|
+
json(res, 409, { error: "decision_not_pending", currentResult: entry.result }, cors);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
agentE.log.updateResult(decisionId, "rejected", reason);
|
|
1273
|
+
server.broadcast({ type: "advisor_action", action: "rejected", decisionId, reason });
|
|
1274
|
+
json(res, 200, { ok: true, decisionId }, cors);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (path === "/pending" && method === "GET") {
|
|
1278
|
+
const agentE = server.getAgentE();
|
|
1279
|
+
const pending = agentE.log.query({ result: "skipped_override" });
|
|
1280
|
+
json(res, 200, {
|
|
1281
|
+
mode: agentE.getMode(),
|
|
1282
|
+
pending,
|
|
1283
|
+
count: pending.length
|
|
1284
|
+
}, cors);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
222
1287
|
json(res, 404, { error: "Not found" }, cors);
|
|
223
1288
|
} catch (err) {
|
|
224
1289
|
console.error("[AgentE Server] Unhandled route error:", err);
|
|
@@ -231,6 +1296,7 @@ var init_routes = __esm({
|
|
|
231
1296
|
"src/routes.ts"() {
|
|
232
1297
|
"use strict";
|
|
233
1298
|
import_core = require("@agent-e/core");
|
|
1299
|
+
init_dashboard();
|
|
234
1300
|
MAX_BODY_BYTES = 1048576;
|
|
235
1301
|
}
|
|
236
1302
|
});
|
|
@@ -242,7 +1308,7 @@ function send(ws, data) {
|
|
|
242
1308
|
}
|
|
243
1309
|
}
|
|
244
1310
|
function createWebSocketHandler(httpServer, server) {
|
|
245
|
-
const wss = new import_ws.WebSocketServer({ server: httpServer });
|
|
1311
|
+
const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
|
|
246
1312
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
247
1313
|
const heartbeatInterval = setInterval(() => {
|
|
248
1314
|
for (const ws of wss.clients) {
|
|
@@ -257,6 +1323,10 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
257
1323
|
}
|
|
258
1324
|
}, 3e4);
|
|
259
1325
|
wss.on("connection", (ws) => {
|
|
1326
|
+
if (wss.clients.size > MAX_WS_CONNECTIONS) {
|
|
1327
|
+
ws.close(1013, "Server at capacity");
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
260
1330
|
console.log("[AgentE Server] Client connected");
|
|
261
1331
|
aliveMap.set(ws, true);
|
|
262
1332
|
ws.on("pong", () => {
|
|
@@ -362,17 +1432,30 @@ function createWebSocketHandler(httpServer, server) {
|
|
|
362
1432
|
}
|
|
363
1433
|
});
|
|
364
1434
|
});
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
wss.
|
|
1435
|
+
function broadcast(data) {
|
|
1436
|
+
const payload = JSON.stringify(data);
|
|
1437
|
+
for (const ws of wss.clients) {
|
|
1438
|
+
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
1439
|
+
ws.send(payload);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return {
|
|
1444
|
+
cleanup: () => {
|
|
1445
|
+
clearInterval(heartbeatInterval);
|
|
1446
|
+
wss.close();
|
|
1447
|
+
},
|
|
1448
|
+
broadcast
|
|
368
1449
|
};
|
|
369
1450
|
}
|
|
370
|
-
var import_ws, import_core2;
|
|
1451
|
+
var import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS;
|
|
371
1452
|
var init_websocket = __esm({
|
|
372
1453
|
"src/websocket.ts"() {
|
|
373
1454
|
"use strict";
|
|
374
1455
|
import_ws = require("ws");
|
|
375
1456
|
import_core2 = require("@agent-e/core");
|
|
1457
|
+
MAX_WS_PAYLOAD = 1048576;
|
|
1458
|
+
MAX_WS_CONNECTIONS = 100;
|
|
376
1459
|
}
|
|
377
1460
|
});
|
|
378
1461
|
|
|
@@ -395,11 +1478,12 @@ var init_AgentEServer = __esm({
|
|
|
395
1478
|
this.adjustmentQueue = [];
|
|
396
1479
|
this.alerts = [];
|
|
397
1480
|
this.startedAt = Date.now();
|
|
398
|
-
this.
|
|
1481
|
+
this.wsHandle = null;
|
|
399
1482
|
this.port = config.port ?? 3100;
|
|
400
1483
|
this.host = config.host ?? "0.0.0.0";
|
|
401
1484
|
this.validateState = config.validateState ?? true;
|
|
402
|
-
this.corsOrigin = config.corsOrigin ?? "
|
|
1485
|
+
this.corsOrigin = config.corsOrigin ?? "http://localhost:3100";
|
|
1486
|
+
this.serveDashboard = config.serveDashboard ?? true;
|
|
403
1487
|
const adapter = {
|
|
404
1488
|
getState: () => {
|
|
405
1489
|
if (!this.lastState) {
|
|
@@ -448,7 +1532,7 @@ var init_AgentEServer = __esm({
|
|
|
448
1532
|
this.server = http.createServer(routeHandler);
|
|
449
1533
|
}
|
|
450
1534
|
async start() {
|
|
451
|
-
this.
|
|
1535
|
+
this.wsHandle = createWebSocketHandler(this.server, this);
|
|
452
1536
|
return new Promise((resolve) => {
|
|
453
1537
|
this.server.listen(this.port, this.host, () => {
|
|
454
1538
|
const addr = this.getAddress();
|
|
@@ -459,7 +1543,7 @@ var init_AgentEServer = __esm({
|
|
|
459
1543
|
}
|
|
460
1544
|
async stop() {
|
|
461
1545
|
this.agentE.stop();
|
|
462
|
-
if (this.
|
|
1546
|
+
if (this.wsHandle) this.wsHandle.cleanup();
|
|
463
1547
|
return new Promise((resolve, reject) => {
|
|
464
1548
|
this.server.close((err) => {
|
|
465
1549
|
if (err) reject(err);
|
|
@@ -553,6 +1637,9 @@ var init_AgentEServer = __esm({
|
|
|
553
1637
|
constrain(param, bounds) {
|
|
554
1638
|
this.agentE.constrain(param, bounds);
|
|
555
1639
|
}
|
|
1640
|
+
broadcast(data) {
|
|
1641
|
+
if (this.wsHandle) this.wsHandle.broadcast(data);
|
|
1642
|
+
}
|
|
556
1643
|
};
|
|
557
1644
|
}
|
|
558
1645
|
});
|