nexus-prime 3.2.1 → 3.2.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/dist/dashboard/index.html +459 -104
- package/dist/dashboard/server.d.ts +13 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +192 -27
- package/dist/dashboard/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
align-items: center;
|
|
76
76
|
justify-content: space-between;
|
|
77
77
|
gap: 1rem;
|
|
78
|
-
padding:
|
|
78
|
+
padding: 1rem 1.4rem;
|
|
79
79
|
border-bottom: 1px solid var(--line);
|
|
80
80
|
background: rgba(5, 8, 13, 0.76);
|
|
81
81
|
backdrop-filter: blur(28px);
|
|
@@ -99,14 +99,38 @@
|
|
|
99
99
|
|
|
100
100
|
h1 {
|
|
101
101
|
margin: 0;
|
|
102
|
-
font-size: clamp(1.
|
|
102
|
+
font-size: clamp(1.18rem, 1.8vw, 1.62rem);
|
|
103
103
|
letter-spacing: -0.04em;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
.subtitle {
|
|
107
107
|
margin-top: 0.15rem;
|
|
108
108
|
color: var(--muted);
|
|
109
|
-
font-size: 0.
|
|
109
|
+
font-size: 0.84rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.banner {
|
|
113
|
+
margin: 0 1rem;
|
|
114
|
+
margin-top: 0.85rem;
|
|
115
|
+
padding: 0.72rem 0.95rem;
|
|
116
|
+
border-radius: 16px;
|
|
117
|
+
border: 1px solid var(--line);
|
|
118
|
+
background: rgba(255, 255, 255, 0.03);
|
|
119
|
+
color: var(--muted);
|
|
120
|
+
font-size: 0.76rem;
|
|
121
|
+
line-height: 1.45;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.banner.warn {
|
|
125
|
+
border-color: rgba(255, 209, 77, 0.22);
|
|
126
|
+
background: rgba(255, 209, 77, 0.08);
|
|
127
|
+
color: #ffe7a0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.banner.bad {
|
|
131
|
+
border-color: rgba(255, 109, 99, 0.28);
|
|
132
|
+
background: rgba(255, 109, 99, 0.08);
|
|
133
|
+
color: #ffc1bc;
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
.header-actions {
|
|
@@ -122,11 +146,12 @@
|
|
|
122
146
|
display: inline-flex;
|
|
123
147
|
align-items: center;
|
|
124
148
|
gap: 0.55rem;
|
|
125
|
-
padding: 0.
|
|
149
|
+
padding: 0.48rem 0.8rem;
|
|
126
150
|
border-radius: 999px;
|
|
127
151
|
border: 1px solid var(--line);
|
|
128
152
|
background: rgba(255, 255, 255, 0.03);
|
|
129
153
|
color: var(--muted);
|
|
154
|
+
font-size: 0.82rem;
|
|
130
155
|
}
|
|
131
156
|
|
|
132
157
|
.status-pill .dot,
|
|
@@ -166,7 +191,7 @@
|
|
|
166
191
|
display: grid;
|
|
167
192
|
grid-template-columns: 320px minmax(0, 1fr) 410px;
|
|
168
193
|
gap: 1rem;
|
|
169
|
-
padding: 1rem 1rem
|
|
194
|
+
padding: 0.9rem 1rem 1rem;
|
|
170
195
|
}
|
|
171
196
|
|
|
172
197
|
.panel {
|
|
@@ -185,7 +210,7 @@
|
|
|
185
210
|
align-items: center;
|
|
186
211
|
justify-content: space-between;
|
|
187
212
|
gap: 0.8rem;
|
|
188
|
-
padding: 1rem
|
|
213
|
+
padding: 0.85rem 1rem;
|
|
189
214
|
border-bottom: 1px solid var(--line);
|
|
190
215
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
|
|
191
216
|
}
|
|
@@ -193,7 +218,7 @@
|
|
|
193
218
|
.panel-header h2,
|
|
194
219
|
.panel-header h3 {
|
|
195
220
|
margin: 0;
|
|
196
|
-
font-size: 0.
|
|
221
|
+
font-size: 0.74rem;
|
|
197
222
|
letter-spacing: 0.14em;
|
|
198
223
|
text-transform: uppercase;
|
|
199
224
|
color: var(--muted);
|
|
@@ -203,13 +228,13 @@
|
|
|
203
228
|
flex: 1;
|
|
204
229
|
min-height: 0;
|
|
205
230
|
overflow: auto;
|
|
206
|
-
padding:
|
|
231
|
+
padding: 0.85rem;
|
|
207
232
|
scrollbar-width: thin;
|
|
208
233
|
scrollbar-color: rgba(188, 204, 255, 0.22) transparent;
|
|
209
234
|
}
|
|
210
235
|
|
|
211
236
|
.panel-body.compact {
|
|
212
|
-
padding: 0.
|
|
237
|
+
padding: 0.75rem;
|
|
213
238
|
}
|
|
214
239
|
|
|
215
240
|
.rail {
|
|
@@ -235,7 +260,7 @@
|
|
|
235
260
|
border: 1px solid var(--line);
|
|
236
261
|
background: var(--panel-soft);
|
|
237
262
|
border-radius: var(--radius-card);
|
|
238
|
-
padding: 0.
|
|
263
|
+
padding: 0.78rem;
|
|
239
264
|
}
|
|
240
265
|
|
|
241
266
|
.card.interactive {
|
|
@@ -259,7 +284,7 @@
|
|
|
259
284
|
}
|
|
260
285
|
|
|
261
286
|
.card-title strong {
|
|
262
|
-
font-size:
|
|
287
|
+
font-size: 0.92rem;
|
|
263
288
|
font-weight: 600;
|
|
264
289
|
}
|
|
265
290
|
|
|
@@ -275,12 +300,12 @@
|
|
|
275
300
|
|
|
276
301
|
.meta {
|
|
277
302
|
color: var(--muted);
|
|
278
|
-
font-size: 0.
|
|
303
|
+
font-size: 0.72rem;
|
|
279
304
|
line-height: 1.5;
|
|
280
305
|
}
|
|
281
306
|
|
|
282
307
|
.hero-metric {
|
|
283
|
-
padding:
|
|
308
|
+
padding: 0.88rem;
|
|
284
309
|
border-radius: var(--radius-card);
|
|
285
310
|
border: 1px solid var(--line);
|
|
286
311
|
background: linear-gradient(160deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
|
|
@@ -288,16 +313,16 @@
|
|
|
288
313
|
|
|
289
314
|
.hero-metric label {
|
|
290
315
|
display: block;
|
|
291
|
-
font-size: 0.
|
|
316
|
+
font-size: 0.68rem;
|
|
292
317
|
color: var(--muted);
|
|
293
318
|
text-transform: uppercase;
|
|
294
319
|
letter-spacing: 0.12em;
|
|
295
|
-
margin-bottom: 0.
|
|
320
|
+
margin-bottom: 0.38rem;
|
|
296
321
|
}
|
|
297
322
|
|
|
298
323
|
.hero-metric strong {
|
|
299
324
|
display: block;
|
|
300
|
-
font-size:
|
|
325
|
+
font-size: 1.62rem;
|
|
301
326
|
letter-spacing: -0.05em;
|
|
302
327
|
margin-bottom: 0.2rem;
|
|
303
328
|
}
|
|
@@ -320,7 +345,7 @@
|
|
|
320
345
|
align-items: center;
|
|
321
346
|
justify-content: space-between;
|
|
322
347
|
gap: 0.75rem;
|
|
323
|
-
padding: 0.
|
|
348
|
+
padding: 0.68rem 0.78rem;
|
|
324
349
|
border-radius: 16px;
|
|
325
350
|
border: 1px solid var(--line);
|
|
326
351
|
background: rgba(255, 255, 255, 0.03);
|
|
@@ -335,6 +360,7 @@
|
|
|
335
360
|
|
|
336
361
|
.client-name {
|
|
337
362
|
font-weight: 600;
|
|
363
|
+
font-size: 0.92rem;
|
|
338
364
|
}
|
|
339
365
|
|
|
340
366
|
.state-chip,
|
|
@@ -343,8 +369,8 @@
|
|
|
343
369
|
align-items: center;
|
|
344
370
|
justify-content: center;
|
|
345
371
|
border-radius: 999px;
|
|
346
|
-
padding: 0.
|
|
347
|
-
font-size: 0.
|
|
372
|
+
padding: 0.2rem 0.5rem;
|
|
373
|
+
font-size: 0.68rem;
|
|
348
374
|
border: 1px solid var(--line);
|
|
349
375
|
background: rgba(255, 255, 255, 0.05);
|
|
350
376
|
text-transform: uppercase;
|
|
@@ -415,13 +441,13 @@
|
|
|
415
441
|
}
|
|
416
442
|
|
|
417
443
|
.dial-value {
|
|
418
|
-
font-size:
|
|
444
|
+
font-size: 1.68rem;
|
|
419
445
|
font-weight: 700;
|
|
420
446
|
letter-spacing: -0.06em;
|
|
421
447
|
}
|
|
422
448
|
|
|
423
449
|
.dial-caption {
|
|
424
|
-
font-size: 0.
|
|
450
|
+
font-size: 0.66rem;
|
|
425
451
|
color: var(--muted);
|
|
426
452
|
text-transform: uppercase;
|
|
427
453
|
letter-spacing: 0.12em;
|
|
@@ -453,10 +479,11 @@
|
|
|
453
479
|
.primary-button {
|
|
454
480
|
border: 0;
|
|
455
481
|
border-radius: 999px;
|
|
456
|
-
padding: 0.
|
|
482
|
+
padding: 0.42rem 0.68rem;
|
|
457
483
|
background: transparent;
|
|
458
484
|
color: var(--muted);
|
|
459
485
|
transition: background 160ms ease, color 160ms ease, transform 160ms ease;
|
|
486
|
+
font-size: 0.72rem;
|
|
460
487
|
}
|
|
461
488
|
|
|
462
489
|
.segmented button.active,
|
|
@@ -513,7 +540,7 @@
|
|
|
513
540
|
|
|
514
541
|
.node-label {
|
|
515
542
|
font-family: var(--mono);
|
|
516
|
-
font-size:
|
|
543
|
+
font-size: 9.5px;
|
|
517
544
|
fill: rgba(246, 247, 251, 0.86);
|
|
518
545
|
pointer-events: none;
|
|
519
546
|
}
|
|
@@ -551,7 +578,7 @@
|
|
|
551
578
|
|
|
552
579
|
.graph-note {
|
|
553
580
|
color: var(--muted);
|
|
554
|
-
font-size: 0.
|
|
581
|
+
font-size: 0.72rem;
|
|
555
582
|
}
|
|
556
583
|
|
|
557
584
|
.graph-footer {
|
|
@@ -572,11 +599,11 @@
|
|
|
572
599
|
display: inline-flex;
|
|
573
600
|
align-items: center;
|
|
574
601
|
gap: 0.35rem;
|
|
575
|
-
padding: 0.
|
|
602
|
+
padding: 0.24rem 0.48rem;
|
|
576
603
|
border-radius: 999px;
|
|
577
604
|
border: 1px solid var(--line);
|
|
578
605
|
background: rgba(255, 255, 255, 0.05);
|
|
579
|
-
font-size: 0.
|
|
606
|
+
font-size: 0.68rem;
|
|
580
607
|
}
|
|
581
608
|
|
|
582
609
|
.entity-header {
|
|
@@ -589,7 +616,7 @@
|
|
|
589
616
|
|
|
590
617
|
.entity-header h3 {
|
|
591
618
|
margin: 0;
|
|
592
|
-
font-size: 0.
|
|
619
|
+
font-size: 0.82rem;
|
|
593
620
|
letter-spacing: 0.08em;
|
|
594
621
|
text-transform: uppercase;
|
|
595
622
|
color: var(--muted);
|
|
@@ -599,13 +626,14 @@
|
|
|
599
626
|
display: flex;
|
|
600
627
|
flex-wrap: wrap;
|
|
601
628
|
gap: 0.35rem;
|
|
629
|
+
justify-content: flex-end;
|
|
602
630
|
}
|
|
603
631
|
|
|
604
632
|
.event-card {
|
|
605
633
|
border-radius: 18px;
|
|
606
634
|
border: 1px solid var(--line);
|
|
607
635
|
background: rgba(255, 255, 255, 0.03);
|
|
608
|
-
padding: 0.
|
|
636
|
+
padding: 0.78rem;
|
|
609
637
|
border-left: 3px solid var(--muted);
|
|
610
638
|
}
|
|
611
639
|
|
|
@@ -623,7 +651,7 @@
|
|
|
623
651
|
|
|
624
652
|
.event-head strong {
|
|
625
653
|
display: block;
|
|
626
|
-
font-size: 0.
|
|
654
|
+
font-size: 0.88rem;
|
|
627
655
|
}
|
|
628
656
|
|
|
629
657
|
.event-head .meta {
|
|
@@ -631,24 +659,24 @@
|
|
|
631
659
|
}
|
|
632
660
|
|
|
633
661
|
details.raw {
|
|
634
|
-
margin-top: 0.
|
|
662
|
+
margin-top: 0.45rem;
|
|
635
663
|
}
|
|
636
664
|
|
|
637
665
|
details.raw summary {
|
|
638
666
|
cursor: pointer;
|
|
639
667
|
color: var(--muted);
|
|
640
|
-
font-size: 0.
|
|
668
|
+
font-size: 0.68rem;
|
|
641
669
|
text-transform: uppercase;
|
|
642
670
|
letter-spacing: 0.08em;
|
|
643
671
|
}
|
|
644
672
|
|
|
645
673
|
pre {
|
|
646
674
|
overflow: auto;
|
|
647
|
-
padding: 0.
|
|
675
|
+
padding: 0.68rem;
|
|
648
676
|
border-radius: 14px;
|
|
649
677
|
background: rgba(0, 0, 0, 0.3);
|
|
650
678
|
border: 1px solid var(--line);
|
|
651
|
-
font-size: 0.
|
|
679
|
+
font-size: 0.7rem;
|
|
652
680
|
line-height: 1.5;
|
|
653
681
|
margin: 0.55rem 0 0;
|
|
654
682
|
white-space: pre-wrap;
|
|
@@ -666,7 +694,7 @@
|
|
|
666
694
|
}
|
|
667
695
|
|
|
668
696
|
.field label {
|
|
669
|
-
font-size: 0.
|
|
697
|
+
font-size: 0.68rem;
|
|
670
698
|
color: var(--muted);
|
|
671
699
|
text-transform: uppercase;
|
|
672
700
|
letter-spacing: 0.08em;
|
|
@@ -676,7 +704,7 @@
|
|
|
676
704
|
.field select,
|
|
677
705
|
.field textarea {
|
|
678
706
|
width: 100%;
|
|
679
|
-
padding: 0.
|
|
707
|
+
padding: 0.62rem 0.72rem;
|
|
680
708
|
border-radius: 14px;
|
|
681
709
|
border: 1px solid var(--line);
|
|
682
710
|
background: rgba(255, 255, 255, 0.03);
|
|
@@ -741,12 +769,12 @@
|
|
|
741
769
|
border: 1px solid var(--line);
|
|
742
770
|
background: rgba(255, 255, 255, 0.03);
|
|
743
771
|
border-radius: 18px;
|
|
744
|
-
padding: 0.
|
|
772
|
+
padding: 0.78rem;
|
|
745
773
|
}
|
|
746
774
|
|
|
747
775
|
.drawer-section h4 {
|
|
748
776
|
margin: 0 0 0.55rem;
|
|
749
|
-
font-size: 0.
|
|
777
|
+
font-size: 0.74rem;
|
|
750
778
|
color: var(--muted);
|
|
751
779
|
text-transform: uppercase;
|
|
752
780
|
letter-spacing: 0.08em;
|
|
@@ -836,6 +864,8 @@
|
|
|
836
864
|
</div>
|
|
837
865
|
</header>
|
|
838
866
|
|
|
867
|
+
<div id="status-banner" class="banner hidden" role="status" aria-live="polite"></div>
|
|
868
|
+
|
|
839
869
|
<main class="layout">
|
|
840
870
|
<aside class="rail left">
|
|
841
871
|
<section class="panel">
|
|
@@ -1068,6 +1098,13 @@
|
|
|
1068
1098
|
</aside>
|
|
1069
1099
|
|
|
1070
1100
|
<script>
|
|
1101
|
+
const DASHBOARD_API_VERSION = '2';
|
|
1102
|
+
const REQUIRED_CAPABILITIES = ['runs', 'memory', 'pod', 'clients', 'events', 'stream'];
|
|
1103
|
+
|
|
1104
|
+
function createResourceState() {
|
|
1105
|
+
return { status: 'idle', error: '' };
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1071
1108
|
const state = {
|
|
1072
1109
|
runs: [],
|
|
1073
1110
|
skills: [],
|
|
@@ -1086,6 +1123,20 @@
|
|
|
1086
1123
|
selected: null,
|
|
1087
1124
|
streamConnected: false,
|
|
1088
1125
|
lastRefreshAt: 0,
|
|
1126
|
+
banner: null,
|
|
1127
|
+
resources: {
|
|
1128
|
+
health: createResourceState(),
|
|
1129
|
+
backends: createResourceState(),
|
|
1130
|
+
runs: createResourceState(),
|
|
1131
|
+
skills: createResourceState(),
|
|
1132
|
+
workflows: createResourceState(),
|
|
1133
|
+
memory: createResourceState(),
|
|
1134
|
+
pod: createResourceState(),
|
|
1135
|
+
clients: createResourceState(),
|
|
1136
|
+
events: createResourceState(),
|
|
1137
|
+
memoryDetail: createResourceState(),
|
|
1138
|
+
memoryNetwork: createResourceState(),
|
|
1139
|
+
},
|
|
1089
1140
|
};
|
|
1090
1141
|
|
|
1091
1142
|
const graphModes = {
|
|
@@ -1147,61 +1198,299 @@
|
|
|
1147
1198
|
.filter(Boolean);
|
|
1148
1199
|
}
|
|
1149
1200
|
|
|
1201
|
+
function setResourceStatus(name, status, error = '') {
|
|
1202
|
+
state.resources[name] = { status, error };
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function getResourceStatus(name) {
|
|
1206
|
+
return state.resources[name] || createResourceState();
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function resourceFailed(name) {
|
|
1210
|
+
return getResourceStatus(name).status === 'error';
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function countFailedResources() {
|
|
1214
|
+
return Object.values(state.resources).filter((resource) => resource.status === 'error').length;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function setBanner(kind, message) {
|
|
1218
|
+
state.banner = message ? { kind, message } : null;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function clearBanner() {
|
|
1222
|
+
state.banner = null;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function normalizeError(error) {
|
|
1226
|
+
return error instanceof Error ? error.message : String(error || 'Unknown error');
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function reconcileBanner() {
|
|
1230
|
+
const healthStatus = getResourceStatus('health');
|
|
1231
|
+
if (healthStatus.status === 'error') {
|
|
1232
|
+
setBanner('bad', 'Connected to stale dashboard server or an incompatible API surface. Open the latest dashboard URL printed by the MCP process.');
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
const version = state.health?.dashboardApiVersion;
|
|
1237
|
+
const capabilities = state.health?.capabilities || {};
|
|
1238
|
+
const missingCapabilities = REQUIRED_CAPABILITIES.filter((capability) => capabilities[capability] !== true);
|
|
1239
|
+
|
|
1240
|
+
if (!version || version !== DASHBOARD_API_VERSION || missingCapabilities.length) {
|
|
1241
|
+
setBanner('bad', `Dashboard compatibility mismatch detected. Expected API v${DASHBOARD_API_VERSION}; missing capabilities: ${missingCapabilities.join(', ') || 'version marker'}.`);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
const failed = Object.entries(state.resources)
|
|
1246
|
+
.filter(([name, resource]) => resource.status === 'error' && !['memoryDetail', 'memoryNetwork'].includes(name))
|
|
1247
|
+
.map(([name]) => name);
|
|
1248
|
+
|
|
1249
|
+
if (failed.length) {
|
|
1250
|
+
setBanner('warn', `Dashboard is partially degraded. Unavailable surfaces: ${failed.join(', ')}.`);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
clearBanner();
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function emptyState(message) {
|
|
1258
|
+
return `<div class="empty">${escapeHtml(message)}</div>`;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function normalizeLegacyCategory(type) {
|
|
1262
|
+
if (String(type).startsWith('memory.')) return 'memory';
|
|
1263
|
+
if (String(type).startsWith('pod.')) return 'pod';
|
|
1264
|
+
if (String(type).startsWith('phantom.')) return 'runtime';
|
|
1265
|
+
if (String(type).startsWith('client.')) return 'clients';
|
|
1266
|
+
if (String(type).startsWith('skill.')) return 'skills';
|
|
1267
|
+
if (String(type).startsWith('workflow.')) return 'workflows';
|
|
1268
|
+
if (String(type).startsWith('tokens.') || String(type).startsWith('cas.') || String(type).startsWith('kv.')) return 'tokens';
|
|
1269
|
+
return 'system';
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function normalizeLegacySeverity(type, payload) {
|
|
1273
|
+
if (type === 'guardrail.check') return payload?.passed ? 'good' : 'bad';
|
|
1274
|
+
if (['phantom.merge', 'phantom.merge.complete', 'workflow.run'].includes(type)) {
|
|
1275
|
+
return payload?.status === 'failed' ? 'bad' : 'good';
|
|
1276
|
+
}
|
|
1277
|
+
if (type === 'client.inferred') return 'warn';
|
|
1278
|
+
if (type === 'dashboard.action' && payload?.status === 'failed') return 'bad';
|
|
1279
|
+
if (type === 'pod.signal') return 'info';
|
|
1280
|
+
return 'info';
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function legacyTitle(type) {
|
|
1284
|
+
return {
|
|
1285
|
+
'system.boot': 'Runtime boot',
|
|
1286
|
+
'memory.store': 'Memory stored',
|
|
1287
|
+
'memory.recall': 'Memory recall',
|
|
1288
|
+
'pod.signal': 'POD signal',
|
|
1289
|
+
'tokens.optimized': 'Tokens optimized',
|
|
1290
|
+
'phantom.worker.start': 'Worker start',
|
|
1291
|
+
'phantom.worker.complete': 'Worker complete',
|
|
1292
|
+
'phantom.merge.complete': 'Merge complete',
|
|
1293
|
+
'phantom.merge': 'Merge decision',
|
|
1294
|
+
'guardrail.check': 'Guardrail check',
|
|
1295
|
+
'ghost.pass': 'Ghost pass',
|
|
1296
|
+
'graph.query': 'Graph query',
|
|
1297
|
+
'darwin.cycle': 'Darwin cycle',
|
|
1298
|
+
'session.dna': 'Session DNA',
|
|
1299
|
+
'skill.register': 'Skill registered',
|
|
1300
|
+
'skill.deploy': 'Skill deployed',
|
|
1301
|
+
'skill.revoke': 'Skill revoked',
|
|
1302
|
+
'workflow.deploy': 'Workflow deployed',
|
|
1303
|
+
'workflow.run': 'Workflow run',
|
|
1304
|
+
'client.heartbeat': 'Client heartbeat',
|
|
1305
|
+
'client.inferred': 'Client inferred',
|
|
1306
|
+
'client.status': 'Client status',
|
|
1307
|
+
'dashboard.action': 'Dashboard action',
|
|
1308
|
+
'nexusnet.publish': 'NexusNet publish',
|
|
1309
|
+
'nexusnet.sync': 'NexusNet sync',
|
|
1310
|
+
'entanglement.create': 'Entanglement created',
|
|
1311
|
+
'entanglement.collapse': 'Entanglement collapsed',
|
|
1312
|
+
'entanglement.correlate': 'Entanglement correlated',
|
|
1313
|
+
'cas.encode': 'CAS encode',
|
|
1314
|
+
'cas.decode': 'CAS decode',
|
|
1315
|
+
'cas.pattern_learned': 'CAS pattern',
|
|
1316
|
+
'kv.merge': 'KV merge',
|
|
1317
|
+
'kv.adapt': 'KV adapt',
|
|
1318
|
+
'kv.consensus': 'KV consensus',
|
|
1319
|
+
}[type] || String(type || 'event');
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function legacySource(type, payload) {
|
|
1323
|
+
if (String(type).startsWith('client.')) return String(payload?.displayName || payload?.clientId || 'client');
|
|
1324
|
+
if (type === 'pod.signal') return String(payload?.workerId || 'pod');
|
|
1325
|
+
if (String(type).startsWith('phantom.')) return String(payload?.workerId || payload?.winner || 'runtime');
|
|
1326
|
+
if (String(type).startsWith('skill.')) return String(payload?.skillId || payload?.name || 'skill');
|
|
1327
|
+
if (String(type).startsWith('workflow.')) return String(payload?.workflowId || 'workflow');
|
|
1328
|
+
return 'nexus-prime';
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function legacySummary(type, payload) {
|
|
1332
|
+
switch (type) {
|
|
1333
|
+
case 'memory.store':
|
|
1334
|
+
return `Priority ${payload?.priority ?? 'n/a'} · ${(payload?.tags || []).join(', ') || 'no tags'}`;
|
|
1335
|
+
case 'memory.recall':
|
|
1336
|
+
return `Recalled ${payload?.count ?? 0} memories for "${payload?.query || ''}"`;
|
|
1337
|
+
case 'pod.signal':
|
|
1338
|
+
return String(payload?.content || 'POD signal received');
|
|
1339
|
+
case 'tokens.optimized':
|
|
1340
|
+
return `Saved ${payload?.savings ?? 0} tokens across ${payload?.files ?? 0} files`;
|
|
1341
|
+
case 'phantom.worker.start':
|
|
1342
|
+
return `${payload?.approach || 'worker'} started for ${payload?.goal || 'task'}`;
|
|
1343
|
+
case 'phantom.worker.complete':
|
|
1344
|
+
return `Confidence ${payload?.confidence ?? 0}`;
|
|
1345
|
+
case 'phantom.merge':
|
|
1346
|
+
return `${payload?.action || 'merge'} · ${payload?.winner || 'unknown winner'}`;
|
|
1347
|
+
case 'dashboard.action':
|
|
1348
|
+
return `${payload?.action || 'action'} → ${payload?.status || 'unknown'}`;
|
|
1349
|
+
default:
|
|
1350
|
+
return typeof payload === 'string' ? payload : JSON.stringify(payload || {});
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function normalizeEventCard(raw) {
|
|
1355
|
+
if (!raw) return null;
|
|
1356
|
+
|
|
1357
|
+
if (raw.category && raw.title && raw.time) {
|
|
1358
|
+
return {
|
|
1359
|
+
id: raw.id || `evt-${raw.time}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1360
|
+
type: raw.type || raw.category,
|
|
1361
|
+
title: String(raw.title),
|
|
1362
|
+
source: String(raw.source || 'nexus-prime'),
|
|
1363
|
+
time: Number(raw.time) || Date.now(),
|
|
1364
|
+
severity: ['good', 'info', 'warn', 'bad'].includes(raw.severity) ? raw.severity : 'info',
|
|
1365
|
+
category: raw.category,
|
|
1366
|
+
summary: String(raw.summary || ''),
|
|
1367
|
+
payload: raw.payload ?? raw,
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
if (raw.type && raw.timestamp) {
|
|
1372
|
+
const payload = raw.data || {};
|
|
1373
|
+
return {
|
|
1374
|
+
id: raw.id || `evt-${raw.timestamp}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1375
|
+
type: raw.type,
|
|
1376
|
+
title: legacyTitle(raw.type),
|
|
1377
|
+
source: legacySource(raw.type, payload),
|
|
1378
|
+
time: Number(raw.timestamp) || Date.now(),
|
|
1379
|
+
severity: normalizeLegacySeverity(raw.type, payload),
|
|
1380
|
+
category: normalizeLegacyCategory(raw.type),
|
|
1381
|
+
summary: legacySummary(raw.type, payload),
|
|
1382
|
+
payload,
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
return {
|
|
1387
|
+
id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1388
|
+
type: 'system.legacy',
|
|
1389
|
+
title: 'Legacy event',
|
|
1390
|
+
source: 'nexus-prime',
|
|
1391
|
+
time: Date.now(),
|
|
1392
|
+
severity: 'info',
|
|
1393
|
+
category: 'system',
|
|
1394
|
+
summary: typeof raw === 'string' ? raw : 'Malformed event payload received',
|
|
1395
|
+
payload: raw,
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1150
1399
|
async function fetchJson(url, options) {
|
|
1151
1400
|
const res = await fetch(url, options);
|
|
1401
|
+
const text = await res.text();
|
|
1152
1402
|
if (!res.ok) {
|
|
1153
|
-
throw new Error(`${res.status} ${res.statusText}`);
|
|
1403
|
+
throw new Error(`${res.status} ${res.statusText}${text ? ` · ${text.slice(0, 120)}` : ''}`);
|
|
1154
1404
|
}
|
|
1155
|
-
return
|
|
1405
|
+
return text ? JSON.parse(text) : null;
|
|
1156
1406
|
}
|
|
1157
1407
|
|
|
1158
1408
|
async function refreshAll() {
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1409
|
+
const resources = [
|
|
1410
|
+
['runs', '/api/runs?limit=20', (value) => { state.runs = Array.isArray(value) ? value : state.runs; }],
|
|
1411
|
+
['skills', '/api/skills', (value) => { state.skills = Array.isArray(value) ? value : state.skills; }],
|
|
1412
|
+
['workflows', '/api/workflows', (value) => { state.workflows = Array.isArray(value) ? value : state.workflows; }],
|
|
1413
|
+
['backends', '/api/backends', (value) => { state.backends = value || state.backends; }],
|
|
1414
|
+
['health', '/api/health', (value) => { state.health = value || state.health; }],
|
|
1415
|
+
['memory', '/api/memory?limit=40', (value) => { state.memories = Array.isArray(value) ? value : state.memories; }],
|
|
1416
|
+
['pod', '/api/pod?limit=30', (value) => { state.pod = value || state.pod; }],
|
|
1417
|
+
['clients', '/api/clients', (value) => { state.clients = Array.isArray(value) ? value : state.clients; }],
|
|
1418
|
+
['events', '/api/events?limit=80', (value) => {
|
|
1419
|
+
state.events = Array.isArray(value)
|
|
1420
|
+
? value.map((event) => normalizeEventCard(event)).filter(Boolean)
|
|
1421
|
+
: state.events;
|
|
1422
|
+
}],
|
|
1423
|
+
];
|
|
1424
|
+
|
|
1425
|
+
const results = await Promise.allSettled(resources.map(([, url]) => fetchJson(url)));
|
|
1426
|
+
let refreshed = false;
|
|
1427
|
+
|
|
1428
|
+
results.forEach((result, index) => {
|
|
1429
|
+
const [name, , assign] = resources[index];
|
|
1430
|
+
if (result.status === 'fulfilled') {
|
|
1431
|
+
assign(result.value);
|
|
1432
|
+
setResourceStatus(name, 'ready');
|
|
1433
|
+
refreshed = true;
|
|
1434
|
+
} else {
|
|
1435
|
+
setResourceStatus(name, 'error', normalizeError(result.reason));
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1170
1438
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
state.backends = backends || {};
|
|
1175
|
-
state.health = health || {};
|
|
1176
|
-
state.memories = Array.isArray(memories) ? memories : [];
|
|
1177
|
-
state.pod = pod || state.pod;
|
|
1178
|
-
state.clients = Array.isArray(clients) ? clients : [];
|
|
1179
|
-
state.events = Array.isArray(events) ? events : [];
|
|
1180
|
-
state.lastRefreshAt = Date.now();
|
|
1439
|
+
if (refreshed) {
|
|
1440
|
+
state.lastRefreshAt = Date.now();
|
|
1441
|
+
}
|
|
1181
1442
|
|
|
1182
1443
|
const focusMemoryId = state.selected?.kind === 'memory'
|
|
1183
1444
|
? state.selected.id
|
|
1184
1445
|
: state.memories[0]?.id;
|
|
1185
|
-
|
|
1446
|
+
|
|
1447
|
+
if (focusMemoryId && !resourceFailed('memory')) {
|
|
1186
1448
|
await refreshMemorySelection(focusMemoryId, false);
|
|
1187
|
-
} else {
|
|
1449
|
+
} else if (!focusMemoryId) {
|
|
1188
1450
|
state.memoryDetail = null;
|
|
1189
1451
|
state.memoryNetwork = { nodes: [], links: [], focusId: null };
|
|
1452
|
+
setResourceStatus('memoryDetail', 'idle');
|
|
1453
|
+
setResourceStatus('memoryNetwork', 'idle');
|
|
1190
1454
|
}
|
|
1191
1455
|
|
|
1456
|
+
reconcileBanner();
|
|
1192
1457
|
populateBackendSelects();
|
|
1193
1458
|
render();
|
|
1194
1459
|
}
|
|
1195
1460
|
|
|
1196
1461
|
async function refreshMemorySelection(memoryId, openDrawer = true) {
|
|
1197
|
-
const [detail, network] = await Promise.
|
|
1462
|
+
const [detail, network] = await Promise.allSettled([
|
|
1198
1463
|
fetchJson(`/api/memory/${encodeURIComponent(memoryId)}`),
|
|
1199
1464
|
fetchJson(`/api/memory/${encodeURIComponent(memoryId)}/network?depth=2&limit=18`),
|
|
1200
1465
|
]);
|
|
1201
|
-
|
|
1202
|
-
|
|
1466
|
+
|
|
1467
|
+
if (detail.status === 'fulfilled') {
|
|
1468
|
+
state.memoryDetail = detail.value;
|
|
1469
|
+
setResourceStatus('memoryDetail', 'ready');
|
|
1470
|
+
} else {
|
|
1471
|
+
state.memoryDetail = state.memories.find((memory) => memory.id === memoryId) || null;
|
|
1472
|
+
setResourceStatus('memoryDetail', 'error', normalizeError(detail.reason));
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
if (network.status === 'fulfilled') {
|
|
1476
|
+
state.memoryNetwork = network.value || { nodes: [], links: [], focusId: memoryId };
|
|
1477
|
+
setResourceStatus('memoryNetwork', 'ready');
|
|
1478
|
+
} else {
|
|
1479
|
+
state.memoryNetwork = {
|
|
1480
|
+
focusId: memoryId,
|
|
1481
|
+
nodes: state.memories.slice(0, 12).map((memory) => ({
|
|
1482
|
+
id: memory.id,
|
|
1483
|
+
label: memory.excerpt,
|
|
1484
|
+
entityType: 'memory',
|
|
1485
|
+
tier: memory.tier,
|
|
1486
|
+
})),
|
|
1487
|
+
links: [],
|
|
1488
|
+
};
|
|
1489
|
+
setResourceStatus('memoryNetwork', 'error', normalizeError(network.reason));
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1203
1492
|
if (openDrawer) {
|
|
1204
|
-
state.selected = { kind: 'memory', id: memoryId, data:
|
|
1493
|
+
state.selected = { kind: 'memory', id: memoryId, data: state.memoryDetail };
|
|
1205
1494
|
}
|
|
1206
1495
|
}
|
|
1207
1496
|
|
|
@@ -1254,6 +1543,7 @@
|
|
|
1254
1543
|
}
|
|
1255
1544
|
|
|
1256
1545
|
function render() {
|
|
1546
|
+
renderBanner();
|
|
1257
1547
|
renderHeader();
|
|
1258
1548
|
renderLeftRail();
|
|
1259
1549
|
renderGraph();
|
|
@@ -1262,19 +1552,38 @@
|
|
|
1262
1552
|
renderDrawer();
|
|
1263
1553
|
}
|
|
1264
1554
|
|
|
1555
|
+
function renderBanner() {
|
|
1556
|
+
const banner = $('status-banner');
|
|
1557
|
+
if (!state.banner?.message) {
|
|
1558
|
+
banner.className = 'banner hidden';
|
|
1559
|
+
banner.textContent = '';
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
banner.className = `banner ${state.banner.kind}`;
|
|
1564
|
+
banner.textContent = state.banner.message;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1265
1567
|
function renderHeader() {
|
|
1266
1568
|
$('header-version').textContent = `v${state.health.release?.packageVersion || '?'}`;
|
|
1267
1569
|
const pill = $('sync-pill');
|
|
1268
|
-
|
|
1269
|
-
pill.classList.toggle('
|
|
1570
|
+
const failedResources = countFailedResources();
|
|
1571
|
+
pill.classList.toggle('live', state.streamConnected && failedResources === 0);
|
|
1572
|
+
pill.classList.toggle('warn', !state.streamConnected || failedResources > 0);
|
|
1270
1573
|
$('sync-label').textContent = state.streamConnected
|
|
1271
|
-
?
|
|
1272
|
-
|
|
1574
|
+
? failedResources
|
|
1575
|
+
? `Degraded · ${failedResources} surfaces unavailable`
|
|
1576
|
+
: `Synchronized · ${formatAgo(state.lastRefreshAt)}`
|
|
1577
|
+
: failedResources
|
|
1578
|
+
? `REST degraded · ${failedResources} unavailable`
|
|
1579
|
+
: 'Stream reconnecting';
|
|
1273
1580
|
}
|
|
1274
1581
|
|
|
1275
1582
|
function renderLeftRail() {
|
|
1276
|
-
$('clients-summary').textContent = `${state.clients.length} visible`;
|
|
1277
|
-
$('clients-list').innerHTML = state.clients.length
|
|
1583
|
+
$('clients-summary').textContent = resourceFailed('clients') ? 'unavailable' : `${state.clients.length} visible`;
|
|
1584
|
+
$('clients-list').innerHTML = resourceFailed('clients') && !state.clients.length
|
|
1585
|
+
? emptyState('Clients endpoint unavailable.')
|
|
1586
|
+
: state.clients.length
|
|
1278
1587
|
? state.clients.map((client) => `
|
|
1279
1588
|
<div class="client-row card interactive ${state.selected?.kind === 'client' && state.selected.id === client.clientId ? 'active' : ''}" data-kind="client" data-id="${escapeHtml(client.clientId)}">
|
|
1280
1589
|
<div class="client-info">
|
|
@@ -1287,37 +1596,47 @@
|
|
|
1287
1596
|
<span class="state-chip ${stateClass(client.state)}">${escapeHtml(client.state)}</span>
|
|
1288
1597
|
</div>
|
|
1289
1598
|
`).join('')
|
|
1290
|
-
: '
|
|
1599
|
+
: emptyState('No clients detected.');
|
|
1291
1600
|
|
|
1292
1601
|
const tokenMetrics = computeTokenMetrics();
|
|
1293
|
-
$('token-summary').textContent = `${tokenMetrics.events} events`;
|
|
1602
|
+
$('token-summary').textContent = resourceFailed('events') ? 'events unavailable' : `${tokenMetrics.events} events`;
|
|
1294
1603
|
$('gross-tokens').textContent = formatNumber(tokenMetrics.gross);
|
|
1295
1604
|
$('saved-tokens').textContent = formatNumber(tokenMetrics.saved);
|
|
1296
1605
|
$('net-tokens').textContent = formatNumber(tokenMetrics.forwarded);
|
|
1297
|
-
$('memory-count').textContent = formatNumber(state.memories.length);
|
|
1606
|
+
$('memory-count').textContent = resourceFailed('memory') && !state.memories.length ? 'n/a' : formatNumber(state.memories.length);
|
|
1298
1607
|
$('dial-value').textContent = `${tokenMetrics.ratio}%`;
|
|
1299
1608
|
const circumference = 2 * Math.PI * 70;
|
|
1300
1609
|
const offset = circumference - (circumference * tokenMetrics.ratio / 100);
|
|
1301
1610
|
$('dial-progress').style.strokeDashoffset = `${offset}`;
|
|
1302
1611
|
|
|
1303
|
-
$('runs-count').textContent = formatNumber(state.runs.length);
|
|
1612
|
+
$('runs-count').textContent = resourceFailed('runs') && !state.runs.length ? 'n/a' : formatNumber(state.runs.length);
|
|
1304
1613
|
const latestRun = state.runs[0];
|
|
1305
|
-
$('latest-run-state').textContent = latestRun
|
|
1614
|
+
$('latest-run-state').textContent = resourceFailed('runs') && !latestRun
|
|
1615
|
+
? 'Runs endpoint unavailable'
|
|
1616
|
+
: latestRun
|
|
1306
1617
|
? `${latestRun.state} · ${latestRun.selectedBackends?.memoryBackend || 'memory n/a'}`
|
|
1307
1618
|
: 'No runs yet';
|
|
1308
1619
|
$('artifact-count').textContent = `${state.skills.length} / ${state.workflows.length}`;
|
|
1309
|
-
$('artifact-summary').textContent =
|
|
1310
|
-
|
|
1311
|
-
|
|
1620
|
+
$('artifact-summary').textContent = resourceFailed('skills') || resourceFailed('workflows')
|
|
1621
|
+
? 'Skill or workflow endpoint unavailable'
|
|
1622
|
+
: `${state.skills.filter((item) => item.rolloutStatus === 'promoted').length} promoted skills · ${state.workflows.filter((item) => item.rolloutStatus === 'promoted').length} promoted workflows`;
|
|
1623
|
+
$('docs-health').textContent = resourceFailed('health')
|
|
1624
|
+
? 'Unknown'
|
|
1625
|
+
: state.health.docs?.pagesWorkflowValid ? 'Healthy' : 'Needs attention';
|
|
1626
|
+
$('ci-health').textContent = resourceFailed('health')
|
|
1627
|
+
? 'Health endpoint unavailable'
|
|
1628
|
+
: state.health.docs?.pagesWorkflowValid
|
|
1312
1629
|
? `Pages valid · ${state.health.ci?.eventHistory || 0} events`
|
|
1313
1630
|
: 'Pages syntax or docs deployment needs attention';
|
|
1314
1631
|
|
|
1315
1632
|
const memory = state.health.memory || {};
|
|
1316
|
-
$('memory-tier-summary').textContent = `${memory.cortex || 0} cortex`;
|
|
1633
|
+
$('memory-tier-summary').textContent = resourceFailed('health') ? 'health unavailable' : `${memory.cortex || 0} cortex`;
|
|
1317
1634
|
|
|
1318
1635
|
const pod = state.pod || {};
|
|
1319
|
-
$('pod-summary').textContent = `${(pod.activeWorkers || []).length} workers`;
|
|
1320
|
-
$('pod-highlights').innerHTML = pod.activeWorkers
|
|
1636
|
+
$('pod-summary').textContent = resourceFailed('pod') ? 'unavailable' : `${(pod.activeWorkers || []).length} workers`;
|
|
1637
|
+
$('pod-highlights').innerHTML = resourceFailed('pod') && !(pod.activeWorkers || []).length
|
|
1638
|
+
? emptyState('POD endpoint unavailable.')
|
|
1639
|
+
: pod.activeWorkers?.length
|
|
1321
1640
|
? pod.activeWorkers.slice(0, 4).map((worker) => `
|
|
1322
1641
|
<div class="card interactive ${state.selected?.kind === 'pod-worker' && state.selected.id === worker.workerId ? 'active' : ''}" data-kind="pod-worker" data-id="${escapeHtml(worker.workerId)}">
|
|
1323
1642
|
<div class="card-title">
|
|
@@ -1328,7 +1647,7 @@
|
|
|
1328
1647
|
<div class="list-inline">${(worker.tags || []).slice(0, 4).map((tag) => chip(tag)).join('')}</div>
|
|
1329
1648
|
</div>
|
|
1330
1649
|
`).join('')
|
|
1331
|
-
: '
|
|
1650
|
+
: emptyState('Waiting for POD traffic.');
|
|
1332
1651
|
}
|
|
1333
1652
|
|
|
1334
1653
|
function buildGraphModel() {
|
|
@@ -1374,7 +1693,14 @@
|
|
|
1374
1693
|
return { nodes, links };
|
|
1375
1694
|
}
|
|
1376
1695
|
|
|
1377
|
-
const nodes = (state.memoryNetwork.nodes || []).
|
|
1696
|
+
const nodes = (state.memoryNetwork.nodes || []).length
|
|
1697
|
+
? (state.memoryNetwork.nodes || []).map((node) => ({ ...node }))
|
|
1698
|
+
: state.memories.slice(0, 18).map((memory) => ({
|
|
1699
|
+
id: memory.id,
|
|
1700
|
+
label: memory.excerpt,
|
|
1701
|
+
entityType: 'memory',
|
|
1702
|
+
tier: memory.tier,
|
|
1703
|
+
}));
|
|
1378
1704
|
const links = (state.memoryNetwork.links || []).map((link) => ({ ...link }));
|
|
1379
1705
|
return { nodes, links };
|
|
1380
1706
|
}
|
|
@@ -1394,7 +1720,19 @@
|
|
|
1394
1720
|
if (!model.nodes.length) {
|
|
1395
1721
|
svg.innerHTML = '';
|
|
1396
1722
|
empty.classList.remove('hidden');
|
|
1723
|
+
empty.textContent = state.graphMode === 'memory'
|
|
1724
|
+
? resourceFailed('memory')
|
|
1725
|
+
? 'Memory endpoint unavailable.'
|
|
1726
|
+
: 'No memory stored yet.'
|
|
1727
|
+
: state.graphMode === 'runs'
|
|
1728
|
+
? resourceFailed('runs')
|
|
1729
|
+
? 'Runs endpoint unavailable.'
|
|
1730
|
+
: 'No run graph data yet.'
|
|
1731
|
+
: resourceFailed('pod')
|
|
1732
|
+
? 'POD endpoint unavailable.'
|
|
1733
|
+
: 'No POD topology data yet.';
|
|
1397
1734
|
$('graph-stats').innerHTML = '';
|
|
1735
|
+
$('graph-note').textContent = empty.textContent;
|
|
1398
1736
|
return;
|
|
1399
1737
|
}
|
|
1400
1738
|
|
|
@@ -1469,6 +1807,9 @@
|
|
|
1469
1807
|
state.graphMode === 'runs' ? chip(`${state.runs.length} recent runs`) : '',
|
|
1470
1808
|
state.graphMode === 'pod' ? chip(`${(state.pod.activeWorkers || []).length} pod workers`) : '',
|
|
1471
1809
|
].filter(Boolean).join('');
|
|
1810
|
+
$('graph-note').textContent = state.graphMode === 'memory' && resourceFailed('memoryNetwork')
|
|
1811
|
+
? 'Memory network endpoint unavailable. Showing snapshot fallback topology.'
|
|
1812
|
+
: config.subtitle;
|
|
1472
1813
|
}
|
|
1473
1814
|
|
|
1474
1815
|
function renderLibrary() {
|
|
@@ -1486,7 +1827,9 @@
|
|
|
1486
1827
|
const container = $('library-list');
|
|
1487
1828
|
|
|
1488
1829
|
if (state.libraryMode === 'memories') {
|
|
1489
|
-
container.innerHTML = state.memories.length
|
|
1830
|
+
container.innerHTML = resourceFailed('memory') && !state.memories.length
|
|
1831
|
+
? emptyState('Memory endpoint unavailable.')
|
|
1832
|
+
: state.memories.length
|
|
1490
1833
|
? state.memories.map((memory) => `
|
|
1491
1834
|
<div class="card interactive ${state.selected?.kind === 'memory' && state.selected.id === memory.id ? 'active' : ''}" data-kind="memory" data-id="${escapeHtml(memory.id)}">
|
|
1492
1835
|
<div class="card-title">
|
|
@@ -1497,9 +1840,11 @@
|
|
|
1497
1840
|
<div class="list-inline">${(memory.tags || []).slice(0, 5).map((tag) => chip(tag)).join('')}</div>
|
|
1498
1841
|
</div>
|
|
1499
1842
|
`).join('')
|
|
1500
|
-
: '
|
|
1843
|
+
: emptyState('No memory stored yet.');
|
|
1501
1844
|
} else if (state.libraryMode === 'skills') {
|
|
1502
|
-
container.innerHTML = state.skills.length
|
|
1845
|
+
container.innerHTML = resourceFailed('skills') && !state.skills.length
|
|
1846
|
+
? emptyState('Skills endpoint unavailable.')
|
|
1847
|
+
: state.skills.length
|
|
1503
1848
|
? state.skills.map((skill) => `
|
|
1504
1849
|
<div class="card interactive ${state.selected?.kind === 'skill' && state.selected.id === skill.skillId ? 'active' : ''}" data-kind="skill" data-id="${escapeHtml(skill.skillId)}">
|
|
1505
1850
|
<div class="card-title">
|
|
@@ -1514,9 +1859,11 @@
|
|
|
1514
1859
|
</div>
|
|
1515
1860
|
</div>
|
|
1516
1861
|
`).join('')
|
|
1517
|
-
: '
|
|
1862
|
+
: emptyState('No skills loaded.');
|
|
1518
1863
|
} else if (state.libraryMode === 'workflows') {
|
|
1519
|
-
container.innerHTML = state.workflows.length
|
|
1864
|
+
container.innerHTML = resourceFailed('workflows') && !state.workflows.length
|
|
1865
|
+
? emptyState('Workflows endpoint unavailable.')
|
|
1866
|
+
: state.workflows.length
|
|
1520
1867
|
? state.workflows.map((workflow) => `
|
|
1521
1868
|
<div class="card interactive ${state.selected?.kind === 'workflow' && state.selected.id === workflow.workflowId ? 'active' : ''}" data-kind="workflow" data-id="${escapeHtml(workflow.workflowId)}">
|
|
1522
1869
|
<div class="card-title">
|
|
@@ -1527,9 +1874,11 @@
|
|
|
1527
1874
|
<div class="list-inline">${chip(`domain:${workflow.domain}`)}${chip(`verify:${workflow.effectiveness?.verificationPasses || 0}`)}</div>
|
|
1528
1875
|
</div>
|
|
1529
1876
|
`).join('')
|
|
1530
|
-
: '
|
|
1877
|
+
: emptyState('No workflows loaded.');
|
|
1531
1878
|
} else if (state.libraryMode === 'pod') {
|
|
1532
|
-
container.innerHTML = state.pod.messages
|
|
1879
|
+
container.innerHTML = resourceFailed('pod') && !(state.pod.messages || []).length
|
|
1880
|
+
? emptyState('POD endpoint unavailable.')
|
|
1881
|
+
: state.pod.messages?.length
|
|
1533
1882
|
? state.pod.messages.map((message) => `
|
|
1534
1883
|
<div class="card interactive ${state.selected?.kind === 'pod-worker' && state.selected.id === message.workerId ? 'active' : ''}" data-kind="pod-worker" data-id="${escapeHtml(message.workerId)}">
|
|
1535
1884
|
<div class="card-title">
|
|
@@ -1541,9 +1890,11 @@
|
|
|
1541
1890
|
<div class="list-inline">${(message.tags || []).map((tag) => chip(tag)).join('')}</div>
|
|
1542
1891
|
</div>
|
|
1543
1892
|
`).join('')
|
|
1544
|
-
: '
|
|
1893
|
+
: emptyState('No POD signals captured.');
|
|
1545
1894
|
} else {
|
|
1546
|
-
container.innerHTML = state.clients.length
|
|
1895
|
+
container.innerHTML = resourceFailed('clients') && !state.clients.length
|
|
1896
|
+
? emptyState('Clients endpoint unavailable.')
|
|
1897
|
+
: state.clients.length
|
|
1547
1898
|
? state.clients.map((client) => `
|
|
1548
1899
|
<div class="card interactive ${state.selected?.kind === 'client' && state.selected.id === client.clientId ? 'active' : ''}" data-kind="client" data-id="${escapeHtml(client.clientId)}">
|
|
1549
1900
|
<div class="card-title">
|
|
@@ -1554,7 +1905,7 @@
|
|
|
1554
1905
|
<div class="list-inline">${(client.evidence || []).map((evidence) => chip(evidence)).join('')}</div>
|
|
1555
1906
|
</div>
|
|
1556
1907
|
`).join('')
|
|
1557
|
-
: '
|
|
1908
|
+
: emptyState('No clients detected.');
|
|
1558
1909
|
}
|
|
1559
1910
|
|
|
1560
1911
|
container.querySelectorAll('.card.interactive').forEach((card) => {
|
|
@@ -1567,7 +1918,7 @@
|
|
|
1567
1918
|
}
|
|
1568
1919
|
|
|
1569
1920
|
function renderEvents() {
|
|
1570
|
-
$('events-summary').textContent = `${state.events.length} signals`;
|
|
1921
|
+
$('events-summary').textContent = resourceFailed('events') ? 'events unavailable' : `${state.events.length} signals`;
|
|
1571
1922
|
document.querySelectorAll('#event-filters button').forEach((button) => {
|
|
1572
1923
|
button.classList.toggle('active', button.dataset.eventFilter === state.eventFilter);
|
|
1573
1924
|
});
|
|
@@ -1593,7 +1944,9 @@
|
|
|
1593
1944
|
</details>
|
|
1594
1945
|
</article>
|
|
1595
1946
|
`).join('')
|
|
1596
|
-
: '
|
|
1947
|
+
: resourceFailed('events')
|
|
1948
|
+
? emptyState('Events endpoint unavailable. Live stream may still populate this panel.')
|
|
1949
|
+
: emptyState('No events for this filter.');
|
|
1597
1950
|
}
|
|
1598
1951
|
|
|
1599
1952
|
function renderDrawer() {
|
|
@@ -2077,10 +2430,13 @@
|
|
|
2077
2430
|
};
|
|
2078
2431
|
stream.onmessage = (event) => {
|
|
2079
2432
|
try {
|
|
2080
|
-
const
|
|
2081
|
-
if (
|
|
2433
|
+
const raw = JSON.parse(event.data);
|
|
2434
|
+
if (raw?.connected) return;
|
|
2435
|
+
const payload = normalizeEventCard(raw);
|
|
2436
|
+
if (!payload) return;
|
|
2082
2437
|
state.events.unshift(payload);
|
|
2083
2438
|
state.events = state.events.slice(0, 120);
|
|
2439
|
+
setResourceStatus('events', 'ready');
|
|
2084
2440
|
if (['runtime', 'memory', 'pod', 'skills', 'workflows', 'clients'].includes(payload.category)) {
|
|
2085
2441
|
void refreshAll().catch(() => {});
|
|
2086
2442
|
} else {
|
|
@@ -2095,11 +2451,10 @@
|
|
|
2095
2451
|
|
|
2096
2452
|
async function bootstrap() {
|
|
2097
2453
|
attachStaticHandlers();
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
}
|
|
2454
|
+
await refreshAll();
|
|
2455
|
+
$('control-status').textContent = state.banner?.message
|
|
2456
|
+
? state.banner.message
|
|
2457
|
+
: 'Dashboard actions stay local and route through the runtime.';
|
|
2103
2458
|
connectStream();
|
|
2104
2459
|
setInterval(() => {
|
|
2105
2460
|
void refreshAll().catch(() => {});
|