nexus-prime 3.2.0 → 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/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/dashboard/index.html +2301 -444
- package/dist/dashboard/server.d.ts +29 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +566 -43
- package/dist/dashboard/server.js.map +1 -1
- package/dist/engines/client-registry.d.ts +42 -0
- package/dist/engines/client-registry.d.ts.map +1 -0
- package/dist/engines/client-registry.js +319 -0
- package/dist/engines/client-registry.js.map +1 -0
- package/dist/engines/event-bus.d.ts +52 -1
- package/dist/engines/event-bus.d.ts.map +1 -1
- package/dist/engines/event-bus.js.map +1 -1
- package/dist/engines/memory.d.ts +59 -0
- package/dist/engines/memory.d.ts.map +1 -1
- package/dist/engines/memory.js +214 -0
- package/dist/engines/memory.js.map +1 -1
- package/dist/engines/pod-network.d.ts +27 -0
- package/dist/engines/pod-network.d.ts.map +1 -1
- package/dist/engines/pod-network.js +66 -1
- package/dist/engines/pod-network.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/phantom/runtime.d.ts +2 -0
- package/dist/phantom/runtime.d.ts.map +1 -1
- package/dist/phantom/runtime.js +71 -4
- package/dist/phantom/runtime.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,608 +3,2465 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Nexus Prime
|
|
6
|
+
<title>Nexus Prime Topology Console</title>
|
|
7
7
|
<style>
|
|
8
8
|
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;600&display=swap');
|
|
9
9
|
|
|
10
10
|
:root {
|
|
11
|
-
--bg: #
|
|
12
|
-
--bg-
|
|
13
|
-
--panel: rgba(
|
|
14
|
-
--panel-strong: rgba(
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
11
|
+
--bg: #040507;
|
|
12
|
+
--bg-deep: #090b14;
|
|
13
|
+
--panel: rgba(19, 23, 34, 0.82);
|
|
14
|
+
--panel-strong: rgba(27, 33, 48, 0.9);
|
|
15
|
+
--panel-soft: rgba(22, 28, 41, 0.66);
|
|
16
|
+
--line: rgba(188, 204, 255, 0.12);
|
|
17
|
+
--line-strong: rgba(188, 204, 255, 0.22);
|
|
18
|
+
--text: #f6f7fb;
|
|
19
|
+
--muted: #9ea6bc;
|
|
20
|
+
--accent: #54ff87;
|
|
21
|
+
--accent-soft: rgba(84, 255, 135, 0.14);
|
|
22
|
+
--blue: #3da1ff;
|
|
23
|
+
--violet: #b05cff;
|
|
24
|
+
--amber: #ffd14d;
|
|
25
|
+
--red: #ff6d63;
|
|
26
|
+
--cyan: #49f0ff;
|
|
27
|
+
--mono: 'IBM Plex Mono', monospace;
|
|
28
|
+
--sans: 'Space Grotesk', sans-serif;
|
|
29
|
+
--radius-panel: 24px;
|
|
30
|
+
--radius-card: 18px;
|
|
31
|
+
--shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
* {
|
|
29
35
|
box-sizing: border-box;
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
html,
|
|
32
39
|
body {
|
|
33
40
|
margin: 0;
|
|
34
|
-
min-height:
|
|
35
|
-
font-family: var(--font-sans);
|
|
36
|
-
color: var(--text);
|
|
41
|
+
min-height: 100%;
|
|
37
42
|
background:
|
|
38
|
-
radial-gradient(circle at
|
|
39
|
-
radial-gradient(circle at
|
|
40
|
-
|
|
43
|
+
radial-gradient(circle at 18% 18%, rgba(61, 161, 255, 0.16), transparent 18rem),
|
|
44
|
+
radial-gradient(circle at 84% 14%, rgba(176, 92, 255, 0.12), transparent 22rem),
|
|
45
|
+
radial-gradient(circle at 55% 88%, rgba(84, 255, 135, 0.12), transparent 20rem),
|
|
46
|
+
linear-gradient(180deg, #020306 0%, var(--bg) 40%, var(--bg-deep) 100%);
|
|
47
|
+
color: var(--text);
|
|
48
|
+
font-family: var(--sans);
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
body {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
button,
|
|
58
|
+
input,
|
|
59
|
+
select,
|
|
60
|
+
textarea {
|
|
61
|
+
font: inherit;
|
|
62
|
+
color: inherit;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
button {
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
a {
|
|
70
|
+
color: inherit;
|
|
41
71
|
}
|
|
42
72
|
|
|
43
73
|
header {
|
|
44
|
-
padding: 1.5rem clamp(1rem, 3vw, 2rem);
|
|
45
74
|
display: flex;
|
|
46
|
-
justify-content: space-between;
|
|
47
75
|
align-items: center;
|
|
76
|
+
justify-content: space-between;
|
|
48
77
|
gap: 1rem;
|
|
78
|
+
padding: 1rem 1.4rem;
|
|
49
79
|
border-bottom: 1px solid var(--line);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
background: rgba(5, 8, 13, 0.76);
|
|
81
|
+
backdrop-filter: blur(28px);
|
|
82
|
+
position: relative;
|
|
83
|
+
z-index: 3;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.brand {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 0.9rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.brand-mark {
|
|
93
|
+
width: 1.15rem;
|
|
94
|
+
height: 1.15rem;
|
|
95
|
+
border: 1px solid rgba(84, 255, 135, 0.7);
|
|
96
|
+
transform: rotate(45deg);
|
|
97
|
+
box-shadow: 0 0 20px rgba(84, 255, 135, 0.18);
|
|
55
98
|
}
|
|
56
99
|
|
|
57
100
|
h1 {
|
|
58
101
|
margin: 0;
|
|
59
|
-
font-size: clamp(1.
|
|
60
|
-
letter-spacing: -0.
|
|
102
|
+
font-size: clamp(1.18rem, 1.8vw, 1.62rem);
|
|
103
|
+
letter-spacing: -0.04em;
|
|
61
104
|
}
|
|
62
105
|
|
|
63
106
|
.subtitle {
|
|
64
|
-
margin-top: 0.
|
|
107
|
+
margin-top: 0.15rem;
|
|
65
108
|
color: var(--muted);
|
|
66
|
-
font-size: 0.
|
|
109
|
+
font-size: 0.84rem;
|
|
67
110
|
}
|
|
68
111
|
|
|
69
|
-
.
|
|
70
|
-
|
|
112
|
+
.banner {
|
|
113
|
+
margin: 0 1rem;
|
|
114
|
+
margin-top: 0.85rem;
|
|
115
|
+
padding: 0.72rem 0.95rem;
|
|
116
|
+
border-radius: 16px;
|
|
71
117
|
border: 1px solid var(--line);
|
|
72
|
-
background:
|
|
73
|
-
|
|
74
|
-
font-size: 0.
|
|
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;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.header-actions {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
gap: 0.75rem;
|
|
140
|
+
flex-wrap: wrap;
|
|
141
|
+
justify-content: flex-end;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.status-pill,
|
|
145
|
+
.mini-pill {
|
|
75
146
|
display: inline-flex;
|
|
76
147
|
align-items: center;
|
|
77
148
|
gap: 0.55rem;
|
|
149
|
+
padding: 0.48rem 0.8rem;
|
|
150
|
+
border-radius: 999px;
|
|
151
|
+
border: 1px solid var(--line);
|
|
152
|
+
background: rgba(255, 255, 255, 0.03);
|
|
153
|
+
color: var(--muted);
|
|
154
|
+
font-size: 0.82rem;
|
|
78
155
|
}
|
|
79
156
|
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
width: 0.
|
|
83
|
-
height: 0.
|
|
157
|
+
.status-pill .dot,
|
|
158
|
+
.client-dot {
|
|
159
|
+
width: 0.58rem;
|
|
160
|
+
height: 0.58rem;
|
|
84
161
|
border-radius: 50%;
|
|
85
|
-
background: var(--
|
|
86
|
-
box-shadow: 0 0 0 0
|
|
162
|
+
background: var(--muted);
|
|
163
|
+
box-shadow: 0 0 0 0 transparent;
|
|
87
164
|
}
|
|
88
165
|
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
166
|
+
.status-pill.live .dot,
|
|
167
|
+
.client-dot.active {
|
|
168
|
+
background: var(--accent);
|
|
169
|
+
box-shadow: 0 0 12px rgba(84, 255, 135, 0.55);
|
|
92
170
|
}
|
|
93
171
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
172
|
+
.status-pill.warn .dot,
|
|
173
|
+
.client-dot.ready {
|
|
174
|
+
background: var(--amber);
|
|
175
|
+
box-shadow: 0 0 10px rgba(255, 209, 77, 0.4);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.client-dot.standby,
|
|
179
|
+
.client-dot.inferred {
|
|
180
|
+
background: var(--blue);
|
|
181
|
+
box-shadow: 0 0 10px rgba(61, 161, 255, 0.35);
|
|
98
182
|
}
|
|
99
183
|
|
|
100
|
-
.
|
|
184
|
+
.client-dot.offline {
|
|
185
|
+
background: rgba(158, 166, 188, 0.55);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.layout {
|
|
189
|
+
flex: 1;
|
|
190
|
+
min-height: 0;
|
|
101
191
|
display: grid;
|
|
102
|
-
grid-template-columns:
|
|
192
|
+
grid-template-columns: 320px minmax(0, 1fr) 410px;
|
|
103
193
|
gap: 1rem;
|
|
194
|
+
padding: 0.9rem 1rem 1rem;
|
|
104
195
|
}
|
|
105
196
|
|
|
106
|
-
.
|
|
197
|
+
.panel {
|
|
107
198
|
background: var(--panel);
|
|
108
199
|
border: 1px solid var(--line);
|
|
109
|
-
border-radius: var(--radius);
|
|
110
|
-
|
|
111
|
-
|
|
200
|
+
border-radius: var(--radius-panel);
|
|
201
|
+
box-shadow: var(--shadow);
|
|
202
|
+
overflow: hidden;
|
|
112
203
|
display: flex;
|
|
113
204
|
flex-direction: column;
|
|
114
|
-
|
|
205
|
+
min-height: 0;
|
|
115
206
|
}
|
|
116
207
|
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
208
|
+
.panel-header {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: space-between;
|
|
212
|
+
gap: 0.8rem;
|
|
213
|
+
padding: 0.85rem 1rem;
|
|
214
|
+
border-bottom: 1px solid var(--line);
|
|
215
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
|
|
122
216
|
}
|
|
123
217
|
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
218
|
+
.panel-header h2,
|
|
219
|
+
.panel-header h3 {
|
|
220
|
+
margin: 0;
|
|
221
|
+
font-size: 0.74rem;
|
|
222
|
+
letter-spacing: 0.14em;
|
|
223
|
+
text-transform: uppercase;
|
|
224
|
+
color: var(--muted);
|
|
127
225
|
}
|
|
128
226
|
|
|
129
|
-
.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
227
|
+
.panel-body {
|
|
228
|
+
flex: 1;
|
|
229
|
+
min-height: 0;
|
|
230
|
+
overflow: auto;
|
|
231
|
+
padding: 0.85rem;
|
|
232
|
+
scrollbar-width: thin;
|
|
233
|
+
scrollbar-color: rgba(188, 204, 255, 0.22) transparent;
|
|
134
234
|
}
|
|
135
235
|
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
grid-template-columns: 1.6fr 1fr;
|
|
139
|
-
gap: 1rem;
|
|
236
|
+
.panel-body.compact {
|
|
237
|
+
padding: 0.75rem;
|
|
140
238
|
}
|
|
141
239
|
|
|
142
|
-
.
|
|
240
|
+
.rail {
|
|
143
241
|
display: grid;
|
|
144
242
|
gap: 1rem;
|
|
243
|
+
min-height: 0;
|
|
145
244
|
}
|
|
146
245
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
border: 1px solid var(--line);
|
|
150
|
-
border-radius: var(--radius);
|
|
151
|
-
padding: 1rem;
|
|
152
|
-
min-height: 12rem;
|
|
246
|
+
.rail.left {
|
|
247
|
+
grid-template-rows: auto auto auto auto;
|
|
153
248
|
}
|
|
154
249
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
font-size: 1rem;
|
|
158
|
-
text-transform: uppercase;
|
|
159
|
-
letter-spacing: 0.09em;
|
|
160
|
-
color: var(--muted);
|
|
250
|
+
.rail.right {
|
|
251
|
+
grid-template-rows: 1fr auto;
|
|
161
252
|
}
|
|
162
253
|
|
|
163
|
-
.
|
|
254
|
+
.stack {
|
|
164
255
|
display: grid;
|
|
165
|
-
gap: 0.
|
|
256
|
+
gap: 0.9rem;
|
|
166
257
|
}
|
|
167
258
|
|
|
168
259
|
.card {
|
|
169
|
-
background: var(--panel-strong);
|
|
170
260
|
border: 1px solid var(--line);
|
|
171
|
-
|
|
172
|
-
|
|
261
|
+
background: var(--panel-soft);
|
|
262
|
+
border-radius: var(--radius-card);
|
|
263
|
+
padding: 0.78rem;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.card.interactive {
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
|
|
173
269
|
}
|
|
174
270
|
|
|
175
|
-
.card
|
|
271
|
+
.card.interactive:hover,
|
|
272
|
+
.card.interactive.active {
|
|
273
|
+
transform: translateY(-2px);
|
|
274
|
+
border-color: var(--line-strong);
|
|
275
|
+
background: rgba(34, 41, 61, 0.84);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.card-title {
|
|
176
279
|
display: flex;
|
|
177
280
|
justify-content: space-between;
|
|
178
|
-
align-items: flex-start;
|
|
179
281
|
gap: 0.75rem;
|
|
180
|
-
|
|
282
|
+
align-items: flex-start;
|
|
283
|
+
margin-bottom: 0.5rem;
|
|
181
284
|
}
|
|
182
285
|
|
|
183
|
-
.card
|
|
184
|
-
|
|
185
|
-
font-
|
|
286
|
+
.card-title strong {
|
|
287
|
+
font-size: 0.92rem;
|
|
288
|
+
font-weight: 600;
|
|
186
289
|
}
|
|
187
290
|
|
|
188
|
-
.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
291
|
+
.meta,
|
|
292
|
+
.mono,
|
|
293
|
+
code,
|
|
294
|
+
pre,
|
|
295
|
+
.chip,
|
|
296
|
+
.state-chip,
|
|
297
|
+
.badge {
|
|
298
|
+
font-family: var(--mono);
|
|
192
299
|
}
|
|
193
300
|
|
|
194
|
-
.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
margin-top: 0.7rem;
|
|
301
|
+
.meta {
|
|
302
|
+
color: var(--muted);
|
|
303
|
+
font-size: 0.72rem;
|
|
304
|
+
line-height: 1.5;
|
|
199
305
|
}
|
|
200
306
|
|
|
201
|
-
.
|
|
202
|
-
padding: 0.
|
|
203
|
-
border-radius:
|
|
204
|
-
font-size: 0.74rem;
|
|
307
|
+
.hero-metric {
|
|
308
|
+
padding: 0.88rem;
|
|
309
|
+
border-radius: var(--radius-card);
|
|
205
310
|
border: 1px solid var(--line);
|
|
206
|
-
background: rgba(255, 255, 255, 0.
|
|
311
|
+
background: linear-gradient(160deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
|
|
207
312
|
}
|
|
208
313
|
|
|
209
|
-
.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
font-family: var(--font-mono);
|
|
314
|
+
.hero-metric label {
|
|
315
|
+
display: block;
|
|
316
|
+
font-size: 0.68rem;
|
|
317
|
+
color: var(--muted);
|
|
214
318
|
text-transform: uppercase;
|
|
319
|
+
letter-spacing: 0.12em;
|
|
320
|
+
margin-bottom: 0.38rem;
|
|
215
321
|
}
|
|
216
322
|
|
|
217
|
-
.
|
|
218
|
-
|
|
219
|
-
|
|
323
|
+
.hero-metric strong {
|
|
324
|
+
display: block;
|
|
325
|
+
font-size: 1.62rem;
|
|
326
|
+
letter-spacing: -0.05em;
|
|
327
|
+
margin-bottom: 0.2rem;
|
|
220
328
|
}
|
|
221
329
|
|
|
222
|
-
.
|
|
223
|
-
|
|
224
|
-
|
|
330
|
+
.metrics-grid {
|
|
331
|
+
display: grid;
|
|
332
|
+
gap: 0.75rem;
|
|
333
|
+
grid-template-columns: 1fr 1fr;
|
|
225
334
|
}
|
|
226
335
|
|
|
227
|
-
.
|
|
228
|
-
|
|
229
|
-
|
|
336
|
+
.ecosystem-list,
|
|
337
|
+
.entity-list,
|
|
338
|
+
.event-list {
|
|
339
|
+
display: grid;
|
|
340
|
+
gap: 0.7rem;
|
|
230
341
|
}
|
|
231
342
|
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
343
|
+
.client-row {
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
justify-content: space-between;
|
|
347
|
+
gap: 0.75rem;
|
|
348
|
+
padding: 0.68rem 0.78rem;
|
|
349
|
+
border-radius: 16px;
|
|
350
|
+
border: 1px solid var(--line);
|
|
351
|
+
background: rgba(255, 255, 255, 0.03);
|
|
235
352
|
}
|
|
236
353
|
|
|
237
|
-
.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
354
|
+
.client-info {
|
|
355
|
+
display: flex;
|
|
356
|
+
align-items: center;
|
|
357
|
+
gap: 0.75rem;
|
|
358
|
+
min-width: 0;
|
|
242
359
|
}
|
|
243
360
|
|
|
244
|
-
.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
display: grid;
|
|
248
|
-
gap: 0.55rem;
|
|
361
|
+
.client-name {
|
|
362
|
+
font-weight: 600;
|
|
363
|
+
font-size: 0.92rem;
|
|
249
364
|
}
|
|
250
365
|
|
|
251
|
-
.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
366
|
+
.state-chip,
|
|
367
|
+
.badge {
|
|
368
|
+
display: inline-flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
justify-content: center;
|
|
371
|
+
border-radius: 999px;
|
|
372
|
+
padding: 0.2rem 0.5rem;
|
|
373
|
+
font-size: 0.68rem;
|
|
374
|
+
border: 1px solid var(--line);
|
|
375
|
+
background: rgba(255, 255, 255, 0.05);
|
|
376
|
+
text-transform: uppercase;
|
|
377
|
+
letter-spacing: 0.04em;
|
|
255
378
|
}
|
|
256
379
|
|
|
257
|
-
.
|
|
258
|
-
|
|
259
|
-
|
|
380
|
+
.state-chip.good {
|
|
381
|
+
color: var(--accent);
|
|
382
|
+
background: rgba(84, 255, 135, 0.1);
|
|
260
383
|
}
|
|
261
384
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
385
|
+
.state-chip.warn {
|
|
386
|
+
color: var(--amber);
|
|
387
|
+
background: rgba(255, 209, 77, 0.1);
|
|
266
388
|
}
|
|
267
|
-
</style>
|
|
268
|
-
</head>
|
|
269
|
-
<body>
|
|
270
|
-
<header>
|
|
271
|
-
<div>
|
|
272
|
-
<h1>Nexus Prime Runtime Console</h1>
|
|
273
|
-
<div class="subtitle">Live runs, backends, skills, workflows, verifier evidence, and release health.</div>
|
|
274
|
-
</div>
|
|
275
|
-
<div id="stream-pill" class="stream-pill">Event stream waiting</div>
|
|
276
|
-
</header>
|
|
277
389
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
<strong id="runs-count">0</strong>
|
|
283
|
-
<div class="meta" id="latest-run-state">No execution yet</div>
|
|
284
|
-
</div>
|
|
285
|
-
<div class="stat">
|
|
286
|
-
<label>Bundled Skills</label>
|
|
287
|
-
<strong id="skills-count">0</strong>
|
|
288
|
-
<div class="meta">Live, session, and base skills</div>
|
|
289
|
-
</div>
|
|
290
|
-
<div class="stat">
|
|
291
|
-
<label>Workflow Library</label>
|
|
292
|
-
<strong id="workflows-count">0</strong>
|
|
293
|
-
<div class="meta">Bundled, local, and derived workflows</div>
|
|
294
|
-
</div>
|
|
295
|
-
<div class="stat">
|
|
296
|
-
<label>Package Version</label>
|
|
297
|
-
<strong id="release-version">unknown</strong>
|
|
298
|
-
<div class="meta" id="pages-health">Pages status unknown</div>
|
|
299
|
-
</div>
|
|
300
|
-
</section>
|
|
390
|
+
.state-chip.info {
|
|
391
|
+
color: var(--blue);
|
|
392
|
+
background: rgba(61, 161, 255, 0.1);
|
|
393
|
+
}
|
|
301
394
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<div id="runs-list" class="list">
|
|
307
|
-
<div class="empty">No runs yet.</div>
|
|
308
|
-
</div>
|
|
309
|
-
</section>
|
|
395
|
+
.state-chip.bad {
|
|
396
|
+
color: var(--red);
|
|
397
|
+
background: rgba(255, 109, 99, 0.1);
|
|
398
|
+
}
|
|
310
399
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
</section>
|
|
400
|
+
.token-dial-wrap {
|
|
401
|
+
display: grid;
|
|
402
|
+
place-items: center;
|
|
403
|
+
padding: 0.6rem 0 0.2rem;
|
|
404
|
+
}
|
|
317
405
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
</section>
|
|
324
|
-
</div>
|
|
406
|
+
.token-dial {
|
|
407
|
+
position: relative;
|
|
408
|
+
width: 168px;
|
|
409
|
+
height: 168px;
|
|
410
|
+
}
|
|
325
411
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
</div>
|
|
332
|
-
</section>
|
|
412
|
+
.token-dial svg {
|
|
413
|
+
width: 168px;
|
|
414
|
+
height: 168px;
|
|
415
|
+
transform: rotate(-90deg);
|
|
416
|
+
}
|
|
333
417
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
</section>
|
|
418
|
+
.dial-bg {
|
|
419
|
+
fill: none;
|
|
420
|
+
stroke: rgba(255, 255, 255, 0.09);
|
|
421
|
+
stroke-width: 12;
|
|
422
|
+
}
|
|
340
423
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
424
|
+
.dial-fg {
|
|
425
|
+
fill: none;
|
|
426
|
+
stroke: var(--accent);
|
|
427
|
+
stroke-width: 12;
|
|
428
|
+
stroke-linecap: round;
|
|
429
|
+
stroke-dasharray: 440;
|
|
430
|
+
stroke-dashoffset: 440;
|
|
431
|
+
filter: drop-shadow(0 0 10px rgba(84, 255, 135, 0.32));
|
|
432
|
+
transition: stroke-dashoffset 300ms ease;
|
|
433
|
+
}
|
|
350
434
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
events: [],
|
|
359
|
-
};
|
|
435
|
+
.dial-center {
|
|
436
|
+
position: absolute;
|
|
437
|
+
inset: 0;
|
|
438
|
+
display: grid;
|
|
439
|
+
place-items: center;
|
|
440
|
+
text-align: center;
|
|
441
|
+
}
|
|
360
442
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
renderWorkflows();
|
|
366
|
-
renderBackends();
|
|
367
|
-
renderHealth();
|
|
368
|
-
renderEvents();
|
|
443
|
+
.dial-value {
|
|
444
|
+
font-size: 1.68rem;
|
|
445
|
+
font-weight: 700;
|
|
446
|
+
letter-spacing: -0.06em;
|
|
369
447
|
}
|
|
370
448
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
449
|
+
.dial-caption {
|
|
450
|
+
font-size: 0.66rem;
|
|
451
|
+
color: var(--muted);
|
|
452
|
+
text-transform: uppercase;
|
|
453
|
+
letter-spacing: 0.12em;
|
|
375
454
|
}
|
|
376
455
|
|
|
377
|
-
|
|
378
|
-
|
|
456
|
+
.graph-panel {
|
|
457
|
+
min-height: 0;
|
|
379
458
|
}
|
|
380
459
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
.replaceAll('>', '>');
|
|
460
|
+
.graph-toolbar {
|
|
461
|
+
display: flex;
|
|
462
|
+
gap: 0.55rem;
|
|
463
|
+
flex-wrap: wrap;
|
|
386
464
|
}
|
|
387
465
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
466
|
+
.segmented {
|
|
467
|
+
display: inline-flex;
|
|
468
|
+
gap: 0.35rem;
|
|
469
|
+
padding: 0.24rem;
|
|
470
|
+
border-radius: 999px;
|
|
471
|
+
border: 1px solid var(--line);
|
|
472
|
+
background: rgba(255, 255, 255, 0.03);
|
|
473
|
+
}
|
|
393
474
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
475
|
+
.segmented button,
|
|
476
|
+
.filter-bar button,
|
|
477
|
+
.action-bar button,
|
|
478
|
+
.ghost-button,
|
|
479
|
+
.primary-button {
|
|
480
|
+
border: 0;
|
|
481
|
+
border-radius: 999px;
|
|
482
|
+
padding: 0.42rem 0.68rem;
|
|
483
|
+
background: transparent;
|
|
484
|
+
color: var(--muted);
|
|
485
|
+
transition: background 160ms ease, color 160ms ease, transform 160ms ease;
|
|
486
|
+
font-size: 0.72rem;
|
|
487
|
+
}
|
|
409
488
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
<h3>${escapeHtml(run.goal)}</h3>
|
|
415
|
-
<div class="meta">${escapeHtml(run.runId)} · ${escapeHtml(run.artifactsPath || '')}</div>
|
|
416
|
-
</div>
|
|
417
|
-
<span class="state ${stateClass(run.state)}">${escapeHtml(run.state)}</span>
|
|
418
|
-
</div>
|
|
419
|
-
<p>${escapeHtml(run.result || 'Run recorded.')}</p>
|
|
420
|
-
<div class="chips">
|
|
421
|
-
${createChip(`workers:${run.workerResults?.length || 0}`)}
|
|
422
|
-
${createChip(`verified:${(run.workerResults || []).filter((item) => item.verified).length}`)}
|
|
423
|
-
${createChip(`memory:${run.selectedBackends?.memoryBackend || 'n/a'}`)}
|
|
424
|
-
${createChip(`compression:${run.selectedBackends?.compressionBackend || 'n/a'}`)}
|
|
425
|
-
</div>
|
|
426
|
-
</article>
|
|
427
|
-
`).join('');
|
|
489
|
+
.segmented button.active,
|
|
490
|
+
.filter-bar button.active {
|
|
491
|
+
background: rgba(84, 255, 135, 0.1);
|
|
492
|
+
color: var(--text);
|
|
428
493
|
}
|
|
429
494
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
container.innerHTML = '<div class="empty">No skills loaded.</div>';
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
495
|
+
.ghost-button {
|
|
496
|
+
border: 1px solid var(--line);
|
|
497
|
+
}
|
|
436
498
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
</div>
|
|
444
|
-
<span class="state ${stateClass(skill.rolloutStatus === 'revoked' ? 'failed' : skill.rolloutStatus === 'promoted' ? 'merged' : 'idle')}">${escapeHtml(skill.rolloutStatus)}</span>
|
|
445
|
-
</div>
|
|
446
|
-
<p>${escapeHtml((skill.instructions || '').split('\n')[0] || 'Skill artifact')}</p>
|
|
447
|
-
<div class="chips">
|
|
448
|
-
${createChip(`risk:${skill.riskClass}`)}
|
|
449
|
-
${createChip(`scope:${skill.scope}`)}
|
|
450
|
-
${skill.domain ? createChip(`domain:${skill.domain}`) : ''}
|
|
451
|
-
</div>
|
|
452
|
-
</article>
|
|
453
|
-
`).join('');
|
|
499
|
+
.ghost-button:hover,
|
|
500
|
+
.primary-button:hover,
|
|
501
|
+
.segmented button:hover,
|
|
502
|
+
.filter-bar button:hover {
|
|
503
|
+
transform: translateY(-1px);
|
|
504
|
+
color: var(--text);
|
|
454
505
|
}
|
|
455
506
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
507
|
+
.primary-button {
|
|
508
|
+
background: linear-gradient(135deg, rgba(84, 255, 135, 0.22), rgba(61, 161, 255, 0.16));
|
|
509
|
+
border: 1px solid rgba(84, 255, 135, 0.2);
|
|
510
|
+
color: var(--text);
|
|
511
|
+
}
|
|
462
512
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
<div class="chips">
|
|
474
|
-
${createChip(`domain:${workflow.domain}`)}
|
|
475
|
-
${createChip(`steps:${(workflow.steps || []).length}`)}
|
|
476
|
-
${createChip(`scope:${workflow.scope}`)}
|
|
477
|
-
</div>
|
|
478
|
-
</article>
|
|
479
|
-
`).join('');
|
|
513
|
+
#graph-stage {
|
|
514
|
+
position: relative;
|
|
515
|
+
min-height: 0;
|
|
516
|
+
height: 100%;
|
|
517
|
+
border-radius: 22px;
|
|
518
|
+
border: 1px solid var(--line);
|
|
519
|
+
background:
|
|
520
|
+
radial-gradient(circle at 50% 50%, rgba(84, 255, 135, 0.05), transparent 28rem),
|
|
521
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
|
|
522
|
+
overflow: hidden;
|
|
480
523
|
}
|
|
481
524
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
525
|
+
#graph-canvas {
|
|
526
|
+
width: 100%;
|
|
527
|
+
height: 100%;
|
|
528
|
+
display: block;
|
|
529
|
+
}
|
|
489
530
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
</article>
|
|
499
|
-
`).join('');
|
|
531
|
+
.graph-empty {
|
|
532
|
+
position: absolute;
|
|
533
|
+
inset: 0;
|
|
534
|
+
display: grid;
|
|
535
|
+
place-items: center;
|
|
536
|
+
color: var(--muted);
|
|
537
|
+
text-align: center;
|
|
538
|
+
padding: 1rem;
|
|
500
539
|
}
|
|
501
540
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
541
|
+
.node-label {
|
|
542
|
+
font-family: var(--mono);
|
|
543
|
+
font-size: 9.5px;
|
|
544
|
+
fill: rgba(246, 247, 251, 0.86);
|
|
545
|
+
pointer-events: none;
|
|
546
|
+
}
|
|
509
547
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
<h3>Runtime</h3>
|
|
514
|
-
<span class="state ${stateClass(health.runtime?.runtime || 'failed')}">${escapeHtml(health.runtime?.runtime || 'unknown')}</span>
|
|
515
|
-
</div>
|
|
516
|
-
<div class="chips">
|
|
517
|
-
${createChip(`runs:${health.runtime?.runsTracked || 0}`)}
|
|
518
|
-
${createChip(`skills:${health.runtime?.skills || 0}`)}
|
|
519
|
-
${createChip(`workflows:${health.runtime?.workflows || 0}`)}
|
|
520
|
-
</div>
|
|
521
|
-
</article>
|
|
522
|
-
<article class="card">
|
|
523
|
-
<div class="card-header">
|
|
524
|
-
<h3>Docs / Release</h3>
|
|
525
|
-
</div>
|
|
526
|
-
<div class="chips">
|
|
527
|
-
${createChip(`version:${health.release?.packageVersion || 'unknown'}`)}
|
|
528
|
-
${createChip(`docs:${health.docs?.present ? 'present' : 'missing'}`)}
|
|
529
|
-
${createChip(`pages:${health.docs?.pagesWorkflowValid ? 'valid' : 'broken'}`)}
|
|
530
|
-
${createChip(`stream:${health.connection?.stream || 'unknown'}`)}
|
|
531
|
-
</div>
|
|
532
|
-
</article>
|
|
533
|
-
`;
|
|
548
|
+
.graph-link {
|
|
549
|
+
stroke: rgba(188, 204, 255, 0.22);
|
|
550
|
+
stroke-width: 1.2;
|
|
534
551
|
}
|
|
535
552
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
container.innerHTML = '<div class="empty">Waiting for runtime activity.</div>';
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
553
|
+
.graph-node {
|
|
554
|
+
cursor: pointer;
|
|
555
|
+
}
|
|
542
556
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
<pre>${escapeHtml(JSON.stringify(event.data, null, 2))}</pre>
|
|
548
|
-
</div>
|
|
549
|
-
`).join('');
|
|
557
|
+
.graph-node circle {
|
|
558
|
+
stroke: rgba(255, 255, 255, 0.1);
|
|
559
|
+
stroke-width: 1.5;
|
|
560
|
+
transition: transform 160ms ease, stroke 160ms ease;
|
|
550
561
|
}
|
|
551
562
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
return response.json();
|
|
563
|
+
.graph-node:hover circle,
|
|
564
|
+
.graph-node.active circle {
|
|
565
|
+
stroke: rgba(255, 255, 255, 0.32);
|
|
556
566
|
}
|
|
557
567
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
loadJson('/api/backends'),
|
|
564
|
-
loadJson('/api/health'),
|
|
565
|
-
]);
|
|
566
|
-
state.runs = Array.isArray(runs) ? runs : [];
|
|
567
|
-
state.skills = Array.isArray(skills) ? skills : [];
|
|
568
|
-
state.workflows = Array.isArray(workflows) ? workflows : [];
|
|
569
|
-
state.backends = backends || {};
|
|
570
|
-
state.health = health || {};
|
|
571
|
-
render();
|
|
568
|
+
.graph-node .halo {
|
|
569
|
+
fill: transparent;
|
|
570
|
+
stroke: rgba(84, 255, 135, 0.24);
|
|
571
|
+
stroke-width: 10;
|
|
572
|
+
opacity: 0;
|
|
572
573
|
}
|
|
573
574
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
} catch (error) {
|
|
578
|
-
console.error(error);
|
|
579
|
-
}
|
|
575
|
+
.graph-node.active .halo {
|
|
576
|
+
opacity: 1;
|
|
577
|
+
}
|
|
580
578
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
579
|
+
.graph-note {
|
|
580
|
+
color: var(--muted);
|
|
581
|
+
font-size: 0.72rem;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.graph-footer {
|
|
585
|
+
display: grid;
|
|
586
|
+
grid-template-columns: 1fr auto;
|
|
587
|
+
gap: 0.9rem;
|
|
588
|
+
align-items: center;
|
|
589
|
+
margin-top: 0.9rem;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.graph-stats {
|
|
593
|
+
display: flex;
|
|
594
|
+
flex-wrap: wrap;
|
|
595
|
+
gap: 0.45rem;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.chip {
|
|
599
|
+
display: inline-flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
gap: 0.35rem;
|
|
602
|
+
padding: 0.24rem 0.48rem;
|
|
603
|
+
border-radius: 999px;
|
|
604
|
+
border: 1px solid var(--line);
|
|
605
|
+
background: rgba(255, 255, 255, 0.05);
|
|
606
|
+
font-size: 0.68rem;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.entity-header {
|
|
610
|
+
display: flex;
|
|
611
|
+
align-items: center;
|
|
612
|
+
justify-content: space-between;
|
|
613
|
+
gap: 0.75rem;
|
|
614
|
+
margin-bottom: 0.8rem;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.entity-header h3 {
|
|
618
|
+
margin: 0;
|
|
619
|
+
font-size: 0.82rem;
|
|
620
|
+
letter-spacing: 0.08em;
|
|
621
|
+
text-transform: uppercase;
|
|
622
|
+
color: var(--muted);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.filter-bar {
|
|
626
|
+
display: flex;
|
|
627
|
+
flex-wrap: wrap;
|
|
628
|
+
gap: 0.35rem;
|
|
629
|
+
justify-content: flex-end;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.event-card {
|
|
633
|
+
border-radius: 18px;
|
|
634
|
+
border: 1px solid var(--line);
|
|
635
|
+
background: rgba(255, 255, 255, 0.03);
|
|
636
|
+
padding: 0.78rem;
|
|
637
|
+
border-left: 3px solid var(--muted);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.event-card.good { border-left-color: var(--accent); }
|
|
641
|
+
.event-card.warn { border-left-color: var(--amber); }
|
|
642
|
+
.event-card.bad { border-left-color: var(--red); }
|
|
643
|
+
.event-card.info { border-left-color: var(--blue); }
|
|
644
|
+
|
|
645
|
+
.event-head {
|
|
646
|
+
display: flex;
|
|
647
|
+
justify-content: space-between;
|
|
648
|
+
gap: 0.75rem;
|
|
649
|
+
margin-bottom: 0.45rem;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.event-head strong {
|
|
653
|
+
display: block;
|
|
654
|
+
font-size: 0.88rem;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.event-head .meta {
|
|
658
|
+
margin-top: 0.1rem;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
details.raw {
|
|
662
|
+
margin-top: 0.45rem;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
details.raw summary {
|
|
666
|
+
cursor: pointer;
|
|
667
|
+
color: var(--muted);
|
|
668
|
+
font-size: 0.68rem;
|
|
669
|
+
text-transform: uppercase;
|
|
670
|
+
letter-spacing: 0.08em;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
pre {
|
|
674
|
+
overflow: auto;
|
|
675
|
+
padding: 0.68rem;
|
|
676
|
+
border-radius: 14px;
|
|
677
|
+
background: rgba(0, 0, 0, 0.3);
|
|
678
|
+
border: 1px solid var(--line);
|
|
679
|
+
font-size: 0.7rem;
|
|
680
|
+
line-height: 1.5;
|
|
681
|
+
margin: 0.55rem 0 0;
|
|
682
|
+
white-space: pre-wrap;
|
|
683
|
+
word-break: break-word;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.control-grid {
|
|
687
|
+
display: grid;
|
|
688
|
+
gap: 0.75rem;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.field {
|
|
692
|
+
display: grid;
|
|
693
|
+
gap: 0.35rem;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.field label {
|
|
697
|
+
font-size: 0.68rem;
|
|
698
|
+
color: var(--muted);
|
|
699
|
+
text-transform: uppercase;
|
|
700
|
+
letter-spacing: 0.08em;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.field input,
|
|
704
|
+
.field select,
|
|
705
|
+
.field textarea {
|
|
706
|
+
width: 100%;
|
|
707
|
+
padding: 0.62rem 0.72rem;
|
|
708
|
+
border-radius: 14px;
|
|
709
|
+
border: 1px solid var(--line);
|
|
710
|
+
background: rgba(255, 255, 255, 0.03);
|
|
711
|
+
outline: none;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.field textarea {
|
|
715
|
+
min-height: 5.4rem;
|
|
716
|
+
resize: vertical;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.inline-fields {
|
|
720
|
+
display: grid;
|
|
721
|
+
gap: 0.7rem;
|
|
722
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.action-bar {
|
|
726
|
+
display: flex;
|
|
727
|
+
flex-wrap: wrap;
|
|
728
|
+
gap: 0.55rem;
|
|
729
|
+
margin-top: 0.4rem;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.drawer {
|
|
733
|
+
position: fixed;
|
|
734
|
+
top: 0;
|
|
735
|
+
right: 0;
|
|
736
|
+
width: min(420px, 92vw);
|
|
737
|
+
height: 100vh;
|
|
738
|
+
padding: 1rem;
|
|
739
|
+
background: rgba(4, 6, 11, 0.88);
|
|
740
|
+
backdrop-filter: blur(28px);
|
|
741
|
+
border-left: 1px solid var(--line);
|
|
742
|
+
transform: translateX(100%);
|
|
743
|
+
transition: transform 220ms ease;
|
|
744
|
+
z-index: 20;
|
|
745
|
+
display: flex;
|
|
746
|
+
flex-direction: column;
|
|
747
|
+
gap: 1rem;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.drawer.open {
|
|
751
|
+
transform: translateX(0);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.drawer-header {
|
|
755
|
+
display: flex;
|
|
756
|
+
justify-content: space-between;
|
|
757
|
+
gap: 0.75rem;
|
|
758
|
+
align-items: flex-start;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.drawer-body {
|
|
762
|
+
min-height: 0;
|
|
763
|
+
overflow: auto;
|
|
764
|
+
display: grid;
|
|
765
|
+
gap: 0.8rem;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.drawer-section {
|
|
769
|
+
border: 1px solid var(--line);
|
|
770
|
+
background: rgba(255, 255, 255, 0.03);
|
|
771
|
+
border-radius: 18px;
|
|
772
|
+
padding: 0.78rem;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.drawer-section h4 {
|
|
776
|
+
margin: 0 0 0.55rem;
|
|
777
|
+
font-size: 0.74rem;
|
|
778
|
+
color: var(--muted);
|
|
779
|
+
text-transform: uppercase;
|
|
780
|
+
letter-spacing: 0.08em;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.empty {
|
|
784
|
+
padding: 1rem;
|
|
785
|
+
border-radius: 18px;
|
|
786
|
+
border: 1px dashed var(--line);
|
|
787
|
+
color: var(--muted);
|
|
788
|
+
text-align: center;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.list-inline {
|
|
792
|
+
display: flex;
|
|
793
|
+
flex-wrap: wrap;
|
|
794
|
+
gap: 0.45rem;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.library-grid {
|
|
798
|
+
display: grid;
|
|
799
|
+
gap: 0.7rem;
|
|
800
|
+
max-height: 15rem;
|
|
801
|
+
overflow: auto;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.hidden {
|
|
805
|
+
display: none !important;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
@media (max-width: 1440px) {
|
|
809
|
+
.layout {
|
|
810
|
+
grid-template-columns: 290px minmax(0, 1fr) 360px;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
@media (max-width: 1180px) {
|
|
815
|
+
body {
|
|
816
|
+
overflow: auto;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.layout {
|
|
820
|
+
grid-template-columns: 1fr;
|
|
821
|
+
height: auto;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.rail.left,
|
|
825
|
+
.rail.right {
|
|
826
|
+
grid-template-rows: none;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.graph-panel {
|
|
830
|
+
min-height: 34rem;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
@media (max-width: 720px) {
|
|
835
|
+
header {
|
|
836
|
+
align-items: flex-start;
|
|
837
|
+
flex-direction: column;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.inline-fields,
|
|
841
|
+
.metrics-grid {
|
|
842
|
+
grid-template-columns: 1fr;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
</style>
|
|
846
|
+
</head>
|
|
847
|
+
<body>
|
|
848
|
+
<header>
|
|
849
|
+
<div class="brand">
|
|
850
|
+
<div class="brand-mark" aria-hidden="true"></div>
|
|
851
|
+
<div>
|
|
852
|
+
<h1>Nexus Prime</h1>
|
|
853
|
+
<div class="subtitle">Topology-first runtime console for memory, pod network, skills, workflows, and live execution.</div>
|
|
854
|
+
</div>
|
|
855
|
+
</div>
|
|
856
|
+
<div class="header-actions">
|
|
857
|
+
<div id="sync-pill" class="status-pill">
|
|
858
|
+
<span class="dot"></span>
|
|
859
|
+
<span id="sync-label">Connecting stream</span>
|
|
860
|
+
</div>
|
|
861
|
+
<div class="mini-pill">
|
|
862
|
+
<span class="mono" id="header-version">v?</span>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
</header>
|
|
866
|
+
|
|
867
|
+
<div id="status-banner" class="banner hidden" role="status" aria-live="polite"></div>
|
|
868
|
+
|
|
869
|
+
<main class="layout">
|
|
870
|
+
<aside class="rail left">
|
|
871
|
+
<section class="panel">
|
|
872
|
+
<div class="panel-header">
|
|
873
|
+
<h2>Connected Ecosystem</h2>
|
|
874
|
+
<span class="badge" id="clients-summary">0 visible</span>
|
|
875
|
+
</div>
|
|
876
|
+
<div class="panel-body compact">
|
|
877
|
+
<div id="clients-list" class="ecosystem-list">
|
|
878
|
+
<div class="empty">Client telemetry loading.</div>
|
|
879
|
+
</div>
|
|
880
|
+
</div>
|
|
881
|
+
</section>
|
|
882
|
+
|
|
883
|
+
<section class="panel">
|
|
884
|
+
<div class="panel-header">
|
|
885
|
+
<h2>Token Supremacy</h2>
|
|
886
|
+
<span class="badge" id="token-summary">0 events</span>
|
|
887
|
+
</div>
|
|
888
|
+
<div class="panel-body">
|
|
889
|
+
<div class="token-dial-wrap">
|
|
890
|
+
<div class="token-dial">
|
|
891
|
+
<svg viewBox="0 0 168 168" aria-hidden="true">
|
|
892
|
+
<circle class="dial-bg" cx="84" cy="84" r="70"></circle>
|
|
893
|
+
<circle id="dial-progress" class="dial-fg" cx="84" cy="84" r="70"></circle>
|
|
894
|
+
</svg>
|
|
895
|
+
<div class="dial-center">
|
|
896
|
+
<div id="dial-value" class="dial-value">0%</div>
|
|
897
|
+
<div class="dial-caption">Avg compression</div>
|
|
898
|
+
</div>
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
901
|
+
<div class="metrics-grid">
|
|
902
|
+
<div class="hero-metric">
|
|
903
|
+
<label>Gross Tokens</label>
|
|
904
|
+
<strong id="gross-tokens">0</strong>
|
|
905
|
+
<div class="meta">Observed from token and CAS events</div>
|
|
906
|
+
</div>
|
|
907
|
+
<div class="hero-metric">
|
|
908
|
+
<label>Tokens Saved</label>
|
|
909
|
+
<strong id="saved-tokens">0</strong>
|
|
910
|
+
<div class="meta">Optimizer and CAS compression savings</div>
|
|
911
|
+
</div>
|
|
912
|
+
<div class="hero-metric">
|
|
913
|
+
<label>Net Forwarded</label>
|
|
914
|
+
<strong id="net-tokens">0</strong>
|
|
915
|
+
<div class="meta">Estimated after compression</div>
|
|
916
|
+
</div>
|
|
917
|
+
<div class="hero-metric">
|
|
918
|
+
<label>Memory Nodes</label>
|
|
919
|
+
<strong id="memory-count">0</strong>
|
|
920
|
+
<div class="meta">Snapshots available to inspect</div>
|
|
921
|
+
</div>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
</section>
|
|
925
|
+
|
|
926
|
+
<section class="panel">
|
|
927
|
+
<div class="panel-header">
|
|
928
|
+
<h2>System Signals</h2>
|
|
929
|
+
<span class="badge" id="memory-tier-summary">0 cortex</span>
|
|
930
|
+
</div>
|
|
931
|
+
<div class="panel-body">
|
|
932
|
+
<div class="stack">
|
|
933
|
+
<div class="hero-metric">
|
|
934
|
+
<label>Tracked Runs</label>
|
|
935
|
+
<strong id="runs-count">0</strong>
|
|
936
|
+
<div class="meta" id="latest-run-state">No runs yet</div>
|
|
937
|
+
</div>
|
|
938
|
+
<div class="hero-metric">
|
|
939
|
+
<label>Skills / Workflows</label>
|
|
940
|
+
<strong id="artifact-count">0 / 0</strong>
|
|
941
|
+
<div class="meta" id="artifact-summary">Runtime library loading</div>
|
|
942
|
+
</div>
|
|
943
|
+
<div class="hero-metric">
|
|
944
|
+
<label>Release Health</label>
|
|
945
|
+
<strong id="docs-health">Unknown</strong>
|
|
946
|
+
<div class="meta" id="ci-health">Pages and CI not loaded yet</div>
|
|
947
|
+
</div>
|
|
948
|
+
</div>
|
|
949
|
+
</div>
|
|
950
|
+
</section>
|
|
951
|
+
|
|
952
|
+
<section class="panel">
|
|
953
|
+
<div class="panel-header">
|
|
954
|
+
<h2>POD Network</h2>
|
|
955
|
+
<span class="badge" id="pod-summary">0 workers</span>
|
|
956
|
+
</div>
|
|
957
|
+
<div class="panel-body compact">
|
|
958
|
+
<div id="pod-highlights" class="stack">
|
|
959
|
+
<div class="empty">Waiting for POD traffic.</div>
|
|
960
|
+
</div>
|
|
961
|
+
</div>
|
|
962
|
+
</section>
|
|
963
|
+
</aside>
|
|
964
|
+
|
|
965
|
+
<section class="panel graph-panel">
|
|
966
|
+
<div class="panel-header">
|
|
967
|
+
<div>
|
|
968
|
+
<h2 id="graph-title">Memory Topology Graph</h2>
|
|
969
|
+
<div class="meta" id="graph-subtitle">Touch a memory to open the inspector and timeline.</div>
|
|
970
|
+
</div>
|
|
971
|
+
<div class="graph-toolbar">
|
|
972
|
+
<div id="graph-modes" class="segmented" aria-label="graph modes">
|
|
973
|
+
<button data-graph-mode="memory" class="active">Memory</button>
|
|
974
|
+
<button data-graph-mode="runs">Runs</button>
|
|
975
|
+
<button data-graph-mode="pod">POD</button>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
<div class="panel-body">
|
|
980
|
+
<div id="graph-stage">
|
|
981
|
+
<svg id="graph-canvas" viewBox="0 0 980 620" preserveAspectRatio="xMidYMid meet"></svg>
|
|
982
|
+
<div id="graph-empty" class="graph-empty hidden">No topology data yet.</div>
|
|
983
|
+
</div>
|
|
984
|
+
<div class="graph-footer">
|
|
985
|
+
<div id="graph-stats" class="graph-stats"></div>
|
|
986
|
+
<div class="graph-note" id="graph-note">Memory graph combines semantic, lineage, and artifact-derived links.</div>
|
|
987
|
+
</div>
|
|
988
|
+
<div class="panel" style="margin-top: 1rem; min-height: 16rem;">
|
|
989
|
+
<div class="panel-header">
|
|
990
|
+
<div class="entity-header" style="margin:0;">
|
|
991
|
+
<h3 id="library-title">Memory Snapshots</h3>
|
|
992
|
+
<div id="library-tabs" class="segmented">
|
|
993
|
+
<button data-library-mode="memories" class="active">Memories</button>
|
|
994
|
+
<button data-library-mode="skills">Skills</button>
|
|
995
|
+
<button data-library-mode="workflows">Workflows</button>
|
|
996
|
+
<button data-library-mode="pod">POD</button>
|
|
997
|
+
<button data-library-mode="clients">Clients</button>
|
|
998
|
+
</div>
|
|
999
|
+
</div>
|
|
1000
|
+
</div>
|
|
1001
|
+
<div class="panel-body compact">
|
|
1002
|
+
<div id="library-list" class="library-grid">
|
|
1003
|
+
<div class="empty">Loading library view.</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
</div>
|
|
1006
|
+
</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
</section>
|
|
1009
|
+
|
|
1010
|
+
<aside class="rail right">
|
|
1011
|
+
<section class="panel">
|
|
1012
|
+
<div class="panel-header">
|
|
1013
|
+
<div>
|
|
1014
|
+
<h2>Neural Stream HUD</h2>
|
|
1015
|
+
<div class="meta" id="events-summary">0 signals</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
<div id="event-filters" class="filter-bar">
|
|
1018
|
+
<button data-event-filter="all" class="active">All</button>
|
|
1019
|
+
<button data-event-filter="memory">Memory</button>
|
|
1020
|
+
<button data-event-filter="tokens">Tokens</button>
|
|
1021
|
+
<button data-event-filter="runtime">Runtime</button>
|
|
1022
|
+
<button data-event-filter="pod">POD</button>
|
|
1023
|
+
<button data-event-filter="skills">Skills</button>
|
|
1024
|
+
<button data-event-filter="workflows">Flows</button>
|
|
1025
|
+
<button data-event-filter="clients">Clients</button>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
<div class="panel-body compact">
|
|
1029
|
+
<div id="event-list" class="event-list">
|
|
1030
|
+
<div class="empty">Waiting for runtime activity.</div>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
</section>
|
|
1034
|
+
|
|
1035
|
+
<section class="panel">
|
|
1036
|
+
<div class="panel-header">
|
|
1037
|
+
<h2>Local Control Plane</h2>
|
|
1038
|
+
<span class="badge">Guarded</span>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="panel-body">
|
|
1041
|
+
<form id="execute-form" class="control-grid">
|
|
1042
|
+
<div class="field">
|
|
1043
|
+
<label for="goal-input">Goal</label>
|
|
1044
|
+
<textarea id="goal-input" placeholder="Run a bounded local task through the runtime."></textarea>
|
|
1045
|
+
</div>
|
|
1046
|
+
<div class="field">
|
|
1047
|
+
<label for="files-input">Files</label>
|
|
1048
|
+
<input id="files-input" type="text" placeholder="src/a.ts, src/b.ts">
|
|
1049
|
+
</div>
|
|
1050
|
+
<div class="field">
|
|
1051
|
+
<label for="skills-input">Skills</label>
|
|
1052
|
+
<input id="skills-input" type="text" placeholder="backend-playbook, orchestration-playbook">
|
|
1053
|
+
</div>
|
|
1054
|
+
<div class="field">
|
|
1055
|
+
<label for="workflows-input">Workflows</label>
|
|
1056
|
+
<input id="workflows-input" type="text" placeholder="backend-execution-loop">
|
|
1057
|
+
</div>
|
|
1058
|
+
<div class="inline-fields">
|
|
1059
|
+
<div class="field">
|
|
1060
|
+
<label for="workers-input">Workers</label>
|
|
1061
|
+
<input id="workers-input" type="number" min="1" max="7" value="2">
|
|
1062
|
+
</div>
|
|
1063
|
+
<div class="field">
|
|
1064
|
+
<label for="memory-backend">Memory Backend</label>
|
|
1065
|
+
<select id="memory-backend"></select>
|
|
1066
|
+
</div>
|
|
1067
|
+
<div class="field">
|
|
1068
|
+
<label for="compression-backend">Compression Backend</label>
|
|
1069
|
+
<select id="compression-backend"></select>
|
|
1070
|
+
</div>
|
|
1071
|
+
<div class="field">
|
|
1072
|
+
<label for="dsl-backend">DSL Compiler</label>
|
|
1073
|
+
<select id="dsl-backend"></select>
|
|
1074
|
+
</div>
|
|
1075
|
+
</div>
|
|
1076
|
+
<div class="action-bar">
|
|
1077
|
+
<button type="submit" class="primary-button">Execute Run</button>
|
|
1078
|
+
<button id="refresh-button" type="button" class="ghost-button">Refresh All</button>
|
|
1079
|
+
</div>
|
|
1080
|
+
<div id="control-status" class="meta">Dashboard actions stay local and route through the runtime.</div>
|
|
1081
|
+
</form>
|
|
1082
|
+
</div>
|
|
1083
|
+
</section>
|
|
1084
|
+
</aside>
|
|
1085
|
+
</main>
|
|
1086
|
+
|
|
1087
|
+
<aside id="drawer" class="drawer" aria-live="polite">
|
|
1088
|
+
<div class="drawer-header">
|
|
1089
|
+
<div>
|
|
1090
|
+
<h2 id="drawer-title" style="margin:0;">Inspector</h2>
|
|
1091
|
+
<div id="drawer-subtitle" class="meta">Select a node or card.</div>
|
|
1092
|
+
</div>
|
|
1093
|
+
<button id="drawer-close" class="ghost-button" type="button">Close</button>
|
|
1094
|
+
</div>
|
|
1095
|
+
<div id="drawer-body" class="drawer-body">
|
|
1096
|
+
<div class="empty">Touch a memory, run, workflow, skill, POD worker, or client to inspect it.</div>
|
|
1097
|
+
</div>
|
|
1098
|
+
</aside>
|
|
1099
|
+
|
|
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
|
+
|
|
1108
|
+
const state = {
|
|
1109
|
+
runs: [],
|
|
1110
|
+
skills: [],
|
|
1111
|
+
workflows: [],
|
|
1112
|
+
backends: {},
|
|
1113
|
+
health: {},
|
|
1114
|
+
memories: [],
|
|
1115
|
+
memoryDetail: null,
|
|
1116
|
+
memoryNetwork: { nodes: [], links: [], focusId: null },
|
|
1117
|
+
pod: { messages: [], activeWorkers: [], tagClusters: [], confidenceBands: { high: 0, medium: 0, low: 0 } },
|
|
1118
|
+
clients: [],
|
|
1119
|
+
events: [],
|
|
1120
|
+
graphMode: 'memory',
|
|
1121
|
+
libraryMode: 'memories',
|
|
1122
|
+
eventFilter: 'all',
|
|
1123
|
+
selected: null,
|
|
1124
|
+
streamConnected: false,
|
|
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
|
+
},
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
const graphModes = {
|
|
1143
|
+
memory: { title: 'Memory Topology Graph', subtitle: 'Touch a memory to open the inspector and timeline.' },
|
|
1144
|
+
runs: { title: 'Run Execution Graph', subtitle: 'Run, worker, workflow, and backend topology from recent executions.' },
|
|
1145
|
+
pod: { title: 'POD Network Graph', subtitle: 'Worker/tag clusters and confidence across the live pod mesh.' },
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
function $(id) {
|
|
1149
|
+
return document.getElementById(id);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function escapeHtml(value) {
|
|
1153
|
+
return String(value ?? '')
|
|
1154
|
+
.replaceAll('&', '&')
|
|
1155
|
+
.replaceAll('<', '<')
|
|
1156
|
+
.replaceAll('>', '>');
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function formatNumber(value) {
|
|
1160
|
+
return new Intl.NumberFormat().format(Number(value || 0));
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function formatDate(value) {
|
|
1164
|
+
if (!value) return 'n/a';
|
|
1165
|
+
try {
|
|
1166
|
+
return new Date(value).toLocaleString();
|
|
1167
|
+
} catch {
|
|
1168
|
+
return 'n/a';
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function formatAgo(value) {
|
|
1173
|
+
if (!value) return 'n/a';
|
|
1174
|
+
const diff = Date.now() - value;
|
|
1175
|
+
const mins = Math.round(diff / 60000);
|
|
1176
|
+
if (mins < 1) return 'just now';
|
|
1177
|
+
if (mins < 60) return `${mins}m ago`;
|
|
1178
|
+
const hours = Math.round(mins / 60);
|
|
1179
|
+
if (hours < 24) return `${hours}h ago`;
|
|
1180
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function chip(text) {
|
|
1184
|
+
return `<span class="chip">${escapeHtml(text)}</span>`;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function stateClass(status) {
|
|
1188
|
+
if (['merged', 'healthy', 'active', 'promoted', 'hot', 'connected', 'validated'].includes(status)) return 'good';
|
|
1189
|
+
if (['ready', 'standby', 'rolled_back', 'inferred', 'staged', 'idle'].includes(status)) return 'warn';
|
|
1190
|
+
if (['revoked', 'failed', 'rejected', 'offline'].includes(status)) return 'bad';
|
|
1191
|
+
return 'info';
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function parseList(value) {
|
|
1195
|
+
return String(value || '')
|
|
1196
|
+
.split(',')
|
|
1197
|
+
.map((item) => item.trim())
|
|
1198
|
+
.filter(Boolean);
|
|
1199
|
+
}
|
|
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
|
+
|
|
1399
|
+
async function fetchJson(url, options) {
|
|
1400
|
+
const res = await fetch(url, options);
|
|
1401
|
+
const text = await res.text();
|
|
1402
|
+
if (!res.ok) {
|
|
1403
|
+
throw new Error(`${res.status} ${res.statusText}${text ? ` · ${text.slice(0, 120)}` : ''}`);
|
|
1404
|
+
}
|
|
1405
|
+
return text ? JSON.parse(text) : null;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
async function refreshAll() {
|
|
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
|
+
});
|
|
1438
|
+
|
|
1439
|
+
if (refreshed) {
|
|
1440
|
+
state.lastRefreshAt = Date.now();
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const focusMemoryId = state.selected?.kind === 'memory'
|
|
1444
|
+
? state.selected.id
|
|
1445
|
+
: state.memories[0]?.id;
|
|
1446
|
+
|
|
1447
|
+
if (focusMemoryId && !resourceFailed('memory')) {
|
|
1448
|
+
await refreshMemorySelection(focusMemoryId, false);
|
|
1449
|
+
} else if (!focusMemoryId) {
|
|
1450
|
+
state.memoryDetail = null;
|
|
1451
|
+
state.memoryNetwork = { nodes: [], links: [], focusId: null };
|
|
1452
|
+
setResourceStatus('memoryDetail', 'idle');
|
|
1453
|
+
setResourceStatus('memoryNetwork', 'idle');
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
reconcileBanner();
|
|
1457
|
+
populateBackendSelects();
|
|
1458
|
+
render();
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
async function refreshMemorySelection(memoryId, openDrawer = true) {
|
|
1462
|
+
const [detail, network] = await Promise.allSettled([
|
|
1463
|
+
fetchJson(`/api/memory/${encodeURIComponent(memoryId)}`),
|
|
1464
|
+
fetchJson(`/api/memory/${encodeURIComponent(memoryId)}/network?depth=2&limit=18`),
|
|
1465
|
+
]);
|
|
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
|
+
|
|
1492
|
+
if (openDrawer) {
|
|
1493
|
+
state.selected = { kind: 'memory', id: memoryId, data: state.memoryDetail };
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
function populateBackendSelects() {
|
|
1498
|
+
const mapping = [
|
|
1499
|
+
['memory-backend', state.backends.memory || [], 'sqlite-memory'],
|
|
1500
|
+
['compression-backend', state.backends.compression || [], 'deterministic-reading-plan'],
|
|
1501
|
+
['dsl-backend', state.backends.dsl || [], 'deterministic-nxl-compiler'],
|
|
1502
|
+
];
|
|
1503
|
+
|
|
1504
|
+
for (const [id, entries, defaultKind] of mapping) {
|
|
1505
|
+
const select = $(id);
|
|
1506
|
+
if (!select) continue;
|
|
1507
|
+
const current = select.value;
|
|
1508
|
+
const options = [{ kind: '', mode: 'default', label: 'Default' }, ...entries];
|
|
1509
|
+
select.innerHTML = options.map((entry) => {
|
|
1510
|
+
const value = entry.kind || '';
|
|
1511
|
+
const label = entry.label || (entry.kind ? `${entry.kind} (${entry.mode})` : 'Default');
|
|
1512
|
+
return `<option value="${escapeHtml(value)}">${escapeHtml(label)}</option>`;
|
|
1513
|
+
}).join('');
|
|
1514
|
+
select.value = current || defaultKind || '';
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function computeTokenMetrics() {
|
|
1519
|
+
const tokenEvents = state.events.filter((event) => event.category === 'tokens');
|
|
1520
|
+
let gross = 0;
|
|
1521
|
+
let saved = 0;
|
|
1522
|
+
let forwarded = 0;
|
|
1523
|
+
const ratios = [];
|
|
1524
|
+
|
|
1525
|
+
for (const event of tokenEvents) {
|
|
1526
|
+
const payload = event.payload || {};
|
|
1527
|
+
if (typeof payload.inputTokens === 'number') gross += payload.inputTokens;
|
|
1528
|
+
if (typeof payload.outputTokens === 'number') forwarded += payload.outputTokens;
|
|
1529
|
+
if (typeof payload.savings === 'number') saved += payload.savings;
|
|
1530
|
+
if (typeof payload.compressionRatio === 'number' && payload.compressionRatio > 0) {
|
|
1531
|
+
ratios.push(Math.min(99, Math.max(1, Math.round((1 - (1 / payload.compressionRatio)) * 100))));
|
|
1532
|
+
}
|
|
1533
|
+
if (typeof payload.pct === 'number') ratios.push(payload.pct);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
return {
|
|
1537
|
+
gross,
|
|
1538
|
+
saved,
|
|
1539
|
+
forwarded,
|
|
1540
|
+
ratio: ratios.length ? Math.round(ratios.reduce((sum, value) => sum + value, 0) / ratios.length) : 0,
|
|
1541
|
+
events: tokenEvents.length,
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function render() {
|
|
1546
|
+
renderBanner();
|
|
1547
|
+
renderHeader();
|
|
1548
|
+
renderLeftRail();
|
|
1549
|
+
renderGraph();
|
|
1550
|
+
renderLibrary();
|
|
1551
|
+
renderEvents();
|
|
1552
|
+
renderDrawer();
|
|
1553
|
+
}
|
|
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
|
+
|
|
1567
|
+
function renderHeader() {
|
|
1568
|
+
$('header-version').textContent = `v${state.health.release?.packageVersion || '?'}`;
|
|
1569
|
+
const pill = $('sync-pill');
|
|
1570
|
+
const failedResources = countFailedResources();
|
|
1571
|
+
pill.classList.toggle('live', state.streamConnected && failedResources === 0);
|
|
1572
|
+
pill.classList.toggle('warn', !state.streamConnected || failedResources > 0);
|
|
1573
|
+
$('sync-label').textContent = state.streamConnected
|
|
1574
|
+
? failedResources
|
|
1575
|
+
? `Degraded · ${failedResources} surfaces unavailable`
|
|
1576
|
+
: `Synchronized · ${formatAgo(state.lastRefreshAt)}`
|
|
1577
|
+
: failedResources
|
|
1578
|
+
? `REST degraded · ${failedResources} unavailable`
|
|
1579
|
+
: 'Stream reconnecting';
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
function renderLeftRail() {
|
|
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
|
|
1587
|
+
? state.clients.map((client) => `
|
|
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)}">
|
|
1589
|
+
<div class="client-info">
|
|
1590
|
+
<span class="client-dot ${escapeHtml(client.state)}"></span>
|
|
1591
|
+
<div>
|
|
1592
|
+
<div class="client-name">${escapeHtml(client.displayName)}</div>
|
|
1593
|
+
<div class="meta">${escapeHtml(client.source)} · ${escapeHtml(client.evidence?.[0] || 'no extra evidence')}</div>
|
|
1594
|
+
</div>
|
|
1595
|
+
</div>
|
|
1596
|
+
<span class="state-chip ${stateClass(client.state)}">${escapeHtml(client.state)}</span>
|
|
1597
|
+
</div>
|
|
1598
|
+
`).join('')
|
|
1599
|
+
: emptyState('No clients detected.');
|
|
1600
|
+
|
|
1601
|
+
const tokenMetrics = computeTokenMetrics();
|
|
1602
|
+
$('token-summary').textContent = resourceFailed('events') ? 'events unavailable' : `${tokenMetrics.events} events`;
|
|
1603
|
+
$('gross-tokens').textContent = formatNumber(tokenMetrics.gross);
|
|
1604
|
+
$('saved-tokens').textContent = formatNumber(tokenMetrics.saved);
|
|
1605
|
+
$('net-tokens').textContent = formatNumber(tokenMetrics.forwarded);
|
|
1606
|
+
$('memory-count').textContent = resourceFailed('memory') && !state.memories.length ? 'n/a' : formatNumber(state.memories.length);
|
|
1607
|
+
$('dial-value').textContent = `${tokenMetrics.ratio}%`;
|
|
1608
|
+
const circumference = 2 * Math.PI * 70;
|
|
1609
|
+
const offset = circumference - (circumference * tokenMetrics.ratio / 100);
|
|
1610
|
+
$('dial-progress').style.strokeDashoffset = `${offset}`;
|
|
1611
|
+
|
|
1612
|
+
$('runs-count').textContent = resourceFailed('runs') && !state.runs.length ? 'n/a' : formatNumber(state.runs.length);
|
|
1613
|
+
const latestRun = state.runs[0];
|
|
1614
|
+
$('latest-run-state').textContent = resourceFailed('runs') && !latestRun
|
|
1615
|
+
? 'Runs endpoint unavailable'
|
|
1616
|
+
: latestRun
|
|
1617
|
+
? `${latestRun.state} · ${latestRun.selectedBackends?.memoryBackend || 'memory n/a'}`
|
|
1618
|
+
: 'No runs yet';
|
|
1619
|
+
$('artifact-count').textContent = `${state.skills.length} / ${state.workflows.length}`;
|
|
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
|
|
1629
|
+
? `Pages valid · ${state.health.ci?.eventHistory || 0} events`
|
|
1630
|
+
: 'Pages syntax or docs deployment needs attention';
|
|
1631
|
+
|
|
1632
|
+
const memory = state.health.memory || {};
|
|
1633
|
+
$('memory-tier-summary').textContent = resourceFailed('health') ? 'health unavailable' : `${memory.cortex || 0} cortex`;
|
|
1634
|
+
|
|
1635
|
+
const pod = state.pod || {};
|
|
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
|
|
1640
|
+
? pod.activeWorkers.slice(0, 4).map((worker) => `
|
|
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)}">
|
|
1642
|
+
<div class="card-title">
|
|
1643
|
+
<strong>${escapeHtml(worker.workerId)}</strong>
|
|
1644
|
+
<span class="state-chip ${stateClass(worker.state)}">${escapeHtml(worker.state)}</span>
|
|
1645
|
+
</div>
|
|
1646
|
+
<div class="meta">${formatAgo(worker.lastMessageTimestamp)} · avg confidence ${Number(worker.avgConfidence || 0).toFixed(2)}</div>
|
|
1647
|
+
<div class="list-inline">${(worker.tags || []).slice(0, 4).map((tag) => chip(tag)).join('')}</div>
|
|
1648
|
+
</div>
|
|
1649
|
+
`).join('')
|
|
1650
|
+
: emptyState('Waiting for POD traffic.');
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function buildGraphModel() {
|
|
1654
|
+
if (state.graphMode === 'runs') {
|
|
1655
|
+
const nodes = [];
|
|
1656
|
+
const links = [];
|
|
1657
|
+
state.runs.slice(0, 4).forEach((run, runIndex) => {
|
|
1658
|
+
const runId = `run:${run.runId}`;
|
|
1659
|
+
nodes.push({ id: runId, label: run.goal, entityType: 'run', run, state: run.state, index: runIndex });
|
|
1660
|
+
(run.workerManifests || []).forEach((worker, workerIndex) => {
|
|
1661
|
+
const workerId = `worker:${worker.workerId}`;
|
|
1662
|
+
nodes.push({ id: workerId, label: `${worker.role}:${worker.strategy}`, entityType: 'worker', worker, runId, index: workerIndex });
|
|
1663
|
+
links.push({ source: runId, target: workerId, type: 'worker' });
|
|
1664
|
+
});
|
|
1665
|
+
(run.activeWorkflows || []).slice(0, 2).forEach((workflow) => {
|
|
1666
|
+
const id = `workflow:${workflow.workflowId}`;
|
|
1667
|
+
nodes.push({ id, label: workflow.name, entityType: 'workflow', workflow, runId });
|
|
1668
|
+
links.push({ source: runId, target: id, type: 'workflow' });
|
|
1669
|
+
});
|
|
1670
|
+
(run.activeSkills || []).slice(0, 2).forEach((skill) => {
|
|
1671
|
+
const id = `skill:${skill.skillId}`;
|
|
1672
|
+
nodes.push({ id, label: skill.name, entityType: 'skill', skill, runId });
|
|
1673
|
+
links.push({ source: runId, target: id, type: 'skill' });
|
|
1674
|
+
});
|
|
1675
|
+
});
|
|
1676
|
+
return { nodes, links };
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
if (state.graphMode === 'pod') {
|
|
1680
|
+
const nodes = [];
|
|
1681
|
+
const links = [];
|
|
1682
|
+
(state.pod.activeWorkers || []).forEach((worker) => {
|
|
1683
|
+
const workerId = `pod:${worker.workerId}`;
|
|
1684
|
+
nodes.push({ id: workerId, label: worker.workerId, entityType: 'pod-worker', worker });
|
|
1685
|
+
(worker.tags || []).slice(0, 5).forEach((tag) => {
|
|
1686
|
+
const tagId = `tag:${tag}`;
|
|
1687
|
+
if (!nodes.find((node) => node.id === tagId)) {
|
|
1688
|
+
nodes.push({ id: tagId, label: tag, entityType: 'tag' });
|
|
1689
|
+
}
|
|
1690
|
+
links.push({ source: workerId, target: tagId, type: 'tag' });
|
|
1691
|
+
});
|
|
1692
|
+
});
|
|
1693
|
+
return { nodes, links };
|
|
1694
|
+
}
|
|
1695
|
+
|
|
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
|
+
}));
|
|
1704
|
+
const links = (state.memoryNetwork.links || []).map((link) => ({ ...link }));
|
|
1705
|
+
return { nodes, links };
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
function renderGraph() {
|
|
1709
|
+
const config = graphModes[state.graphMode];
|
|
1710
|
+
$('graph-title').textContent = config.title;
|
|
1711
|
+
$('graph-subtitle').textContent = config.subtitle;
|
|
1712
|
+
document.querySelectorAll('#graph-modes button').forEach((button) => {
|
|
1713
|
+
button.classList.toggle('active', button.dataset.graphMode === state.graphMode);
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
const model = buildGraphModel();
|
|
1717
|
+
const svg = $('graph-canvas');
|
|
1718
|
+
const empty = $('graph-empty');
|
|
1719
|
+
|
|
1720
|
+
if (!model.nodes.length) {
|
|
1721
|
+
svg.innerHTML = '';
|
|
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.';
|
|
1734
|
+
$('graph-stats').innerHTML = '';
|
|
1735
|
+
$('graph-note').textContent = empty.textContent;
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
empty.classList.add('hidden');
|
|
1740
|
+
const width = 980;
|
|
1741
|
+
const height = 620;
|
|
1742
|
+
const positions = state.graphMode === 'runs'
|
|
1743
|
+
? layoutRunGraph(model.nodes, width, height)
|
|
1744
|
+
: state.graphMode === 'pod'
|
|
1745
|
+
? layoutPodGraph(model.nodes, width, height)
|
|
1746
|
+
: layoutMemoryGraph(model.nodes, width, height, state.memoryNetwork.focusId);
|
|
1747
|
+
|
|
1748
|
+
const colorFor = (node) => {
|
|
1749
|
+
const type = node.entityType;
|
|
1750
|
+
if (type === 'memory') {
|
|
1751
|
+
if (node.tier === 'cortex') return '#b05cff';
|
|
1752
|
+
if (node.tier === 'hippocampus') return '#49f0ff';
|
|
1753
|
+
return '#54ff87';
|
|
1754
|
+
}
|
|
1755
|
+
if (type === 'run' || type === 'worker') return '#3da1ff';
|
|
1756
|
+
if (type === 'workflow') return '#ffd14d';
|
|
1757
|
+
if (type === 'skill') return '#54ff87';
|
|
1758
|
+
if (type === 'pod-worker') return '#49f0ff';
|
|
1759
|
+
if (type === 'tag') return '#ff9f5f';
|
|
1760
|
+
return '#9ea6bc';
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
const sizeFor = (node) => {
|
|
1764
|
+
if (node.entityType === 'memory' && node.id === state.memoryNetwork.focusId) return 28;
|
|
1765
|
+
if (node.entityType === 'run') return 24;
|
|
1766
|
+
if (node.entityType === 'worker' || node.entityType === 'pod-worker') return 18;
|
|
1767
|
+
if (node.entityType === 'workflow' || node.entityType === 'skill') return 16;
|
|
1768
|
+
if (node.entityType === 'tag') return 12;
|
|
1769
|
+
return 14;
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
svg.innerHTML = `
|
|
1773
|
+
<g>
|
|
1774
|
+
${(model.links || []).map((link) => {
|
|
1775
|
+
const source = positions[link.source];
|
|
1776
|
+
const target = positions[link.target];
|
|
1777
|
+
if (!source || !target) return '';
|
|
1778
|
+
return `<line class="graph-link" x1="${source.x}" y1="${source.y}" x2="${target.x}" y2="${target.y}" opacity="${link.type === 'artifact-derived' ? 0.45 : 0.8}"></line>`;
|
|
1779
|
+
}).join('')}
|
|
1780
|
+
${(model.nodes || []).map((node) => {
|
|
1781
|
+
const pos = positions[node.id];
|
|
1782
|
+
if (!pos) return '';
|
|
1783
|
+
const active = isNodeActive(node);
|
|
1784
|
+
const radius = sizeFor(node);
|
|
1785
|
+
return `
|
|
1786
|
+
<g class="graph-node ${active ? 'active' : ''}" data-node-id="${escapeHtml(node.id)}" transform="translate(${pos.x}, ${pos.y})">
|
|
1787
|
+
<circle class="halo" r="${radius + 6}"></circle>
|
|
1788
|
+
<circle r="${radius}" fill="${colorFor(node)}" opacity="0.9"></circle>
|
|
1789
|
+
<text class="node-label" x="${radius + 10}" y="4">${escapeHtml(shortLabel(node.label))}</text>
|
|
1790
|
+
</g>
|
|
1791
|
+
`;
|
|
1792
|
+
}).join('')}
|
|
1793
|
+
</g>
|
|
1794
|
+
`;
|
|
1795
|
+
|
|
1796
|
+
svg.querySelectorAll('.graph-node').forEach((nodeElement) => {
|
|
1797
|
+
nodeElement.addEventListener('click', async () => {
|
|
1798
|
+
const nodeId = nodeElement.getAttribute('data-node-id');
|
|
1799
|
+
await openNode(nodeId, model.nodes.find((node) => node.id === nodeId));
|
|
1800
|
+
});
|
|
1801
|
+
});
|
|
1802
|
+
|
|
1803
|
+
$('graph-stats').innerHTML = [
|
|
1804
|
+
chip(`${model.nodes.length} nodes`),
|
|
1805
|
+
chip(`${model.links.length} links`),
|
|
1806
|
+
state.graphMode === 'memory' ? chip(`${state.memories.length} memory snapshots`) : '',
|
|
1807
|
+
state.graphMode === 'runs' ? chip(`${state.runs.length} recent runs`) : '',
|
|
1808
|
+
state.graphMode === 'pod' ? chip(`${(state.pod.activeWorkers || []).length} pod workers`) : '',
|
|
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;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function renderLibrary() {
|
|
1816
|
+
document.querySelectorAll('#library-tabs button').forEach((button) => {
|
|
1817
|
+
button.classList.toggle('active', button.dataset.libraryMode === state.libraryMode);
|
|
1818
|
+
});
|
|
1819
|
+
const titles = {
|
|
1820
|
+
memories: 'Memory Snapshots',
|
|
1821
|
+
skills: 'Skills',
|
|
1822
|
+
workflows: 'Workflows',
|
|
1823
|
+
pod: 'POD Signals',
|
|
1824
|
+
clients: 'Connected Ecosystem',
|
|
1825
|
+
};
|
|
1826
|
+
$('library-title').textContent = titles[state.libraryMode];
|
|
1827
|
+
const container = $('library-list');
|
|
1828
|
+
|
|
1829
|
+
if (state.libraryMode === 'memories') {
|
|
1830
|
+
container.innerHTML = resourceFailed('memory') && !state.memories.length
|
|
1831
|
+
? emptyState('Memory endpoint unavailable.')
|
|
1832
|
+
: state.memories.length
|
|
1833
|
+
? state.memories.map((memory) => `
|
|
1834
|
+
<div class="card interactive ${state.selected?.kind === 'memory' && state.selected.id === memory.id ? 'active' : ''}" data-kind="memory" data-id="${escapeHtml(memory.id)}">
|
|
1835
|
+
<div class="card-title">
|
|
1836
|
+
<strong>${escapeHtml(memory.excerpt)}</strong>
|
|
1837
|
+
<span class="state-chip ${stateClass(memory.tier)}">${escapeHtml(memory.tier)}</span>
|
|
1838
|
+
</div>
|
|
1839
|
+
<div class="meta">${formatDate(memory.timestamp)} · priority ${Number(memory.priority || 0).toFixed(2)} · ${memory.linkCount} links</div>
|
|
1840
|
+
<div class="list-inline">${(memory.tags || []).slice(0, 5).map((tag) => chip(tag)).join('')}</div>
|
|
1841
|
+
</div>
|
|
1842
|
+
`).join('')
|
|
1843
|
+
: emptyState('No memory stored yet.');
|
|
1844
|
+
} else if (state.libraryMode === 'skills') {
|
|
1845
|
+
container.innerHTML = resourceFailed('skills') && !state.skills.length
|
|
1846
|
+
? emptyState('Skills endpoint unavailable.')
|
|
1847
|
+
: state.skills.length
|
|
1848
|
+
? state.skills.map((skill) => `
|
|
1849
|
+
<div class="card interactive ${state.selected?.kind === 'skill' && state.selected.id === skill.skillId ? 'active' : ''}" data-kind="skill" data-id="${escapeHtml(skill.skillId)}">
|
|
1850
|
+
<div class="card-title">
|
|
1851
|
+
<strong>${escapeHtml(skill.name)}</strong>
|
|
1852
|
+
<span class="state-chip ${stateClass(skill.rolloutStatus)}">${escapeHtml(skill.rolloutStatus)}</span>
|
|
1853
|
+
</div>
|
|
1854
|
+
<div class="meta">${escapeHtml(skill.provenance)} · scope ${escapeHtml(skill.scope)} · risk ${escapeHtml(skill.riskClass)}</div>
|
|
1855
|
+
<div class="list-inline">
|
|
1856
|
+
${skill.domain ? chip(`domain:${skill.domain}`) : ''}
|
|
1857
|
+
${chip(`success:${skill.effectiveness?.successes || 0}`)}
|
|
1858
|
+
${chip(`verify:${skill.effectiveness?.verificationPasses || 0}`)}
|
|
1859
|
+
</div>
|
|
1860
|
+
</div>
|
|
1861
|
+
`).join('')
|
|
1862
|
+
: emptyState('No skills loaded.');
|
|
1863
|
+
} else if (state.libraryMode === 'workflows') {
|
|
1864
|
+
container.innerHTML = resourceFailed('workflows') && !state.workflows.length
|
|
1865
|
+
? emptyState('Workflows endpoint unavailable.')
|
|
1866
|
+
: state.workflows.length
|
|
1867
|
+
? state.workflows.map((workflow) => `
|
|
1868
|
+
<div class="card interactive ${state.selected?.kind === 'workflow' && state.selected.id === workflow.workflowId ? 'active' : ''}" data-kind="workflow" data-id="${escapeHtml(workflow.workflowId)}">
|
|
1869
|
+
<div class="card-title">
|
|
1870
|
+
<strong>${escapeHtml(workflow.name)}</strong>
|
|
1871
|
+
<span class="state-chip ${stateClass(workflow.rolloutStatus)}">${escapeHtml(workflow.rolloutStatus)}</span>
|
|
1872
|
+
</div>
|
|
1873
|
+
<div class="meta">${escapeHtml(workflow.description)} · ${workflow.steps?.length || 0} steps · ${escapeHtml(workflow.scope)}</div>
|
|
1874
|
+
<div class="list-inline">${chip(`domain:${workflow.domain}`)}${chip(`verify:${workflow.effectiveness?.verificationPasses || 0}`)}</div>
|
|
1875
|
+
</div>
|
|
1876
|
+
`).join('')
|
|
1877
|
+
: emptyState('No workflows loaded.');
|
|
1878
|
+
} else if (state.libraryMode === 'pod') {
|
|
1879
|
+
container.innerHTML = resourceFailed('pod') && !(state.pod.messages || []).length
|
|
1880
|
+
? emptyState('POD endpoint unavailable.')
|
|
1881
|
+
: state.pod.messages?.length
|
|
1882
|
+
? state.pod.messages.map((message) => `
|
|
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)}">
|
|
1884
|
+
<div class="card-title">
|
|
1885
|
+
<strong>${escapeHtml(message.workerId)}</strong>
|
|
1886
|
+
<span class="state-chip ${stateClass(message.type === 'instruction' ? 'warn' : 'active')}">${escapeHtml(message.type)}</span>
|
|
1887
|
+
</div>
|
|
1888
|
+
<div class="meta">${formatDate(message.timestamp)} · confidence ${Number(message.confidence || 0).toFixed(2)}</div>
|
|
1889
|
+
<div class="meta">${escapeHtml(message.content)}</div>
|
|
1890
|
+
<div class="list-inline">${(message.tags || []).map((tag) => chip(tag)).join('')}</div>
|
|
1891
|
+
</div>
|
|
1892
|
+
`).join('')
|
|
1893
|
+
: emptyState('No POD signals captured.');
|
|
1894
|
+
} else {
|
|
1895
|
+
container.innerHTML = resourceFailed('clients') && !state.clients.length
|
|
1896
|
+
? emptyState('Clients endpoint unavailable.')
|
|
1897
|
+
: state.clients.length
|
|
1898
|
+
? state.clients.map((client) => `
|
|
1899
|
+
<div class="card interactive ${state.selected?.kind === 'client' && state.selected.id === client.clientId ? 'active' : ''}" data-kind="client" data-id="${escapeHtml(client.clientId)}">
|
|
1900
|
+
<div class="card-title">
|
|
1901
|
+
<strong>${escapeHtml(client.displayName)}</strong>
|
|
1902
|
+
<span class="state-chip ${stateClass(client.state)}">${escapeHtml(client.state)}</span>
|
|
1903
|
+
</div>
|
|
1904
|
+
<div class="meta">${escapeHtml(client.source)} · last seen ${escapeHtml(formatAgo(client.lastSeen || client.lastHeartbeat))}</div>
|
|
1905
|
+
<div class="list-inline">${(client.evidence || []).map((evidence) => chip(evidence)).join('')}</div>
|
|
1906
|
+
</div>
|
|
1907
|
+
`).join('')
|
|
1908
|
+
: emptyState('No clients detected.');
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
container.querySelectorAll('.card.interactive').forEach((card) => {
|
|
1912
|
+
card.addEventListener('click', () => {
|
|
1913
|
+
const kind = card.getAttribute('data-kind');
|
|
1914
|
+
const id = card.getAttribute('data-id');
|
|
1915
|
+
void openEntity(kind, id);
|
|
1916
|
+
});
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
function renderEvents() {
|
|
1921
|
+
$('events-summary').textContent = resourceFailed('events') ? 'events unavailable' : `${state.events.length} signals`;
|
|
1922
|
+
document.querySelectorAll('#event-filters button').forEach((button) => {
|
|
1923
|
+
button.classList.toggle('active', button.dataset.eventFilter === state.eventFilter);
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
const visible = state.events
|
|
1927
|
+
.filter((event) => state.eventFilter === 'all' || event.category === state.eventFilter)
|
|
1928
|
+
.slice(0, 60);
|
|
1929
|
+
|
|
1930
|
+
$('event-list').innerHTML = visible.length
|
|
1931
|
+
? visible.map((event) => `
|
|
1932
|
+
<article class="event-card ${escapeHtml(event.severity)}">
|
|
1933
|
+
<div class="event-head">
|
|
1934
|
+
<div>
|
|
1935
|
+
<strong>${escapeHtml(event.title)}</strong>
|
|
1936
|
+
<div class="meta">${escapeHtml(event.source)} · ${formatDate(event.time)}</div>
|
|
1937
|
+
</div>
|
|
1938
|
+
<span class="state-chip ${stateClass(event.severity === 'bad' ? 'failed' : event.severity === 'warn' ? 'inferred' : 'active')}">${escapeHtml(event.category)}</span>
|
|
1939
|
+
</div>
|
|
1940
|
+
<div class="meta">${escapeHtml(event.summary)}</div>
|
|
1941
|
+
<details class="raw">
|
|
1942
|
+
<summary>Payload</summary>
|
|
1943
|
+
<pre>${escapeHtml(JSON.stringify(event.payload, null, 2))}</pre>
|
|
1944
|
+
</details>
|
|
1945
|
+
</article>
|
|
1946
|
+
`).join('')
|
|
1947
|
+
: resourceFailed('events')
|
|
1948
|
+
? emptyState('Events endpoint unavailable. Live stream may still populate this panel.')
|
|
1949
|
+
: emptyState('No events for this filter.');
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
function renderDrawer() {
|
|
1953
|
+
const drawer = $('drawer');
|
|
1954
|
+
if (!state.selected || !state.selected.data) {
|
|
1955
|
+
drawer.classList.remove('open');
|
|
1956
|
+
$('drawer-title').textContent = 'Inspector';
|
|
1957
|
+
$('drawer-subtitle').textContent = 'Select a node or card.';
|
|
1958
|
+
$('drawer-body').innerHTML = '<div class="empty">Touch a memory, run, workflow, skill, POD worker, or client to inspect it.</div>';
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
drawer.classList.add('open');
|
|
1963
|
+
const { kind, data } = state.selected;
|
|
1964
|
+
$('drawer-title').textContent = formatDrawerTitle(kind, data);
|
|
1965
|
+
$('drawer-subtitle').textContent = formatDrawerSubtitle(kind, data);
|
|
1966
|
+
$('drawer-body').innerHTML = renderDrawerSections(kind, data);
|
|
1967
|
+
bindDrawerActions();
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function formatDrawerTitle(kind, data) {
|
|
1971
|
+
if (kind === 'memory') return data.excerpt || 'Memory';
|
|
1972
|
+
if (kind === 'run') return data.goal || 'Run';
|
|
1973
|
+
if (kind === 'skill') return data.name || 'Skill';
|
|
1974
|
+
if (kind === 'workflow') return data.name || 'Workflow';
|
|
1975
|
+
if (kind === 'pod-worker') return data.workerId || 'POD Worker';
|
|
1976
|
+
if (kind === 'client') return data.displayName || 'Client';
|
|
1977
|
+
return 'Inspector';
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function formatDrawerSubtitle(kind, data) {
|
|
1981
|
+
if (kind === 'memory') return `${data.id} · ${data.tier} · ${formatDate(data.timestamp)}`;
|
|
1982
|
+
if (kind === 'run') return `${data.runId} · ${data.state}`;
|
|
1983
|
+
if (kind === 'skill') return `${data.skillId} · ${data.rolloutStatus}`;
|
|
1984
|
+
if (kind === 'workflow') return `${data.workflowId} · ${data.rolloutStatus}`;
|
|
1985
|
+
if (kind === 'pod-worker') return `${data.messageCount || data.messages?.length || 0} messages`;
|
|
1986
|
+
if (kind === 'client') return `${data.clientId} · ${data.state}`;
|
|
1987
|
+
return '';
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
function renderDrawerSections(kind, data) {
|
|
1991
|
+
if (kind === 'memory') {
|
|
1992
|
+
return `
|
|
1993
|
+
<section class="drawer-section">
|
|
1994
|
+
<h4>Snapshot</h4>
|
|
1995
|
+
<div class="list-inline">
|
|
1996
|
+
${chip(`tier:${data.tier}`)}
|
|
1997
|
+
${chip(`priority:${Number(data.priority || 0).toFixed(2)}`)}
|
|
1998
|
+
${chip(`links:${data.linkCount || 0}`)}
|
|
1999
|
+
${chip(`access:${data.accessCount || 0}`)}
|
|
2000
|
+
</div>
|
|
2001
|
+
<pre>${escapeHtml(data.content || data.excerpt || '')}</pre>
|
|
2002
|
+
</section>
|
|
2003
|
+
<section class="drawer-section">
|
|
2004
|
+
<h4>Lineage</h4>
|
|
2005
|
+
${renderSmallList(data.lineage?.map((item) => ({
|
|
2006
|
+
label: item.excerpt,
|
|
2007
|
+
meta: `${item.tier} · ${formatDate(item.timestamp)}`,
|
|
2008
|
+
action: `memory:${item.id}`,
|
|
2009
|
+
})) || [])}
|
|
2010
|
+
</section>
|
|
2011
|
+
<section class="drawer-section">
|
|
2012
|
+
<h4>Linked Memories</h4>
|
|
2013
|
+
${renderSmallList(data.linkedMemories?.map((item) => ({
|
|
2014
|
+
label: item.excerpt,
|
|
2015
|
+
meta: `${item.linkCount} links · ${formatAgo(item.timestamp)}`,
|
|
2016
|
+
action: `memory:${item.id}`,
|
|
2017
|
+
})) || [])}
|
|
2018
|
+
</section>
|
|
2019
|
+
<section class="drawer-section">
|
|
2020
|
+
<h4>Timeline</h4>
|
|
2021
|
+
${renderSmallList(data.timeline?.map((item) => ({
|
|
2022
|
+
label: item.excerpt,
|
|
2023
|
+
meta: `${formatDate(item.timestamp)} · ${item.tier}`,
|
|
2024
|
+
action: `memory:${item.id}`,
|
|
2025
|
+
})) || [])}
|
|
2026
|
+
</section>
|
|
2027
|
+
<section class="drawer-section">
|
|
2028
|
+
<h4>Related Runtime Objects</h4>
|
|
2029
|
+
<div class="list-inline">${(data.related || []).map((item) => chip(`${item.type}:${item.id}`)).join('') || '<span class="meta">No derived runtime links</span>'}</div>
|
|
2030
|
+
</section>
|
|
2031
|
+
`;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
if (kind === 'run') {
|
|
2035
|
+
return `
|
|
2036
|
+
<section class="drawer-section">
|
|
2037
|
+
<h4>Run Summary</h4>
|
|
2038
|
+
<div class="list-inline">
|
|
2039
|
+
${chip(`state:${data.state}`)}
|
|
2040
|
+
${chip(`workers:${data.workerResults?.length || 0}`)}
|
|
2041
|
+
${chip(`verified:${data.verificationResults?.filter((item) => item.passed).length || 0}`)}
|
|
2042
|
+
</div>
|
|
2043
|
+
<pre>${escapeHtml(data.result || '')}</pre>
|
|
2044
|
+
</section>
|
|
2045
|
+
<section class="drawer-section">
|
|
2046
|
+
<h4>Backends</h4>
|
|
2047
|
+
<div class="list-inline">
|
|
2048
|
+
${chip(`memory:${data.selectedBackends?.memoryBackend || 'n/a'}`)}
|
|
2049
|
+
${chip(`compression:${data.selectedBackends?.compressionBackend || 'n/a'}`)}
|
|
2050
|
+
${chip(`dsl:${data.selectedBackends?.dslCompiler || 'n/a'}`)}
|
|
2051
|
+
</div>
|
|
2052
|
+
</section>
|
|
2053
|
+
<section class="drawer-section">
|
|
2054
|
+
<h4>Worker Results</h4>
|
|
2055
|
+
${renderSmallList((data.workerResults || []).map((worker) => ({
|
|
2056
|
+
label: `${worker.workerId} · ${worker.role}`,
|
|
2057
|
+
meta: `${worker.verified ? 'verified' : 'unverified'} · ${worker.modifiedFiles?.length || 0} files`,
|
|
2058
|
+
})))}
|
|
2059
|
+
</section>
|
|
2060
|
+
`;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
if (kind === 'skill') {
|
|
2064
|
+
return `
|
|
2065
|
+
<section class="drawer-section">
|
|
2066
|
+
<h4>Skill</h4>
|
|
2067
|
+
<div class="list-inline">
|
|
2068
|
+
${chip(`scope:${data.scope}`)}
|
|
2069
|
+
${chip(`risk:${data.riskClass}`)}
|
|
2070
|
+
${chip(`status:${data.rolloutStatus}`)}
|
|
2071
|
+
</div>
|
|
2072
|
+
<pre>${escapeHtml(data.instructions || '')}</pre>
|
|
2073
|
+
</section>
|
|
2074
|
+
<section class="drawer-section">
|
|
2075
|
+
<h4>Effectiveness</h4>
|
|
2076
|
+
<div class="list-inline">
|
|
2077
|
+
${chip(`success:${data.effectiveness?.successes || 0}`)}
|
|
2078
|
+
${chip(`fail:${data.effectiveness?.failures || 0}`)}
|
|
2079
|
+
${chip(`verify:${data.effectiveness?.verificationPasses || 0}`)}
|
|
2080
|
+
${chip(`tokenDelta:${data.effectiveness?.tokenDelta || 0}`)}
|
|
2081
|
+
</div>
|
|
2082
|
+
</section>
|
|
2083
|
+
<section class="drawer-section">
|
|
2084
|
+
<h4>Actions</h4>
|
|
2085
|
+
<div class="action-bar">
|
|
2086
|
+
<button class="primary-button" data-drawer-action="deploy-skill" data-id="${escapeHtml(data.skillId)}">Deploy</button>
|
|
2087
|
+
<button class="ghost-button" data-drawer-action="revoke-skill" data-id="${escapeHtml(data.skillId)}">Revoke</button>
|
|
2088
|
+
</div>
|
|
2089
|
+
</section>
|
|
2090
|
+
`;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
if (kind === 'workflow') {
|
|
2094
|
+
return `
|
|
2095
|
+
<section class="drawer-section">
|
|
2096
|
+
<h4>Workflow</h4>
|
|
2097
|
+
<div class="list-inline">
|
|
2098
|
+
${chip(`scope:${data.scope}`)}
|
|
2099
|
+
${chip(`steps:${data.steps?.length || 0}`)}
|
|
2100
|
+
${chip(`status:${data.rolloutStatus}`)}
|
|
2101
|
+
</div>
|
|
2102
|
+
<pre>${escapeHtml(data.description || '')}</pre>
|
|
2103
|
+
</section>
|
|
2104
|
+
<section class="drawer-section">
|
|
2105
|
+
<h4>Verifier Hooks</h4>
|
|
2106
|
+
${renderSmallList((data.verifierHooks || []).map((hook) => ({ label: hook, meta: data.domain })))}
|
|
2107
|
+
</section>
|
|
2108
|
+
<section class="drawer-section">
|
|
2109
|
+
<h4>Actions</h4>
|
|
2110
|
+
<div class="action-bar">
|
|
2111
|
+
<button class="primary-button" data-drawer-action="deploy-workflow" data-id="${escapeHtml(data.workflowId)}">Deploy</button>
|
|
2112
|
+
<button class="ghost-button" data-drawer-action="run-workflow" data-id="${escapeHtml(data.workflowId)}">Run</button>
|
|
2113
|
+
</div>
|
|
2114
|
+
</section>
|
|
2115
|
+
`;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
if (kind === 'pod-worker') {
|
|
2119
|
+
return `
|
|
2120
|
+
<section class="drawer-section">
|
|
2121
|
+
<h4>Worker Snapshot</h4>
|
|
2122
|
+
<div class="list-inline">
|
|
2123
|
+
${chip(`state:${data.state}`)}
|
|
2124
|
+
${chip(`messages:${data.messageCount || data.messages?.length || 0}`)}
|
|
2125
|
+
${chip(`avg:${Number(data.avgConfidence || 0).toFixed(2)}`)}
|
|
2126
|
+
</div>
|
|
2127
|
+
<div class="list-inline">${(data.tags || []).map((tag) => chip(tag)).join('')}</div>
|
|
2128
|
+
</section>
|
|
2129
|
+
<section class="drawer-section">
|
|
2130
|
+
<h4>Recent Signals</h4>
|
|
2131
|
+
${renderSmallList((data.messages || []).map((message) => ({
|
|
2132
|
+
label: message.content,
|
|
2133
|
+
meta: `${message.type} · ${formatDate(message.timestamp)}`,
|
|
2134
|
+
})))}
|
|
2135
|
+
</section>
|
|
2136
|
+
`;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
if (kind === 'client') {
|
|
2140
|
+
return `
|
|
2141
|
+
<section class="drawer-section">
|
|
2142
|
+
<h4>Client Status</h4>
|
|
2143
|
+
<div class="list-inline">
|
|
2144
|
+
${chip(`state:${data.state}`)}
|
|
2145
|
+
${chip(`source:${data.source}`)}
|
|
2146
|
+
${chip(`confidence:${Number(data.confidence || 0).toFixed(2)}`)}
|
|
2147
|
+
</div>
|
|
2148
|
+
<div class="meta">Last heartbeat: ${formatDate(data.lastHeartbeat)} · last seen: ${formatDate(data.lastSeen)}</div>
|
|
2149
|
+
</section>
|
|
2150
|
+
<section class="drawer-section">
|
|
2151
|
+
<h4>Evidence</h4>
|
|
2152
|
+
${renderSmallList((data.evidence || []).map((entry) => ({ label: entry, meta: data.source })))}
|
|
2153
|
+
</section>
|
|
2154
|
+
<section class="drawer-section">
|
|
2155
|
+
<h4>Actions</h4>
|
|
2156
|
+
<div class="action-bar">
|
|
2157
|
+
<button class="primary-button" data-drawer-action="reconnect-client" data-id="${escapeHtml(data.clientId)}">Reconnect</button>
|
|
2158
|
+
<button class="ghost-button" data-drawer-action="clear-client" data-id="${escapeHtml(data.clientId)}">Clear Stale</button>
|
|
2159
|
+
</div>
|
|
2160
|
+
</section>
|
|
2161
|
+
`;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
return '<div class="empty">No details available.</div>';
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
function renderSmallList(items) {
|
|
2168
|
+
if (!items.length) {
|
|
2169
|
+
return '<div class="empty">No linked items.</div>';
|
|
2170
|
+
}
|
|
2171
|
+
return items.map((item) => `
|
|
2172
|
+
<div class="card interactive small-list-item" ${item.action ? `data-entity-action="${escapeHtml(item.action)}"` : ''}>
|
|
2173
|
+
<div class="card-title">
|
|
2174
|
+
<strong>${escapeHtml(item.label)}</strong>
|
|
2175
|
+
</div>
|
|
2176
|
+
<div class="meta">${escapeHtml(item.meta || '')}</div>
|
|
2177
|
+
</div>
|
|
2178
|
+
`).join('');
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function bindDrawerActions() {
|
|
2182
|
+
$('drawer-body').querySelectorAll('[data-drawer-action]').forEach((button) => {
|
|
2183
|
+
button.addEventListener('click', async () => {
|
|
2184
|
+
const action = button.getAttribute('data-drawer-action');
|
|
2185
|
+
const id = button.getAttribute('data-id');
|
|
2186
|
+
await handleDrawerAction(action, id);
|
|
2187
|
+
});
|
|
586
2188
|
});
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
2189
|
+
|
|
2190
|
+
$('drawer-body').querySelectorAll('[data-entity-action]').forEach((card) => {
|
|
2191
|
+
card.addEventListener('click', () => {
|
|
2192
|
+
const action = card.getAttribute('data-entity-action');
|
|
2193
|
+
const [kind, id] = action.split(':');
|
|
2194
|
+
void openEntity(kind, id);
|
|
2195
|
+
});
|
|
591
2196
|
});
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
async function handleDrawerAction(action, id) {
|
|
2200
|
+
try {
|
|
2201
|
+
if (action === 'deploy-skill') {
|
|
2202
|
+
await fetchJson('/api/skills/deploy', postJson({ skillId: id }));
|
|
2203
|
+
} else if (action === 'revoke-skill') {
|
|
2204
|
+
await fetchJson('/api/skills/revoke', postJson({ skillId: id }));
|
|
2205
|
+
} else if (action === 'deploy-workflow') {
|
|
2206
|
+
await fetchJson('/api/workflows/deploy', postJson({ workflowId: id }));
|
|
2207
|
+
} else if (action === 'run-workflow') {
|
|
2208
|
+
await fetchJson('/api/workflows/run', postJson({ workflowId: id }));
|
|
2209
|
+
} else if (action === 'reconnect-client') {
|
|
2210
|
+
await fetchJson(`/api/clients/${encodeURIComponent(id)}/reconnect`, postJson({}));
|
|
2211
|
+
} else if (action === 'clear-client') {
|
|
2212
|
+
await fetchJson(`/api/clients/${encodeURIComponent(id)}/clear`, postJson({}));
|
|
2213
|
+
}
|
|
2214
|
+
$('control-status').textContent = `Action ${action} completed.`;
|
|
2215
|
+
await refreshAll();
|
|
2216
|
+
} catch (error) {
|
|
2217
|
+
$('control-status').textContent = `Action ${action} failed: ${error.message}`;
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
function postJson(body) {
|
|
2222
|
+
return {
|
|
2223
|
+
method: 'POST',
|
|
2224
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2225
|
+
body: JSON.stringify(body),
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
async function openNode(nodeId, node) {
|
|
2230
|
+
if (!nodeId || !node) return;
|
|
2231
|
+
if (node.entityType === 'memory') {
|
|
2232
|
+
await openEntity('memory', node.id);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
if (node.entityType === 'run') {
|
|
2236
|
+
const runId = node.id.replace('run:', '');
|
|
2237
|
+
const run = state.runs.find((item) => item.runId === runId);
|
|
2238
|
+
if (run) state.selected = { kind: 'run', id: runId, data: run };
|
|
2239
|
+
} else if (node.entityType === 'workflow') {
|
|
2240
|
+
const workflowId = node.id.replace('workflow:', '');
|
|
2241
|
+
const workflow = state.workflows.find((item) => item.workflowId === workflowId);
|
|
2242
|
+
if (workflow) state.selected = { kind: 'workflow', id: workflowId, data: workflow };
|
|
2243
|
+
} else if (node.entityType === 'skill') {
|
|
2244
|
+
const skillId = node.id.replace('skill:', '');
|
|
2245
|
+
const skill = state.skills.find((item) => item.skillId === skillId);
|
|
2246
|
+
if (skill) state.selected = { kind: 'skill', id: skillId, data: skill };
|
|
2247
|
+
} else if (node.entityType === 'pod-worker') {
|
|
2248
|
+
await openEntity('pod-worker', node.id.replace('pod:', ''));
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
render();
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
async function openEntity(kind, id) {
|
|
2255
|
+
if (!kind || !id) return;
|
|
2256
|
+
if (kind === 'memory') {
|
|
2257
|
+
await refreshMemorySelection(id, true);
|
|
2258
|
+
} else if (kind === 'run') {
|
|
2259
|
+
const run = state.runs.find((item) => item.runId === id) || await fetchJson(`/api/runs/${encodeURIComponent(id)}`);
|
|
2260
|
+
if (run?.error) return;
|
|
2261
|
+
state.selected = { kind: 'run', id, data: run };
|
|
2262
|
+
} else if (kind === 'skill') {
|
|
2263
|
+
const skill = state.skills.find((item) => item.skillId === id);
|
|
2264
|
+
if (!skill) return;
|
|
2265
|
+
state.selected = { kind: 'skill', id, data: skill };
|
|
2266
|
+
} else if (kind === 'workflow') {
|
|
2267
|
+
const workflow = state.workflows.find((item) => item.workflowId === id);
|
|
2268
|
+
if (!workflow) return;
|
|
2269
|
+
state.selected = { kind: 'workflow', id, data: workflow };
|
|
2270
|
+
} else if (kind === 'pod-worker') {
|
|
2271
|
+
const worker = await fetchJson(`/api/pod/${encodeURIComponent(id)}`);
|
|
2272
|
+
state.selected = { kind: 'pod-worker', id, data: worker };
|
|
2273
|
+
} else if (kind === 'client') {
|
|
2274
|
+
const client = state.clients.find((item) => item.clientId === id);
|
|
2275
|
+
if (!client) return;
|
|
2276
|
+
state.selected = { kind: 'client', id, data: client };
|
|
2277
|
+
}
|
|
2278
|
+
render();
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
function shortLabel(label) {
|
|
2282
|
+
const text = String(label || '');
|
|
2283
|
+
return text.length > 26 ? `${text.slice(0, 23)}...` : text;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
function isNodeActive(node) {
|
|
2287
|
+
if (!state.selected) return false;
|
|
2288
|
+
if (state.selected.kind === 'memory') return node.id === state.selected.id;
|
|
2289
|
+
if (state.selected.kind === 'run') return node.id === `run:${state.selected.id}`;
|
|
2290
|
+
if (state.selected.kind === 'skill') return node.id === `skill:${state.selected.id}`;
|
|
2291
|
+
if (state.selected.kind === 'workflow') return node.id === `workflow:${state.selected.id}`;
|
|
2292
|
+
if (state.selected.kind === 'pod-worker') return node.id === `pod:${state.selected.id}`;
|
|
2293
|
+
return false;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
function layoutMemoryGraph(nodes, width, height, focusId) {
|
|
2297
|
+
const positions = {};
|
|
2298
|
+
const center = { x: width / 2, y: height / 2 };
|
|
2299
|
+
const focusNode = nodes.find((node) => node.id === focusId);
|
|
2300
|
+
if (focusNode) {
|
|
2301
|
+
positions[focusNode.id] = center;
|
|
2302
|
+
}
|
|
2303
|
+
const memoryNodes = nodes.filter((node) => node.entityType === 'memory' && node.id !== focusId);
|
|
2304
|
+
const outerNodes = nodes.filter((node) => node.entityType !== 'memory');
|
|
2305
|
+
placeRing(memoryNodes, positions, center, 180, -Math.PI / 2);
|
|
2306
|
+
placeRing(outerNodes, positions, center, 280, -Math.PI / 3);
|
|
2307
|
+
if (!focusNode && nodes[0]) {
|
|
2308
|
+
positions[nodes[0].id] = center;
|
|
2309
|
+
}
|
|
2310
|
+
return positions;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
function layoutRunGraph(nodes, width, height) {
|
|
2314
|
+
const positions = {};
|
|
2315
|
+
const runs = nodes.filter((node) => node.entityType === 'run');
|
|
2316
|
+
const workers = nodes.filter((node) => node.entityType === 'worker');
|
|
2317
|
+
const other = nodes.filter((node) => !['run', 'worker'].includes(node.entityType));
|
|
2318
|
+
const gap = width / Math.max(runs.length + 1, 2);
|
|
2319
|
+
runs.forEach((run, index) => {
|
|
2320
|
+
positions[run.id] = { x: gap * (index + 1), y: height * 0.38 };
|
|
2321
|
+
});
|
|
2322
|
+
workers.forEach((worker, index) => {
|
|
2323
|
+
const parent = positions[worker.runId];
|
|
2324
|
+
const offset = ((index % 3) - 1) * 90;
|
|
2325
|
+
positions[worker.id] = { x: (parent?.x || width / 2) + offset, y: height * 0.68 + Math.floor(index / 3) * 40 };
|
|
2326
|
+
});
|
|
2327
|
+
placeRing(other, positions, { x: width / 2, y: height * 0.18 }, 220, -Math.PI / 2);
|
|
2328
|
+
return positions;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
function layoutPodGraph(nodes, width, height) {
|
|
2332
|
+
const positions = {};
|
|
2333
|
+
const workers = nodes.filter((node) => node.entityType === 'pod-worker');
|
|
2334
|
+
const tags = nodes.filter((node) => node.entityType === 'tag');
|
|
2335
|
+
placeRing(workers, positions, { x: width / 2, y: height / 2 }, 170, -Math.PI / 2);
|
|
2336
|
+
placeRing(tags, positions, { x: width / 2, y: height / 2 }, 300, -Math.PI / 2);
|
|
2337
|
+
return positions;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
function placeRing(nodes, positions, center, radius, startAngle) {
|
|
2341
|
+
const total = nodes.length || 1;
|
|
2342
|
+
nodes.forEach((node, index) => {
|
|
2343
|
+
const angle = startAngle + ((Math.PI * 2) * index / total);
|
|
2344
|
+
positions[node.id] = {
|
|
2345
|
+
x: center.x + Math.cos(angle) * radius,
|
|
2346
|
+
y: center.y + Math.sin(angle) * radius,
|
|
2347
|
+
};
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
function attachStaticHandlers() {
|
|
2352
|
+
$('refresh-button').addEventListener('click', async () => {
|
|
2353
|
+
try {
|
|
2354
|
+
$('control-status').textContent = 'Refreshing runtime surfaces...';
|
|
2355
|
+
await refreshAll();
|
|
2356
|
+
$('control-status').textContent = 'Dashboard refreshed.';
|
|
2357
|
+
} catch (error) {
|
|
2358
|
+
$('control-status').textContent = `Refresh failed: ${error.message}`;
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
|
|
2362
|
+
$('execute-form').addEventListener('submit', async (event) => {
|
|
2363
|
+
event.preventDefault();
|
|
2364
|
+
const goal = $('goal-input').value.trim();
|
|
2365
|
+
if (!goal) {
|
|
2366
|
+
$('control-status').textContent = 'Goal is required.';
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
const payload = {
|
|
2371
|
+
goal,
|
|
2372
|
+
files: parseList($('files-input').value),
|
|
2373
|
+
workers: Number($('workers-input').value || 2),
|
|
2374
|
+
skillNames: parseList($('skills-input').value),
|
|
2375
|
+
workflowSelectors: parseList($('workflows-input').value),
|
|
2376
|
+
backendSelectors: {
|
|
2377
|
+
memory: $('memory-backend').value || undefined,
|
|
2378
|
+
compression: $('compression-backend').value || undefined,
|
|
2379
|
+
dsl: $('dsl-backend').value || undefined,
|
|
2380
|
+
},
|
|
2381
|
+
};
|
|
2382
|
+
|
|
2383
|
+
try {
|
|
2384
|
+
$('control-status').textContent = 'Executing runtime task...';
|
|
2385
|
+
const run = await fetchJson('/api/runtime/execute', postJson(payload));
|
|
2386
|
+
$('control-status').textContent = `Run ${run.runId} created with state ${run.state}.`;
|
|
2387
|
+
state.selected = { kind: 'run', id: run.runId, data: run };
|
|
2388
|
+
await refreshAll();
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
$('control-status').textContent = `Execution failed: ${error.message}`;
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
|
|
2394
|
+
$('drawer-close').addEventListener('click', () => {
|
|
2395
|
+
state.selected = null;
|
|
2396
|
+
render();
|
|
2397
|
+
});
|
|
2398
|
+
|
|
2399
|
+
document.querySelectorAll('#graph-modes button').forEach((button) => {
|
|
2400
|
+
button.addEventListener('click', async () => {
|
|
2401
|
+
state.graphMode = button.dataset.graphMode;
|
|
2402
|
+
render();
|
|
2403
|
+
});
|
|
2404
|
+
});
|
|
2405
|
+
|
|
2406
|
+
document.querySelectorAll('#library-tabs button').forEach((button) => {
|
|
2407
|
+
button.addEventListener('click', () => {
|
|
2408
|
+
state.libraryMode = button.dataset.libraryMode;
|
|
2409
|
+
render();
|
|
2410
|
+
});
|
|
2411
|
+
});
|
|
2412
|
+
|
|
2413
|
+
document.querySelectorAll('#event-filters button').forEach((button) => {
|
|
2414
|
+
button.addEventListener('click', () => {
|
|
2415
|
+
state.eventFilter = button.dataset.eventFilter;
|
|
2416
|
+
render();
|
|
2417
|
+
});
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
function connectStream() {
|
|
2422
|
+
const stream = new EventSource('/stream');
|
|
2423
|
+
stream.onopen = () => {
|
|
2424
|
+
state.streamConnected = true;
|
|
2425
|
+
renderHeader();
|
|
2426
|
+
};
|
|
2427
|
+
stream.onerror = () => {
|
|
2428
|
+
state.streamConnected = false;
|
|
2429
|
+
renderHeader();
|
|
2430
|
+
};
|
|
2431
|
+
stream.onmessage = (event) => {
|
|
2432
|
+
try {
|
|
2433
|
+
const raw = JSON.parse(event.data);
|
|
2434
|
+
if (raw?.connected) return;
|
|
2435
|
+
const payload = normalizeEventCard(raw);
|
|
2436
|
+
if (!payload) return;
|
|
2437
|
+
state.events.unshift(payload);
|
|
2438
|
+
state.events = state.events.slice(0, 120);
|
|
2439
|
+
setResourceStatus('events', 'ready');
|
|
2440
|
+
if (['runtime', 'memory', 'pod', 'skills', 'workflows', 'clients'].includes(payload.category)) {
|
|
2441
|
+
void refreshAll().catch(() => {});
|
|
2442
|
+
} else {
|
|
2443
|
+
renderEvents();
|
|
2444
|
+
renderHeader();
|
|
602
2445
|
}
|
|
2446
|
+
} catch {
|
|
2447
|
+
// ignore malformed stream chunks
|
|
603
2448
|
}
|
|
604
2449
|
};
|
|
605
2450
|
}
|
|
606
2451
|
|
|
607
|
-
|
|
2452
|
+
async function bootstrap() {
|
|
2453
|
+
attachStaticHandlers();
|
|
2454
|
+
await refreshAll();
|
|
2455
|
+
$('control-status').textContent = state.banner?.message
|
|
2456
|
+
? state.banner.message
|
|
2457
|
+
: 'Dashboard actions stay local and route through the runtime.';
|
|
2458
|
+
connectStream();
|
|
2459
|
+
setInterval(() => {
|
|
2460
|
+
void refreshAll().catch(() => {});
|
|
2461
|
+
}, 20000);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
bootstrap();
|
|
608
2465
|
</script>
|
|
609
2466
|
</body>
|
|
610
2467
|
</html>
|