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.
@@ -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 Runtime Console</title>
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: #0f1310;
12
- --bg-alt: #15201a;
13
- --panel: rgba(245, 242, 230, 0.07);
14
- --panel-strong: rgba(245, 242, 230, 0.12);
15
- --line: rgba(245, 242, 230, 0.12);
16
- --text: #f3efe2;
17
- --muted: #b9b29d;
18
- --accent: #f6b94d;
19
- --accent-soft: rgba(246, 185, 77, 0.18);
20
- --good: #64d2a3;
21
- --warn: #f6b94d;
22
- --bad: #ff7d66;
23
- --font-sans: 'Space Grotesk', sans-serif;
24
- --font-mono: 'IBM Plex Mono', monospace;
25
- --radius: 24px;
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: 100vh;
35
- font-family: var(--font-sans);
36
- color: var(--text);
41
+ min-height: 100%;
37
42
  background:
38
- radial-gradient(circle at top left, rgba(246, 185, 77, 0.12), transparent 24rem),
39
- radial-gradient(circle at bottom right, rgba(100, 210, 163, 0.12), transparent 28rem),
40
- linear-gradient(135deg, var(--bg), #0a0d0b 60%, var(--bg-alt));
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
- position: sticky;
51
- top: 0;
52
- backdrop-filter: blur(24px);
53
- background: rgba(10, 13, 11, 0.72);
54
- z-index: 10;
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.4rem, 2.6vw, 2.1rem);
60
- letter-spacing: -0.05em;
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.25rem;
107
+ margin-top: 0.15rem;
65
108
  color: var(--muted);
66
- font-size: 0.95rem;
109
+ font-size: 0.84rem;
67
110
  }
68
111
 
69
- .stream-pill {
70
- padding: 0.65rem 0.9rem;
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: var(--panel);
73
- border-radius: 999px;
74
- font-size: 0.85rem;
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
- .stream-pill::before {
81
- content: '';
82
- width: 0.7rem;
83
- height: 0.7rem;
157
+ .status-pill .dot,
158
+ .client-dot {
159
+ width: 0.58rem;
160
+ height: 0.58rem;
84
161
  border-radius: 50%;
85
- background: var(--warn);
86
- box-shadow: 0 0 0 0.4rem rgba(246, 185, 77, 0.12);
162
+ background: var(--muted);
163
+ box-shadow: 0 0 0 0 transparent;
87
164
  }
88
165
 
89
- .stream-pill.live::before {
90
- background: var(--good);
91
- box-shadow: 0 0 0 0.4rem rgba(100, 210, 163, 0.12);
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
- main {
95
- padding: 1.25rem clamp(1rem, 3vw, 2rem) 2rem;
96
- display: grid;
97
- gap: 1rem;
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
- .hero {
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: repeat(auto-fit, minmax(200px, 1fr));
192
+ grid-template-columns: 320px minmax(0, 1fr) 410px;
103
193
  gap: 1rem;
194
+ padding: 0.9rem 1rem 1rem;
104
195
  }
105
196
 
106
- .stat {
197
+ .panel {
107
198
  background: var(--panel);
108
199
  border: 1px solid var(--line);
109
- border-radius: var(--radius);
110
- padding: 1rem 1.1rem;
111
- min-height: 8rem;
200
+ border-radius: var(--radius-panel);
201
+ box-shadow: var(--shadow);
202
+ overflow: hidden;
112
203
  display: flex;
113
204
  flex-direction: column;
114
- justify-content: space-between;
205
+ min-height: 0;
115
206
  }
116
207
 
117
- .stat label {
118
- color: var(--muted);
119
- font-size: 0.8rem;
120
- text-transform: uppercase;
121
- letter-spacing: 0.08em;
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
- .stat strong {
125
- font-size: 2rem;
126
- letter-spacing: -0.05em;
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
- .stat code,
130
- .chip,
131
- pre,
132
- .meta {
133
- font-family: var(--font-mono);
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
- .grid {
137
- display: grid;
138
- grid-template-columns: 1.6fr 1fr;
139
- gap: 1rem;
236
+ .panel-body.compact {
237
+ padding: 0.75rem;
140
238
  }
141
239
 
142
- .stack {
240
+ .rail {
143
241
  display: grid;
144
242
  gap: 1rem;
243
+ min-height: 0;
145
244
  }
146
245
 
147
- section {
148
- background: var(--panel);
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
- section h2 {
156
- margin: 0 0 0.9rem;
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
- .list {
254
+ .stack {
164
255
  display: grid;
165
- gap: 0.75rem;
256
+ gap: 0.9rem;
166
257
  }
167
258
 
168
259
  .card {
169
- background: var(--panel-strong);
170
260
  border: 1px solid var(--line);
171
- border-radius: 18px;
172
- padding: 0.9rem;
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-header {
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
- margin-bottom: 0.45rem;
282
+ align-items: flex-start;
283
+ margin-bottom: 0.5rem;
181
284
  }
182
285
 
183
- .card h3 {
184
- margin: 0;
185
- font-size: 1rem;
286
+ .card-title strong {
287
+ font-size: 0.92rem;
288
+ font-weight: 600;
186
289
  }
187
290
 
188
- .card p {
189
- margin: 0;
190
- color: var(--muted);
191
- line-height: 1.45;
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
- .chips {
195
- display: flex;
196
- flex-wrap: wrap;
197
- gap: 0.45rem;
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
- .chip {
202
- padding: 0.28rem 0.55rem;
203
- border-radius: 999px;
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.03);
311
+ background: linear-gradient(160deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
207
312
  }
208
313
 
209
- .state {
210
- padding: 0.25rem 0.55rem;
211
- border-radius: 999px;
212
- font-size: 0.75rem;
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
- .state.good {
218
- color: var(--good);
219
- background: rgba(100, 210, 163, 0.12);
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
- .state.warn {
223
- color: var(--warn);
224
- background: rgba(246, 185, 77, 0.12);
330
+ .metrics-grid {
331
+ display: grid;
332
+ gap: 0.75rem;
333
+ grid-template-columns: 1fr 1fr;
225
334
  }
226
335
 
227
- .state.bad {
228
- color: var(--bad);
229
- background: rgba(255, 125, 102, 0.12);
336
+ .ecosystem-list,
337
+ .entity-list,
338
+ .event-list {
339
+ display: grid;
340
+ gap: 0.7rem;
230
341
  }
231
342
 
232
- .meta {
233
- color: var(--muted);
234
- font-size: 0.8rem;
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
- .empty {
238
- color: var(--muted);
239
- border: 1px dashed var(--line);
240
- border-radius: 18px;
241
- padding: 1rem;
354
+ .client-info {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 0.75rem;
358
+ min-width: 0;
242
359
  }
243
360
 
244
- .event-feed {
245
- max-height: 26rem;
246
- overflow: auto;
247
- display: grid;
248
- gap: 0.55rem;
361
+ .client-name {
362
+ font-weight: 600;
363
+ font-size: 0.92rem;
249
364
  }
250
365
 
251
- .event {
252
- border-left: 3px solid var(--accent);
253
- padding: 0.6rem 0.75rem;
254
- background: rgba(255, 255, 255, 0.03);
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
- .event strong {
258
- display: block;
259
- margin-bottom: 0.25rem;
380
+ .state-chip.good {
381
+ color: var(--accent);
382
+ background: rgba(84, 255, 135, 0.1);
260
383
  }
261
384
 
262
- @media (max-width: 1000px) {
263
- .grid {
264
- grid-template-columns: 1fr;
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
- <main>
279
- <section class="hero" aria-label="runtime stats">
280
- <div class="stat">
281
- <label>Tracked Runs</label>
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
- <div class="grid">
303
- <div class="stack">
304
- <section>
305
- <h2>Runs</h2>
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
- <section>
312
- <h2>Skills</h2>
313
- <div id="skills-list" class="list">
314
- <div class="empty">No skills loaded.</div>
315
- </div>
316
- </section>
400
+ .token-dial-wrap {
401
+ display: grid;
402
+ place-items: center;
403
+ padding: 0.6rem 0 0.2rem;
404
+ }
317
405
 
318
- <section>
319
- <h2>Workflows</h2>
320
- <div id="workflows-list" class="list">
321
- <div class="empty">No workflows loaded.</div>
322
- </div>
323
- </section>
324
- </div>
406
+ .token-dial {
407
+ position: relative;
408
+ width: 168px;
409
+ height: 168px;
410
+ }
325
411
 
326
- <div class="stack">
327
- <section>
328
- <h2>Backends</h2>
329
- <div id="backends-list" class="list">
330
- <div class="empty">Backend catalog unavailable.</div>
331
- </div>
332
- </section>
412
+ .token-dial svg {
413
+ width: 168px;
414
+ height: 168px;
415
+ transform: rotate(-90deg);
416
+ }
333
417
 
334
- <section>
335
- <h2>Health</h2>
336
- <div id="health-panel" class="list">
337
- <div class="empty">Health status unavailable.</div>
338
- </div>
339
- </section>
418
+ .dial-bg {
419
+ fill: none;
420
+ stroke: rgba(255, 255, 255, 0.09);
421
+ stroke-width: 12;
422
+ }
340
423
 
341
- <section>
342
- <h2>Live Events</h2>
343
- <div id="event-feed" class="event-feed">
344
- <div class="empty">Waiting for runtime activity.</div>
345
- </div>
346
- </section>
347
- </div>
348
- </div>
349
- </main>
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
- <script>
352
- const state = {
353
- runs: [],
354
- skills: [],
355
- workflows: [],
356
- backends: {},
357
- health: {},
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
- function render() {
362
- renderStats();
363
- renderRuns();
364
- renderSkills();
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
- function stateClass(value) {
372
- if (['merged', 'healthy', 'connected', true].includes(value)) return 'good';
373
- if (['rolled_back', 'idle'].includes(value)) return 'warn';
374
- return 'bad';
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
- function createChip(text) {
378
- return `<span class="chip">${escapeHtml(text)}</span>`;
456
+ .graph-panel {
457
+ min-height: 0;
379
458
  }
380
459
 
381
- function escapeHtml(value) {
382
- return String(value)
383
- .replaceAll('&', '&amp;')
384
- .replaceAll('<', '&lt;')
385
- .replaceAll('>', '&gt;');
460
+ .graph-toolbar {
461
+ display: flex;
462
+ gap: 0.55rem;
463
+ flex-wrap: wrap;
386
464
  }
387
465
 
388
- function renderStats() {
389
- document.getElementById('runs-count').textContent = String(state.runs.length);
390
- document.getElementById('skills-count').textContent = String(state.skills.length);
391
- document.getElementById('workflows-count').textContent = String(state.workflows.length);
392
- document.getElementById('release-version').textContent = state.health.release?.packageVersion || 'unknown';
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
- const latestRun = state.runs[0];
395
- document.getElementById('latest-run-state').textContent = latestRun
396
- ? `${latestRun.state} · ${latestRun.selectedBackends?.memoryBackend || 'unknown'}`
397
- : 'No execution yet';
398
- document.getElementById('pages-health').textContent = state.health.docs?.pagesWorkflowValid
399
- ? 'Pages workflow healthy'
400
- : 'Pages workflow needs attention';
401
- }
402
-
403
- function renderRuns() {
404
- const container = document.getElementById('runs-list');
405
- if (!state.runs.length) {
406
- container.innerHTML = '<div class="empty">No runs yet.</div>';
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
- container.innerHTML = state.runs.map((run) => `
411
- <article class="card">
412
- <div class="card-header">
413
- <div>
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
- function renderSkills() {
431
- const container = document.getElementById('skills-list');
432
- if (!state.skills.length) {
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
- container.innerHTML = state.skills.slice(0, 12).map((skill) => `
438
- <article class="card">
439
- <div class="card-header">
440
- <div>
441
- <h3>${escapeHtml(skill.name)}</h3>
442
- <div class="meta">${escapeHtml(skill.provenance || '')}</div>
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
- function renderWorkflows() {
457
- const container = document.getElementById('workflows-list');
458
- if (!state.workflows.length) {
459
- container.innerHTML = '<div class="empty">No workflows loaded.</div>';
460
- return;
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
- container.innerHTML = state.workflows.slice(0, 12).map((workflow) => `
464
- <article class="card">
465
- <div class="card-header">
466
- <div>
467
- <h3>${escapeHtml(workflow.name)}</h3>
468
- <div class="meta">${escapeHtml(workflow.provenance || '')}</div>
469
- </div>
470
- <span class="state ${stateClass(workflow.rolloutStatus === 'revoked' ? 'failed' : workflow.rolloutStatus === 'promoted' ? 'merged' : 'idle')}">${escapeHtml(workflow.rolloutStatus)}</span>
471
- </div>
472
- <p>${escapeHtml(workflow.description || 'Workflow artifact')}</p>
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
- function renderBackends() {
483
- const container = document.getElementById('backends-list');
484
- const groups = Object.entries(state.backends || {});
485
- if (!groups.length) {
486
- container.innerHTML = '<div class="empty">Backend catalog unavailable.</div>';
487
- return;
488
- }
525
+ #graph-canvas {
526
+ width: 100%;
527
+ height: 100%;
528
+ display: block;
529
+ }
489
530
 
490
- container.innerHTML = groups.map(([group, entries]) => `
491
- <article class="card">
492
- <div class="card-header">
493
- <h3>${escapeHtml(group)}</h3>
494
- </div>
495
- <div class="chips">
496
- ${(entries || []).map((entry) => createChip(`${entry.kind}:${entry.mode}`)).join('')}
497
- </div>
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
- function renderHealth() {
503
- const container = document.getElementById('health-panel');
504
- const health = state.health || {};
505
- if (!Object.keys(health).length) {
506
- container.innerHTML = '<div class="empty">Health status unavailable.</div>';
507
- return;
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
- container.innerHTML = `
511
- <article class="card">
512
- <div class="card-header">
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
- function renderEvents() {
537
- const container = document.getElementById('event-feed');
538
- if (!state.events.length) {
539
- container.innerHTML = '<div class="empty">Waiting for runtime activity.</div>';
540
- return;
541
- }
553
+ .graph-node {
554
+ cursor: pointer;
555
+ }
542
556
 
543
- container.innerHTML = state.events.slice(0, 20).map((event) => `
544
- <div class="event">
545
- <strong>${escapeHtml(event.type)}</strong>
546
- <div class="meta">${new Date(event.timestamp).toLocaleTimeString()}</div>
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
- async function loadJson(url) {
553
- const response = await fetch(url);
554
- if (!response.ok) throw new Error(`${url} failed`);
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
- async function refreshAll() {
559
- const [runs, skills, workflows, backends, health] = await Promise.all([
560
- loadJson('/api/runs'),
561
- loadJson('/api/skills'),
562
- loadJson('/api/workflows'),
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
- async function boot() {
575
- try {
576
- await refreshAll();
577
- } catch (error) {
578
- console.error(error);
579
- }
575
+ .graph-node.active .halo {
576
+ opacity: 1;
577
+ }
580
578
 
581
- const eventSource = new EventSource('/stream');
582
- eventSource.addEventListener('open', () => {
583
- const pill = document.getElementById('stream-pill');
584
- pill.classList.add('live');
585
- pill.textContent = 'Event stream live';
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('&', '&amp;')
1155
+ .replaceAll('<', '&lt;')
1156
+ .replaceAll('>', '&gt;');
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
- eventSource.addEventListener('error', () => {
588
- const pill = document.getElementById('stream-pill');
589
- pill.classList.remove('live');
590
- pill.textContent = 'Event stream reconnecting';
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
- eventSource.onmessage = async (event) => {
593
- const parsed = JSON.parse(event.data);
594
- state.events.unshift(parsed);
595
- state.events = state.events.slice(0, 40);
596
- renderEvents();
597
- if (parsed.type.startsWith('phantom.') || parsed.type.startsWith('skill.') || parsed.type.startsWith('guardrail') || parsed.type.startsWith('memory.')) {
598
- try {
599
- await refreshAll();
600
- } catch (error) {
601
- console.error(error);
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
- boot();
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>