@theihtisham/agent-shadow-brain 6.0.3 → 6.2.0

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +40 -40
  2. package/README.md +427 -366
  3. package/dist/brain/brain-bus.d.ts +40 -0
  4. package/dist/brain/brain-bus.d.ts.map +1 -0
  5. package/dist/brain/brain-bus.js +94 -0
  6. package/dist/brain/brain-bus.js.map +1 -0
  7. package/dist/brain/collision-detective.d.ts.map +1 -1
  8. package/dist/brain/collision-detective.js +16 -0
  9. package/dist/brain/collision-detective.js.map +1 -1
  10. package/dist/brain/dream-engine.d.ts.map +1 -1
  11. package/dist/brain/dream-engine.js +12 -0
  12. package/dist/brain/dream-engine.js.map +1 -1
  13. package/dist/brain/forgetting-curve.d.ts.map +1 -1
  14. package/dist/brain/forgetting-curve.js +19 -0
  15. package/dist/brain/forgetting-curve.js.map +1 -1
  16. package/dist/brain/global-brain.d.ts +4 -0
  17. package/dist/brain/global-brain.d.ts.map +1 -1
  18. package/dist/brain/global-brain.js +26 -0
  19. package/dist/brain/global-brain.js.map +1 -1
  20. package/dist/brain/memory-graph.d.ts +51 -0
  21. package/dist/brain/memory-graph.d.ts.map +1 -0
  22. package/dist/brain/memory-graph.js +267 -0
  23. package/dist/brain/memory-graph.js.map +1 -0
  24. package/dist/brain/memory-seeder.d.ts.map +1 -1
  25. package/dist/brain/memory-seeder.js +23 -0
  26. package/dist/brain/memory-seeder.js.map +1 -1
  27. package/dist/brain/subagent-bridge.d.ts.map +1 -1
  28. package/dist/brain/subagent-bridge.js +12 -0
  29. package/dist/brain/subagent-bridge.js.map +1 -1
  30. package/dist/cli.js +1 -1
  31. package/dist/dashboard/server.d.ts +19 -0
  32. package/dist/dashboard/server.d.ts.map +1 -1
  33. package/dist/dashboard/server.js +169 -5
  34. package/dist/dashboard/server.js.map +1 -1
  35. package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
  36. package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
  37. package/dist/dashboard/v6-dashboard-html.js +2129 -942
  38. package/dist/dashboard/v6-dashboard-html.js.map +1 -1
  39. package/docs/launch/RELEASE_CHECKLIST_v6.0.3.md +102 -102
  40. package/docs/launch/RELEASE_NOTES_v6.0.3.md +111 -111
  41. package/docs/launch/RELEASE_NOTES_v6.1.0.md +38 -0
  42. package/docs/launch/RELEASE_NOTES_v6.2.0.md +54 -0
  43. package/package.json +27 -25
  44. package/tools/export-resume-pack.mjs +56 -56
  45. package/tools/record-global-memory.mjs +72 -72
@@ -2,12 +2,13 @@
2
2
  // Responsive, glassmorphism, dark/light, Chart.js analytics, command palette (Cmd+K),
3
3
  // toast notifications, Lucide icons, GSAP transitions. Served at / (default) and /hive.
4
4
  export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
5
- <html lang="en" data-theme="light">
5
+ <html lang="en" data-theme="light" data-navmode="simple">
6
6
  <head>
7
7
  <meta charset="UTF-8">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
9
9
  <meta name="color-scheme" content="dark light">
10
- <title>Shadow Brain v6.0 — Hive Mind Control</title>
10
+ <title>Shadow Brain v6.2 — Hive Mind Control</title>
11
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%230a0e1a'/><path d='M50 50 L24 30 M50 50 L76 30 M50 50 L24 70 M50 50 L76 70' stroke='%23ffffff' stroke-opacity='0.28' stroke-width='3'/><circle cx='50' cy='50' r='12' fill='%2318ffff'/><circle cx='24' cy='30' r='7' fill='%2310e68c'/><circle cx='76' cy='30' r='7' fill='%23ffb53f'/><circle cx='24' cy='70' r='7' fill='%23a855f7'/><circle cx='76' cy='70' r='7' fill='%23ec4899'/></svg>">
11
12
  <link rel="preconnect" href="https://cdn.jsdelivr.net">
12
13
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
13
14
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
@@ -49,10 +50,11 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
49
50
  --radius-lg: 14px;
50
51
  --radius-xl: 20px;
51
52
 
52
- --shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
53
- --shadow: 0 4px 16px rgba(0,0,0,0.24), 0 0 0 1px var(--border);
54
- --shadow-lg: 0 12px 48px rgba(0,0,0,0.45), 0 0 0 1px var(--border-strong);
53
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
54
+ --shadow: 0 6px 24px -6px rgba(0,0,0,0.5), 0 0 0 1px var(--border);
55
+ --shadow-lg: 0 18px 56px -12px rgba(0,0,0,0.6), 0 0 0 1px var(--border-strong);
55
56
  --shadow-glow: 0 0 40px rgba(24,255,255,0.15);
57
+ --card-sheen: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0) 42%);
56
58
 
57
59
  --duration-fast: 120ms;
58
60
  --duration: 220ms;
@@ -93,9 +95,10 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
93
95
  --danger: #dc2626;
94
96
  --info: #2563eb;
95
97
  --shadow-sm: 0 1px 2px rgba(15,23,42,0.06);
96
- --shadow: 0 4px 16px rgba(15,23,42,0.08), 0 0 0 1px var(--border);
97
- --shadow-lg: 0 12px 48px rgba(15,23,42,0.14), 0 0 0 1px var(--border-strong);
98
+ --shadow: 0 6px 22px -6px rgba(15,23,42,0.14), 0 0 0 1px var(--border);
99
+ --shadow-lg: 0 18px 50px -12px rgba(15,23,42,0.2), 0 0 0 1px var(--border-strong);
98
100
  --shadow-glow: 0 0 32px rgba(8,145,178,0.12);
101
+ --card-sheen: linear-gradient(180deg, rgba(255,255,255,0.5), rgba(255,255,255,0) 42%);
99
102
  }
100
103
  html[data-theme="light"] body::before {
101
104
  background:
@@ -289,28 +292,45 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
289
292
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
290
293
  color: var(--text-3); padding: 6px 12px; font-weight: 700;
291
294
  }
295
+ /* Simple / Advanced mode toggle */
296
+ .mode-toggle {
297
+ display: flex; gap: 3px; padding: 3px;
298
+ background: var(--bg-elev-1); border: 1px solid var(--border);
299
+ border-radius: 999px; margin: 0 6px 14px;
300
+ }
301
+ .mode-btn {
302
+ flex: 1; padding: 6px 10px; border-radius: 999px;
303
+ font-size: 11.5px; font-weight: 700; color: var(--text-3);
304
+ transition: all var(--duration-fast) var(--easing);
305
+ }
306
+ .mode-btn:hover { color: var(--text-2); }
307
+ .mode-btn.active { background: var(--brand-gradient); color: #fff; box-shadow: 0 2px 10px -2px rgba(168,85,247,0.5); }
308
+ /* In simple mode, advanced groups are hidden entirely. */
309
+ html[data-navmode="simple"] [data-adv] { display: none; }
292
310
  .nav-item {
293
311
  display: flex; align-items: center; gap: 10px;
294
312
  padding: 8px 12px;
295
- border-radius: 8px;
313
+ border-radius: 9px;
296
314
  cursor: pointer;
297
315
  font-size: 12.5px;
298
316
  color: var(--text-2);
299
- transition: all var(--duration-fast) var(--easing);
317
+ transition: background var(--duration-fast) var(--easing), color var(--duration-fast) var(--easing), transform var(--duration-fast) var(--easing);
300
318
  position: relative;
301
319
  width: 100%; text-align: left;
302
320
  font-weight: 500;
303
321
  }
304
- .nav-item:hover { background: var(--bg-elev-2); color: var(--text); }
322
+ .nav-item:hover { background: var(--bg-elev-2); color: var(--text); transform: translateX(2px); }
305
323
  .nav-item.active {
306
324
  background: var(--brand-gradient-soft);
307
325
  color: var(--text);
326
+ font-weight: 600;
308
327
  }
309
328
  .nav-item.active::before {
310
329
  content: "";
311
- position: absolute; left: 0; top: 4px; bottom: 4px;
312
- width: 3px; border-radius: 2px;
330
+ position: absolute; left: 0; top: 5px; bottom: 5px;
331
+ width: 3px; border-radius: 0 3px 3px 0;
313
332
  background: var(--brand-gradient);
333
+ box-shadow: 0 0 10px var(--brand-cyan);
314
334
  }
315
335
  .nav-item svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-3); transition: color var(--duration-fast); }
316
336
  .nav-item:hover svg, .nav-item.active svg { color: var(--brand-cyan); }
@@ -371,11 +391,18 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
371
391
  border: 1px solid var(--border);
372
392
  border-radius: var(--radius-lg);
373
393
  padding: 18px;
374
- transition: all var(--duration) var(--easing);
394
+ transition: transform var(--duration) var(--easing), box-shadow var(--duration) var(--easing), border-color var(--duration) var(--easing);
375
395
  position: relative;
376
396
  overflow: hidden;
397
+ box-shadow: var(--shadow-sm);
377
398
  }
378
- .card:hover { border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow); }
399
+ .card::after {
400
+ content: "";
401
+ position: absolute; inset: 0 0 auto 0; height: 50%;
402
+ background: var(--card-sheen);
403
+ pointer-events: none; border-radius: var(--radius-lg) var(--radius-lg) 0 0;
404
+ }
405
+ .card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--shadow); }
379
406
  .card.span-2 { grid-column: span 2; }
380
407
  .card.span-full { grid-column: 1 / -1; }
381
408
  .card.accent::before {
@@ -383,6 +410,9 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
383
410
  position: absolute; inset: 0; pointer-events: none;
384
411
  background: linear-gradient(135deg, rgba(24,255,255,0.08), transparent 50%);
385
412
  }
413
+ /* Canvas-bearing cards (constellation, charts, graphs) skip the top sheen so
414
+ visualizations stay crisp — especially in light theme. */
415
+ .card:has(canvas)::after { display: none; }
386
416
  .card-header {
387
417
  display: flex; align-items: center; justify-content: space-between;
388
418
  margin-bottom: 14px; gap: 8px;
@@ -397,98 +427,100 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
397
427
  .stat-card .label { font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }
398
428
  .stat-card .value { font-size: 32px; font-weight: 900; letter-spacing: -1px; margin: 6px 0 4px; font-variant-numeric: tabular-nums; }
399
429
  .stat-card .value.accent { background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
400
- .stat-card .trend { font-size: 11px; color: var(--text-2); display: flex; align-items: center; gap: 4px; }
401
- .stat-card .trend.up { color: var(--success); }
402
- .stat-card .trend.down { color: var(--danger); }
403
-
404
- .workflow-strip {
405
- display: grid;
406
- grid-template-columns: minmax(0, 1fr) auto;
407
- gap: 12px;
408
- align-items: end;
409
- margin-bottom: 16px;
410
- }
411
- .workflow-path {
412
- font-family: var(--font-mono);
413
- font-size: 12px;
414
- color: var(--text-2);
415
- background: var(--bg-elev-1);
416
- border: 1px solid var(--border);
417
- border-radius: 8px;
418
- padding: 10px 12px;
419
- width: 100%;
420
- }
421
- .warning-stack { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
422
- .warning-banner {
423
- display: flex;
424
- gap: 10px;
425
- align-items: flex-start;
426
- border: 1px solid rgba(217, 119, 6, 0.38);
427
- background: rgba(217, 119, 6, 0.10);
428
- color: var(--warning);
429
- border-radius: 8px;
430
- padding: 10px 12px;
431
- font-size: 12px;
432
- }
433
- .brain-feed { display: flex; flex-direction: column; gap: 10px; }
434
- .stack { display: flex; flex-direction: column; gap: 8px; }
435
- .memory-item {
436
- border: 1px solid var(--border);
437
- background: var(--bg-elev-1);
438
- border-radius: 8px;
439
- padding: 10px 12px;
440
- }
441
- .memory-item .meta {
442
- display: flex;
443
- gap: 8px;
444
- flex-wrap: wrap;
445
- align-items: center;
446
- margin-bottom: 6px;
447
- font-size: 11px;
448
- color: var(--text-3);
449
- }
450
- .memory-item .content {
451
- color: var(--text-2);
452
- font-size: 12px;
453
- line-height: 1.45;
454
- word-break: break-word;
455
- }
456
- .ops-agent-grid {
457
- display: grid;
458
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
459
- gap: 8px;
460
- }
461
- .ops-agent {
462
- border: 1px solid var(--border);
463
- background: var(--bg-elev-1);
464
- border-radius: 8px;
465
- padding: 9px 10px;
466
- font-size: 12px;
467
- }
468
- .ops-agent .name { font-weight: 700; margin-bottom: 5px; }
469
- .ops-agent .status { display: flex; gap: 6px; flex-wrap: wrap; }
470
- .empty-tight { color: var(--text-3); padding: 18px 4px; font-size: 12px; text-align: center; }
471
-
472
- /* ──────────────────────────────────────────────────────────────────── */
473
- /* Rows, pills, badges */
430
+ .stat-card .trend { font-size: 11px; color: var(--text-2); display: flex; align-items: center; gap: 4px; }
431
+ .stat-card .trend.up { color: var(--success); }
432
+ .stat-card .trend.down { color: var(--danger); }
433
+
434
+ .workflow-strip {
435
+ display: grid;
436
+ grid-template-columns: minmax(0, 1fr) auto;
437
+ gap: 12px;
438
+ align-items: end;
439
+ margin-bottom: 16px;
440
+ }
441
+ .workflow-path {
442
+ font-family: var(--font-mono);
443
+ font-size: 12px;
444
+ color: var(--text-2);
445
+ background: var(--bg-elev-1);
446
+ border: 1px solid var(--border);
447
+ border-radius: 8px;
448
+ padding: 10px 12px;
449
+ width: 100%;
450
+ }
451
+ .warning-stack { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
452
+ .warning-banner {
453
+ display: flex;
454
+ gap: 10px;
455
+ align-items: flex-start;
456
+ border: 1px solid rgba(217, 119, 6, 0.38);
457
+ background: rgba(217, 119, 6, 0.10);
458
+ color: var(--warning);
459
+ border-radius: 8px;
460
+ padding: 10px 12px;
461
+ font-size: 12px;
462
+ }
463
+ .brain-feed { display: flex; flex-direction: column; gap: 10px; }
464
+ .stack { display: flex; flex-direction: column; gap: 8px; }
465
+ .memory-item {
466
+ border: 1px solid var(--border);
467
+ background: var(--bg-elev-1);
468
+ border-radius: 8px;
469
+ padding: 10px 12px;
470
+ }
471
+ .memory-item .meta {
472
+ display: flex;
473
+ gap: 8px;
474
+ flex-wrap: wrap;
475
+ align-items: center;
476
+ margin-bottom: 6px;
477
+ font-size: 11px;
478
+ color: var(--text-3);
479
+ }
480
+ .memory-item .content {
481
+ color: var(--text-2);
482
+ font-size: 12px;
483
+ line-height: 1.45;
484
+ word-break: break-word;
485
+ }
486
+ .ops-agent-grid {
487
+ display: grid;
488
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
489
+ gap: 8px;
490
+ }
491
+ .ops-agent {
492
+ border: 1px solid var(--border);
493
+ background: var(--bg-elev-1);
494
+ border-radius: 8px;
495
+ padding: 9px 10px;
496
+ font-size: 12px;
497
+ }
498
+ .ops-agent .name { font-weight: 700; margin-bottom: 5px; }
499
+ .ops-agent .status { display: flex; gap: 6px; flex-wrap: wrap; }
500
+ .empty-tight { color: var(--text-3); padding: 18px 4px; font-size: 12px; text-align: center; }
501
+
502
+ /* ──────────────────────────────────────────────────────────────────── */
503
+ /* Rows, pills, badges */
474
504
  /* ──────────────────────────────────────────────────────────────────── */
475
505
  .row {
476
506
  display: flex; justify-content: space-between; align-items: center;
477
- padding: 8px 0; border-bottom: 1px solid var(--border);
478
- gap: 8px;
507
+ padding: 9px 6px; border-bottom: 1px solid var(--border);
508
+ gap: 8px; border-radius: 6px;
509
+ transition: background var(--duration-fast) var(--easing);
479
510
  }
511
+ .row:hover { background: var(--bg-elev-2); }
480
512
  .row:last-child { border: none; }
481
513
  .row .k { font-size: 12px; color: var(--text-2); }
482
- .row .v { font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
514
+ .row .v { font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
483
515
  .row .v.good { color: var(--success); }
484
516
  .row .v.warn { color: var(--warning); }
485
517
  .row .v.bad { color: var(--danger); }
486
518
 
487
519
  .pill {
488
520
  display: inline-flex; align-items: center; gap: 4px;
489
- padding: 2px 8px; border-radius: 9px;
521
+ padding: 3px 9px; border-radius: 999px;
490
522
  background: var(--bg-elev-3); border: 1px solid var(--border);
491
- font-size: 10px; font-weight: 600; letter-spacing: 0.02em;
523
+ font-size: 10px; font-weight: 700; letter-spacing: 0.02em;
492
524
  color: var(--text-2);
493
525
  }
494
526
  .pill.green { background: rgba(16,230,140,.12); color: var(--success); border-color: rgba(16,230,140,.3); }
@@ -506,24 +538,26 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
506
538
  padding: 8px 14px;
507
539
  background: var(--bg-elev-2);
508
540
  border: 1px solid var(--border);
509
- border-radius: 8px;
541
+ border-radius: 9px;
510
542
  color: var(--text);
511
543
  font-size: 12.5px;
512
544
  font-weight: 600;
513
- transition: all var(--duration) var(--easing);
545
+ transition: transform var(--duration-fast) var(--easing), box-shadow var(--duration) var(--easing), background var(--duration) var(--easing), border-color var(--duration) var(--easing), filter var(--duration) var(--easing);
514
546
  white-space: nowrap;
547
+ box-shadow: var(--shadow-sm);
515
548
  }
516
- .btn:hover { background: var(--bg-elev-3); border-color: var(--border-strong); transform: translateY(-1px); }
517
- .btn:active { transform: translateY(0); }
549
+ .btn:hover { background: var(--bg-elev-3); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow); }
550
+ .btn:active { transform: translateY(0) scale(0.98); }
518
551
  .btn svg { width: 13px; height: 13px; }
519
- .btn.sm { padding: 4px 10px; font-size: 11px; }
520
- .btn.lg { padding: 11px 18px; font-size: 13px; }
552
+ .btn.sm { padding: 4px 10px; font-size: 11px; border-radius: 7px; }
553
+ .btn.lg { padding: 11px 18px; font-size: 13px; border-radius: 11px; }
521
554
  .btn.primary {
522
555
  background: var(--brand-gradient);
523
556
  color: #fff;
524
557
  border-color: transparent;
558
+ box-shadow: 0 4px 14px -4px rgba(168,85,247,0.5);
525
559
  }
526
- .btn.primary:hover { filter: brightness(1.08); box-shadow: 0 6px 20px rgba(24,255,255,0.25); }
560
+ .btn.primary:hover { filter: brightness(1.08); transform: translateY(-1px); box-shadow: 0 8px 24px -4px rgba(24,255,255,0.4); }
527
561
  .btn.ghost { background: transparent; }
528
562
  .btn.ghost:hover { background: var(--bg-elev-2); }
529
563
  .btn.danger { color: var(--danger); border-color: rgba(255,88,100,.35); }
@@ -579,16 +613,18 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
579
613
  /* Tables */
580
614
  /* ──────────────────────────────────────────────────────────────────── */
581
615
  .table { width: 100%; border-collapse: collapse; }
582
- .table th, .table td { padding: 9px 12px; text-align: left; font-size: 12px; }
616
+ .table th, .table td { padding: 11px 14px; text-align: left; font-size: 12px; }
583
617
  .table th {
584
618
  background: var(--bg-elev-2);
585
619
  color: var(--text-3);
586
620
  font-weight: 700; letter-spacing: 0.06em;
587
621
  text-transform: uppercase; font-size: 10px;
588
- border-bottom: 1px solid var(--border);
622
+ border-bottom: 1px solid var(--border-strong);
623
+ position: sticky; top: 0; z-index: 1;
589
624
  }
590
- .table tr { border-bottom: 1px solid var(--border); transition: background var(--duration-fast); }
591
- .table tr:hover { background: var(--bg-elev-2); }
625
+ .table tbody tr { border-bottom: 1px solid var(--border); transition: background var(--duration-fast), box-shadow var(--duration-fast); }
626
+ .table tbody tr:last-child { border-bottom: none; }
627
+ .table tbody tr:hover { background: var(--brand-gradient-soft); box-shadow: inset 3px 0 0 var(--brand-cyan); }
592
628
  .table code { color: var(--brand-cyan); font-size: 11px; }
593
629
 
594
630
  .empty {
@@ -825,6 +861,181 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
825
861
  /* Skeleton loader */
826
862
  .skel { background: linear-gradient(90deg, var(--bg-elev-2) 0%, var(--bg-elev-3) 50%, var(--bg-elev-2) 100%); background-size: 200% 100%; animation: skel 1.2s linear infinite; border-radius: 6px; }
827
863
  @keyframes skel { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
864
+
865
+ /* ──────────────────────────────────────────────────────────────────── */
866
+ /* Realtime activity (v6.1) */
867
+ /* ──────────────────────────────────────────────────────────────────── */
868
+ .live-badge {
869
+ display: inline-flex; align-items: center; gap: 5px;
870
+ font-family: var(--font-mono); font-size: 9px; font-weight: 800; letter-spacing: 0.12em;
871
+ color: var(--success); background: rgba(16,230,140,.12);
872
+ border: 1px solid rgba(16,230,140,.32); border-radius: 999px;
873
+ padding: 2px 8px; vertical-align: middle;
874
+ }
875
+ .live-badge::before {
876
+ content: ""; width: 6px; height: 6px; border-radius: 50%;
877
+ background: var(--success); box-shadow: 0 0 6px var(--success);
878
+ animation: pulse 1.3s infinite;
879
+ }
880
+ .live-badge.paused { color: var(--warning); background: rgba(255,181,63,.12); border-color: rgba(255,181,63,.32); }
881
+ .live-badge.paused::before { background: var(--warning); box-shadow: none; animation: none; }
882
+ .filter-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
883
+ .chip {
884
+ padding: 5px 12px; border-radius: 999px; font-size: 11.5px; font-weight: 600;
885
+ background: var(--bg-elev-2); border: 1px solid var(--border); color: var(--text-2);
886
+ transition: all var(--duration-fast) var(--easing);
887
+ }
888
+ .chip:hover { border-color: var(--border-strong); color: var(--text); }
889
+ .chip.active { background: var(--brand-gradient); color: #fff; border-color: transparent; }
890
+ .act-search {
891
+ background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 8px;
892
+ padding: 7px 12px; color: var(--text); font-size: 12px; min-width: 180px;
893
+ }
894
+ .act-search:focus { outline: none; border-color: var(--brand-cyan); box-shadow: 0 0 0 3px rgba(24,255,255,0.12); }
895
+ tr.act-fresh { animation: actFreshIn var(--duration-slow) var(--easing); }
896
+ @keyframes actFreshIn { from { background: rgba(24,255,255,0.12); opacity: 0; transform: translateY(-4px); } to { background: transparent; opacity: 1; transform: none; } }
897
+ .conn-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--success); box-shadow: 0 0 6px var(--success); transition: all var(--duration); }
898
+ .conn-dot.offline { background: var(--warning); box-shadow: none; animation: pulse 1s infinite; }
899
+
900
+ /* ──────────────────────────────────────────────────────────────────── */
901
+ /* Memory Constellation (v6.2) */
902
+ /* ──────────────────────────────────────────────────────────────────── */
903
+ .const-wrap {
904
+ position: relative;
905
+ height: 600px;
906
+ background:
907
+ radial-gradient(ellipse at 30% 20%, rgba(24,255,255,0.05) 0%, transparent 55%),
908
+ radial-gradient(ellipse at 75% 80%, rgba(168,85,247,0.06) 0%, transparent 55%);
909
+ overflow: hidden;
910
+ cursor: grab;
911
+ }
912
+ .const-wrap.dragging { cursor: grabbing; }
913
+ .const-wrap canvas { display: block; width: 100%; height: 100%; }
914
+ .const-legend {
915
+ position: absolute; top: 12px; left: 12px;
916
+ display: flex; flex-direction: column; gap: 5px;
917
+ background: var(--bg-glass-strong); backdrop-filter: blur(12px);
918
+ border: 1px solid var(--border); border-radius: 8px; padding: 8px 11px;
919
+ font-size: 11px; color: var(--text-2); pointer-events: none;
920
+ }
921
+ .const-legend .lg-row { display: flex; align-items: center; gap: 7px; }
922
+ .const-legend .lg-dot { width: 9px; height: 9px; border-radius: 50%; box-shadow: 0 0 6px currentColor; }
923
+ .const-meta {
924
+ position: absolute; top: 12px; right: 12px;
925
+ background: var(--bg-glass-strong); backdrop-filter: blur(12px);
926
+ border: 1px solid var(--border); border-radius: 8px; padding: 6px 11px;
927
+ font-family: var(--font-mono); font-size: 10.5px; color: var(--brand-cyan); pointer-events: none;
928
+ }
929
+ .const-hint {
930
+ position: absolute; bottom: 10px; left: 12px;
931
+ font-size: 10.5px; color: var(--text-3); pointer-events: none;
932
+ background: var(--bg-glass); border-radius: 6px; padding: 3px 9px;
933
+ }
934
+ .const-empty {
935
+ position: absolute; inset: 0; display: none;
936
+ flex-direction: column; align-items: center; justify-content: center;
937
+ text-align: center; gap: 8px; padding: 24px;
938
+ }
939
+ .const-empty.show { display: flex; }
940
+ .const-empty svg { width: 52px; height: 52px; color: var(--text-dim); margin-bottom: 4px; }
941
+ .const-empty .ce-title { font-size: 15px; font-weight: 700; color: var(--text-2); }
942
+ .const-empty .ce-msg { font-size: 12px; color: var(--text-3); max-width: 360px; line-height: 1.5; }
943
+ .const-drawer {
944
+ position: absolute; top: 0; right: 0; bottom: 0; width: min(340px, 80%);
945
+ background: var(--bg-glass-strong); backdrop-filter: blur(28px);
946
+ border-left: 1px solid var(--border-strong);
947
+ transform: translateX(100%); transition: transform var(--duration-slow) var(--easing);
948
+ padding: 16px; overflow-y: auto; z-index: 5;
949
+ }
950
+ .const-drawer.open { transform: translateX(0); box-shadow: -12px 0 40px rgba(0,0,0,0.35); }
951
+ .const-drawer-close {
952
+ position: absolute; top: 10px; right: 12px;
953
+ width: 26px; height: 26px; border-radius: 6px; font-size: 18px; line-height: 1;
954
+ color: var(--text-2); background: var(--bg-elev-2); border: 1px solid var(--border);
955
+ }
956
+ .const-drawer-close:hover { color: var(--text); background: var(--bg-elev-3); }
957
+ .cd-type { font-size: 10px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }
958
+ .cd-title { font-size: 15px; font-weight: 700; margin: 6px 0 12px; line-height: 1.35; word-break: break-word; }
959
+ .cd-section { margin-top: 12px; }
960
+ .cd-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-3); margin-bottom: 4px; font-weight: 700; }
961
+ .cd-content { font-size: 12px; color: var(--text-2); line-height: 1.5; word-break: break-word; }
962
+ .const-tip {
963
+ position: absolute; pointer-events: none; z-index: 6; display: none;
964
+ background: var(--bg-glass-strong); backdrop-filter: blur(12px);
965
+ border: 1px solid var(--border-strong); border-radius: 7px; padding: 6px 10px;
966
+ font-size: 11px; color: var(--text); max-width: 260px;
967
+ transform: translate(-50%, calc(-100% - 12px));
968
+ }
969
+ .const-tip.show { display: block; }
970
+ .tag-cloud { display: flex; flex-wrap: wrap; gap: 7px; }
971
+ .tag-cloud .tc {
972
+ border: 1px solid var(--border); border-radius: 999px; padding: 4px 11px;
973
+ font-size: 12px; color: var(--text-2); background: var(--bg-elev-1);
974
+ }
975
+ .tag-cloud .tc b { color: var(--brand-cyan); font-variant-numeric: tabular-nums; }
976
+
977
+ /* Overview constellation hero */
978
+ .const-hero { background: linear-gradient(120deg, rgba(24,255,255,0.08), rgba(168,85,247,0.08) 60%, transparent); }
979
+ .const-hero:hover { border-color: var(--border-accent); box-shadow: var(--shadow-glow); }
980
+ .const-hero-inner { display: flex; align-items: center; gap: 20px; }
981
+ .const-hero-art { flex-shrink: 0; width: 132px; height: 96px; }
982
+ .const-hero-art svg { width: 100%; height: 100%; filter: drop-shadow(0 0 8px rgba(24,255,255,0.35)); }
983
+ .const-hero-art .he { stroke: rgba(24,255,255,0.4); stroke-width: 1.2; }
984
+ .const-hero-art circle { animation: synapse 1.6s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
985
+ .const-hero-body { flex: 1; min-width: 0; }
986
+ .const-hero-title { font-size: 19px; font-weight: 800; letter-spacing: -0.4px; background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
987
+ .const-hero-sub { font-size: 12.5px; color: var(--text-3); margin: 4px 0 10px; }
988
+ .const-hero-stats { display: flex; gap: 18px; font-size: 12px; color: var(--text-2); }
989
+ .const-hero-stats b { font-size: 17px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; margin-right: 4px; }
990
+ @media (max-width: 768px) { .const-hero-art { display: none; } .const-hero-inner { gap: 12px; } }
991
+
992
+ /* ──────────────────────────────────────────────────────────────────── */
993
+ /* Revived tab visualizations (v6.2) */
994
+ /* ──────────────────────────────────────────────────────────────────── */
995
+ /* Reusable empty-state */
996
+ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 8px; padding: 36px 20px; }
997
+ .empty-state svg { width: 44px; height: 44px; color: var(--text-dim); margin-bottom: 4px; }
998
+ .empty-state .es-title { font-size: 14px; font-weight: 700; color: var(--text-2); }
999
+ .empty-state .es-msg { font-size: 12px; color: var(--text-3); max-width: 380px; line-height: 1.5; }
1000
+ .empty-state .es-cta { margin-top: 6px; }
1001
+
1002
+ /* Risk gauge + matrix (Pre-Mortem) */
1003
+ .risk-head { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin-bottom: 16px; }
1004
+ .risk-gauge { position: relative; width: 96px; height: 96px; flex-shrink: 0; }
1005
+ .risk-gauge canvas { width: 96px; height: 96px; }
1006
+ .risk-gauge .rg-num { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 900; }
1007
+ .risk-matrix { display: grid; grid-template-columns: 64px repeat(4, 1fr); gap: 5px; margin: 14px 0; }
1008
+ .risk-matrix .rm-h { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-3); display: flex; align-items: center; justify-content: center; padding: 4px; }
1009
+ .risk-matrix .rm-yl { font-size: 10px; color: var(--text-3); display: flex; align-items: center; justify-content: flex-end; padding-right: 6px; }
1010
+ .risk-matrix .rm-cell { aspect-ratio: 2.6/1; border-radius: 6px; border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 13px; color: #fff; min-height: 30px; transition: all var(--duration); }
1011
+ .risk-matrix .rm-cell.empty { background: var(--bg-elev-1); color: var(--text-dim); font-weight: 400; }
1012
+ .fail-card { padding: 12px 14px; background: var(--bg-elev-1); border: 1px solid var(--border); border-left-width: 3px; border-radius: 8px; margin-bottom: 8px; }
1013
+ .fail-card .fc-head { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }
1014
+ .fail-card .fc-desc { font-size: 12.5px; color: var(--text); line-height: 1.45; }
1015
+ .fail-card .fc-mit { font-size: 11.5px; color: var(--text-2); margin-top: 6px; padding-left: 14px; border-left: 2px solid var(--success); }
1016
+
1017
+ /* Attention heat bars */
1018
+ .heat-row { margin-bottom: 10px; }
1019
+ .heat-row .hr-top { display: flex; justify-content: space-between; align-items: center; font-size: 11.5px; margin-bottom: 4px; gap: 8px; }
1020
+ .heat-row .hr-pct { font-family: var(--font-mono); font-weight: 800; flex-shrink: 0; }
1021
+ .heat-row .hr-track { height: 22px; background: var(--bg-elev-1); border-radius: 5px; overflow: hidden; border: 1px solid var(--border); }
1022
+ .heat-row .hr-fill { height: 100%; border-radius: 5px 0 0 5px; transition: width var(--duration-slow) var(--easing); }
1023
+ .heat-row .hr-reason { font-size: 10.5px; color: var(--text-3); margin-top: 3px; }
1024
+
1025
+ /* Reputation trust cards */
1026
+ .trust-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
1027
+ .trust-card { background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 12px; padding: 16px; display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; transition: all var(--duration); }
1028
+ .trust-card:hover { border-color: var(--border-accent); transform: translateY(-2px); }
1029
+ .trust-ring { position: relative; width: 92px; height: 92px; }
1030
+ .trust-ring canvas { width: 92px; height: 92px; }
1031
+ .trust-ring .tr-pct { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 19px; font-weight: 900; }
1032
+ .trust-card .tc-name { font-weight: 700; font-size: 13px; }
1033
+ .trust-card .tc-sub { font-size: 11px; color: var(--text-3); }
1034
+
1035
+ /* Causal / DAG canvas */
1036
+ .dag-wrap { position: relative; height: 420px; background: radial-gradient(ellipse at center, rgba(168,85,247,0.04), transparent 70%); border-radius: 10px; overflow: hidden; border: 1px solid var(--border); }
1037
+ .dag-wrap canvas { display: block; width: 100%; height: 100%; }
1038
+ .dag-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--text-3); font-size: 12.5px; text-align: center; padding: 20px; }
828
1039
  </style>
829
1040
  </head>
830
1041
  <body>
@@ -860,18 +1071,19 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
860
1071
  </div>
861
1072
 
862
1073
  <div class="brand">
863
- <div class="name">Shadow Brain <span style="color:var(--text-3);font-size:10px;font-weight:600">v6.0 Hive Mind</span></div>
1074
+ <div class="name">Shadow Brain <span style="color:var(--text-3);font-size:10px;font-weight:600">v6.2 Hive Mind</span></div>
864
1075
  <div class="state" id="brain-state">learning · reflecting · charging</div>
865
1076
  </div>
866
1077
 
867
1078
  <div class="header-stats" id="header-stats">
868
1079
  <div class="stat-pill"><span class="n" id="stat-agents">—</span> agents</div>
869
1080
  <div class="stat-pill"><span class="n" id="stat-memories">—</span> memories</div>
870
- <div class="stat-pill"><span class="n" id="stat-dreams">—</span> dreams</div>
871
- <div class="stat-pill"><span class="n" id="stat-savings">$—</span> saved</div>
1081
+ <div class="stat-pill"><span class="n" id="stat-dreams">—</span> reflections</div>
1082
+ <div class="stat-pill"><span class="n" id="stat-tools">—</span> tools</div>
872
1083
  </div>
873
1084
 
874
1085
  <div class="header-right">
1086
+ <span class="conn-dot" id="conn-dot" title="Realtime connection: connecting"></span>
875
1087
  <button class="kbd-hint" onclick="openCmdPalette()" title="Open command palette">
876
1088
  <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
877
1089
  Search <kbd>⌘K</kbd>
@@ -890,79 +1102,161 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
890
1102
  ══════════════════════════════════════════════════════════════════════ -->
891
1103
  <div class="layout">
892
1104
  <aside class="sidebar" id="sidebar">
1105
+ <div class="mode-toggle" id="mode-toggle" title="Simple shows the essentials. Advanced reveals every feature.">
1106
+ <button class="mode-btn active" data-mode="simple" onclick="setMode('simple')">Simple</button>
1107
+ <button class="mode-btn" data-mode="advanced" onclick="setMode('advanced')">Advanced</button>
1108
+ </div>
1109
+
893
1110
  <div class="nav-group">
894
- <div class="nav-group-label">Overview</div>
895
- <button class="nav-item active" data-tab="overview" data-title="Overview" data-icon="activity">
896
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
897
- Dashboard
898
- </button>
899
- <button class="nav-item" data-tab="ww2br" data-title="WW2BR Brain">
900
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5"/><path d="M4 6h9l1 2h6v8h-9l-1-2H4"/></svg>
901
- WW2BR Brain<span class="badge-count" id="badge-ww2br">0</span>
902
- </button>
903
- <button class="nav-item" data-tab="graph" data-title="Live Graph">
904
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><circle cx="12" cy="12" r="8"/></svg>
905
- Live Graph
906
- </button>
907
- <button class="nav-item" data-tab="agents" data-title="Agents">
1111
+ <div class="nav-group-label">Main</div>
1112
+ <button class="nav-item active" data-tab="overview" data-title="Home" title="Your starting point — a quick summary of everything the brain knows and is doing.">
1113
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
1114
+ Home
1115
+ </button>
1116
+ <button class="nav-item" data-tab="intelligence" data-title="Brain Map" title="A visual map of everything the brain remembers — memories, projects, and the tools that created them.">
1117
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><circle cx="5" cy="18" r="2"/><circle cx="19" cy="18" r="2"/><line x1="7" y1="7" x2="10" y2="10.5"/><line x1="17" y1="7" x2="14" y2="10.5"/><line x1="7" y1="17" x2="10" y2="13.5"/><line x1="17" y1="17" x2="14" y2="13.5"/></svg>
1118
+ Brain Map<span class="badge-count" id="badge-intelligence">0</span>
1119
+ </button>
1120
+ <button class="nav-item" data-tab="memory" data-title="Memories" title="Browse and search everything the brain has learned and saved.">
1121
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
1122
+ Memories
1123
+ </button>
1124
+ <button class="nav-item" data-tab="chat" data-title="Ask the Brain" title="Ask a question in plain language and get answers from the brain's memory.">
1125
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1126
+ Ask the Brain
1127
+ </button>
1128
+ <button class="nav-item" data-tab="activity" data-title="Activity" title="A live feed of what the brain and your tools are doing right now.">
1129
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
1130
+ Activity
1131
+ </button>
1132
+ <button class="nav-item" data-tab="agents" data-title="Connected Tools" title="See which AI coding tools (Claude, Cursor, Codex…) are connected to your brain.">
908
1133
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
909
- Agents<span class="badge-count" id="badge-agents">0</span>
1134
+ Connected Tools<span class="badge-count" id="badge-agents">0</span>
1135
+ </button>
1136
+ <button class="nav-item" data-tab="livebrain" data-title="Brain Health" title="A health check of your brain's memory quality, with tips to improve it.">
1137
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
1138
+ Brain Health
910
1139
  </button>
911
- <button class="nav-item" data-tab="chat" data-title="Chat with Brain"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>Chat with Brain</button>
912
- <button class="nav-item" data-tab="memory" data-title="Memory Browser">
913
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
914
- Memory Browser
915
- </button>
916
- <button class="nav-item" data-tab="seeder" data-title="Memory Seeder">
917
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20"/><path d="M5 9c0-3 2-5 7-7 5 2 7 4 7 7 0 5-4 8-7 8s-7-3-7-8z"/><path d="M8 12h8"/></svg>
918
- Memory Seeder<span class="badge-count" id="badge-seeder">0</span>
919
- </button>
920
- <button class="nav-item" data-tab="livebrain" data-title="Live Brain">
921
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4"/><path d="M12 18v4"/><path d="m4.93 4.93 2.83 2.83"/><path d="m16.24 16.24 2.83 2.83"/><path d="M2 12h4"/><path d="M18 12h4"/><circle cx="12" cy="12" r="5"/></svg>
922
- Live Brain
923
- </button>
924
- <button class="nav-item" data-tab="activity" data-title="Activity Log">
925
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
926
- Activity Log
1140
+ <button class="nav-item" data-tab="config" data-title="Settings" title="Set up and adjust how Shadow Brain works.">
1141
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
1142
+ Settings
927
1143
  </button>
928
1144
  </div>
929
1145
 
930
- <div class="nav-group">
931
- <div class="nav-group-label">Cognition</div>
932
- <button class="nav-item" data-tab="sabb" data-title="Sub-Agent Bridge"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>SABB<span class="badge-count" id="badge-sabb">0</span></button>
933
- <button class="nav-item" data-tab="causal" data-title="Causal Chains"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>Causal<span class="badge-count" id="badge-causal">0</span></button>
934
- <button class="nav-item" data-tab="dream" data-title="Dream Engine"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>Dream<span class="badge-count" id="badge-dream">0</span></button>
935
- <button class="nav-item" data-tab="debate" data-title="Swarm Debate"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>Swarm Debate</button>
936
- <button class="nav-item" data-tab="premortem" data-title="Pre-Mortem"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>Pre-Mortem</button>
937
- <button class="nav-item" data-tab="branch" data-title="Branch Brains"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>Branch Brains</button>
938
- <button class="nav-item" data-tab="forget" data-title="Forgetting Curve"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>Forgetting</button>
1146
+ <div class="nav-group" data-adv="1">
1147
+ <div class="nav-group-label">Explore</div>
1148
+ <button class="nav-item" data-tab="ww2br" data-title="Project Memory" title="Memory and status for one specific project.">
1149
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5"/><path d="M4 6h9l1 2h6v8h-9l-1-2H4"/></svg>
1150
+ Project Memory<span class="badge-count" id="badge-ww2br">0</span>
1151
+ </button>
1152
+ <button class="nav-item" data-tab="graph" data-title="Live Network" title="A live animation of your tools talking to the brain.">
1153
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><circle cx="12" cy="12" r="8"/></svg>
1154
+ Live Network
1155
+ </button>
1156
+ <button class="nav-item" data-tab="seeder" data-title="Add Knowledge" title="Research a topic and save it as verified, source-backed knowledge.">
1157
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20"/><path d="M5 9c0-3 2-5 7-7 5 2 7 4 7 7 0 5-4 8-7 8s-7-3-7-8z"/><path d="M8 12h8"/></svg>
1158
+ Add Knowledge<span class="badge-count" id="badge-seeder">0</span>
1159
+ </button>
1160
+ <button class="nav-item" data-tab="garden" data-title="Memory Garden" title="A relaxing visual of your memories growing over time.">
1161
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s-8-4-8-12c0-4 3-7 8-7s8 3 8 7c0 8-8 12-8 12z"/><path d="M12 17v-5M12 7V5"/></svg>
1162
+ Memory Garden
1163
+ </button>
939
1164
  </div>
940
1165
 
941
- <div class="nav-group">
942
- <div class="nav-group-label">Safety & Trust</div>
943
- <button class="nav-item" data-tab="collision" data-title="Collision Detective"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>Collisions<span class="badge-count" id="badge-collision">0</span></button>
944
- <button class="nav-item" data-tab="reputation" data-title="Reputation Ledger"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="7"/><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/></svg>Reputation</button>
945
- <button class="nav-item" data-tab="calibration" data-title="Calibration"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>Calibration</button>
946
- <button class="nav-item" data-tab="formal" data-title="Formal Rules"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>Formal Rules</button>
947
- <button class="nav-item" data-tab="privacy" data-title="Privacy & Safety"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Privacy & Safety</button>
948
- <button class="nav-item" data-tab="attention" data-title="Attention Heatmap"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/><line x1="21.17" y1="8" x2="12" y2="8"/></svg>Attention</button>
1166
+ <div class="nav-group" data-adv="1">
1167
+ <div class="nav-group-label">How the Brain Thinks</div>
1168
+ <button class="nav-item" data-tab="sabb" data-title="Sub-Agents" title="When a tool spawns helper agents, the brain shares the right context with them.">
1169
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
1170
+ Sub-Agents<span class="badge-count" id="badge-sabb">0</span>
1171
+ </button>
1172
+ <button class="nav-item" data-tab="causal" data-title="Decision Trails" title="Trace why a decision was made — what led to it and what it caused.">
1173
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
1174
+ Decision Trails<span class="badge-count" id="badge-causal">0</span>
1175
+ </button>
1176
+ <button class="nav-item" data-tab="dream" data-title="Reflections" title="While idle, the brain reviews past work and finds patterns and lessons.">
1177
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
1178
+ Reflections<span class="badge-count" id="badge-dream">0</span>
1179
+ </button>
1180
+ <button class="nav-item" data-tab="debate" data-title="Debates" title="Have multiple AI viewpoints argue a tough decision, then get a verdict.">
1181
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1182
+ Debates
1183
+ </button>
1184
+ <button class="nav-item" data-tab="premortem" data-title="Risk Check" title="Before a big task, see what went wrong last time so you can avoid it.">
1185
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
1186
+ Risk Check
1187
+ </button>
1188
+ <button class="nav-item" data-tab="branch" data-title="Branch Memory" title="Memory that follows your git branch, so each branch has its own context.">
1189
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
1190
+ Branch Memory
1191
+ </button>
1192
+ <button class="nav-item" data-tab="forget" data-title="Memory Cleanup" title="Old, unused memories fade while important ones are kept — like human memory.">
1193
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
1194
+ Memory Cleanup
1195
+ </button>
949
1196
  </div>
950
1197
 
951
- <div class="nav-group">
952
- <div class="nav-group-label">Economy & Growth</div>
953
- <button class="nav-item" data-tab="tokens" data-title="Token Economy"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>Tokens</button>
954
- <button class="nav-item" data-tab="voice" data-title="Voice Mode"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/></svg>Voice</button>
955
- <button class="nav-item" data-tab="garden" data-title="Brain Garden"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s-8-4-8-12c0-4 3-7 8-7s8 3 8 7c0 8-8 12-8 12z"/><path d="M12 17v-5M12 7V5"/></svg>Garden</button>
956
- <button class="nav-item" data-tab="pr" data-title="PR Auto-Review"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><line x1="6" y1="9" x2="6" y2="21"/></svg>PR Review</button>
957
- <button class="nav-item" data-tab="team" data-title="Team Sync"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>Team Sync</button>
958
- <button class="nav-item" data-tab="exchange" data-title="Brain Exchange"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/></svg>Exchange</button>
1198
+ <div class="nav-group" data-adv="1">
1199
+ <div class="nav-group-label">Safety &amp; Trust</div>
1200
+ <button class="nav-item" data-tab="collision" data-title="Conflicts" title="Catches two tools about to edit the same code at the same time.">
1201
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>
1202
+ Conflicts<span class="badge-count" id="badge-collision">0</span>
1203
+ </button>
1204
+ <button class="nav-item" data-tab="reputation" data-title="Trust Scores" title="How reliable each AI tool has been, based on its track record.">
1205
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="7"/><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/></svg>
1206
+ Trust Scores
1207
+ </button>
1208
+ <button class="nav-item" data-tab="calibration" data-title="Accuracy" title="How well each tool's confidence matches how often it's actually right.">
1209
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>
1210
+ Accuracy
1211
+ </button>
1212
+ <button class="nav-item" data-tab="attention" data-title="Focus Map" title="Shows which memories most influenced a particular decision.">
1213
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/><line x1="21.17" y1="8" x2="12" y2="8"/></svg>
1214
+ Focus Map
1215
+ </button>
1216
+ <button class="nav-item" data-tab="formal" data-title="Code Rules" title="Turn plain-language rules into checks your linters can enforce.">
1217
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
1218
+ Code Rules
1219
+ </button>
1220
+ <button class="nav-item" data-tab="privacy" data-title="Privacy &amp; Safety" title="Keep everything offline and block risky actions.">
1221
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
1222
+ Privacy &amp; Safety
1223
+ </button>
959
1224
  </div>
960
1225
 
961
- <div class="nav-group">
1226
+ <div class="nav-group" data-adv="1">
1227
+ <div class="nav-group-label">Cost &amp; Sharing</div>
1228
+ <button class="nav-item" data-tab="tokens" data-title="Cost &amp; Usage" title="Track how much your AI tools are spending and where you can save.">
1229
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
1230
+ Cost &amp; Usage
1231
+ </button>
1232
+ <button class="nav-item" data-tab="pr" data-title="Code Review" title="Generate a review for a pull request using what the brain knows.">
1233
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><line x1="6" y1="9" x2="6" y2="21"/></svg>
1234
+ Code Review
1235
+ </button>
1236
+ <button class="nav-item" data-tab="voice" data-title="Voice" title="Talk to the brain and hear answers, hands-free.">
1237
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/></svg>
1238
+ Voice
1239
+ </button>
1240
+ <button class="nav-item" data-tab="team" data-title="Team Sync" title="Share brain memory with your teammates.">
1241
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
1242
+ Team Sync
1243
+ </button>
1244
+ <button class="nav-item" data-tab="exchange" data-title="Share Brain" title="Export a memory pack to share, or import one from someone else.">
1245
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/></svg>
1246
+ Share Brain
1247
+ </button>
1248
+ </div>
1249
+
1250
+ <div class="nav-group" data-adv="1">
962
1251
  <div class="nav-group-label">System</div>
963
- <button class="nav-item" data-tab="features" data-title="Feature Toggles"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="5" width="22" height="14" rx="7"/><circle cx="16" cy="12" r="3"/></svg>Feature Toggles</button>
964
- <button class="nav-item" data-tab="models" data-title="Models & Intelligence"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>Models & Intel</button>
965
- <button class="nav-item" data-tab="config" data-title="Configuration"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>Configuration</button>
1252
+ <button class="nav-item" data-tab="features" data-title="Features" title="Turn individual brain features on or off.">
1253
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="5" width="22" height="14" rx="7"/><circle cx="16" cy="12" r="3"/></svg>
1254
+ Features
1255
+ </button>
1256
+ <button class="nav-item" data-tab="models" data-title="AI Models" title="Choose and configure which AI models the brain uses.">
1257
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
1258
+ AI Models
1259
+ </button>
966
1260
  </div>
967
1261
  </aside>
968
1262
 
@@ -973,21 +1267,21 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
973
1267
  <div class="accent-bar"></div>
974
1268
  <div class="section-header">
975
1269
  <div>
976
- <h1 class="section-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>Hive Mind Overview</h1>
977
- <div class="section-subtitle">Real-time cross-agent intelligence · 22 modules · local-first</div>
1270
+ <h1 class="section-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>Welcome to your Shadow Brain</h1>
1271
+ <div class="section-subtitle">A shared memory for your AI coding tools — private, on your machine. Here's what it knows and is doing right now.</div>
978
1272
  </div>
979
1273
  <div class="section-actions">
980
- <button class="btn" onclick="runDream()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>Dream Now</button>
981
- <button class="btn" onclick="runForget()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>Consolidate</button>
982
- <button class="btn danger" onclick="toggleAirGap()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Air-Gap</button>
1274
+ <button class="btn" onclick="runDream()" title="Have the brain review recent work and find patterns"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>Reflect Now</button>
1275
+ <button class="btn" onclick="runForget()" title="Tidy memory — fade old entries, keep the important ones"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>Clean Up</button>
1276
+ <button class="btn danger" onclick="toggleAirGap()" title="Block all outgoing network — fully offline"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Go Offline</button>
983
1277
  </div>
984
1278
  </div>
985
1279
 
986
1280
  <div class="grid cols-4">
987
1281
  <div class="card stat-card accent">
988
- <div class="label">Agents Connected</div>
1282
+ <div class="label">Tools Connected</div>
989
1283
  <div class="value accent" id="ov-agents">—</div>
990
- <div class="trend">of 10 supported</div>
1284
+ <div class="trend">AI tools linked to your brain</div>
991
1285
  </div>
992
1286
  <div class="card stat-card accent">
993
1287
  <div class="label">Memories Stored</div>
@@ -995,20 +1289,46 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
995
1289
  <div class="trend" id="ov-memories-sub">across all projects</div>
996
1290
  </div>
997
1291
  <div class="card stat-card accent">
998
- <div class="label">Sub-Agents (SABB)</div>
1292
+ <div class="label">Helper Agents</div>
999
1293
  <div class="value" id="ov-subagents">—</div>
1000
1294
  <div class="trend" id="ov-subagents-sub">graduated / quarantined</div>
1001
1295
  </div>
1002
1296
  <div class="card stat-card accent">
1003
- <div class="label">Monthly Savings</div>
1004
- <div class="value" style="color:var(--success)" id="ov-savings">$—</div>
1005
- <div class="trend up" id="ov-savings-sub">projection</div>
1297
+ <div class="label">Active Tool</div>
1298
+ <div class="value" style="font-size:22px" id="ov-current-agent">—</div>
1299
+ <div class="trend" id="ov-current-agent-sub">using the brain right now</div>
1300
+ </div>
1301
+ </div>
1302
+
1303
+ <div class="card accent span-full mt-lg const-hero" onclick="switchTab('intelligence','')" style="cursor:pointer">
1304
+ <div class="const-hero-inner">
1305
+ <div class="const-hero-art">
1306
+ <svg viewBox="0 0 120 90" xmlns="http://www.w3.org/2000/svg">
1307
+ <line x1="60" y1="45" x2="24" y2="20" class="he"/><line x1="60" y1="45" x2="98" y2="22" class="he"/>
1308
+ <line x1="60" y1="45" x2="22" y2="68" class="he"/><line x1="60" y1="45" x2="100" y2="66" class="he"/>
1309
+ <line x1="60" y1="45" x2="60" y2="14" class="he"/><line x1="24" y1="20" x2="22" y2="68" class="he"/>
1310
+ <circle cx="60" cy="45" r="9" fill="#18ffff"/>
1311
+ <circle cx="24" cy="20" r="5" fill="#10e68c"/><circle cx="98" cy="22" r="5" fill="#ffb53f"/>
1312
+ <circle cx="22" cy="68" r="5" fill="#a855f7"/><circle cx="100" cy="66" r="4" fill="#18ffff"/>
1313
+ <circle cx="60" cy="14" r="4" fill="#ec4899"/>
1314
+ </svg>
1315
+ </div>
1316
+ <div class="const-hero-body">
1317
+ <div class="const-hero-title">Memory Constellation</div>
1318
+ <div class="const-hero-sub">Explore every memory, project, agent &amp; tag as a living, importance-ranked graph.</div>
1319
+ <div class="const-hero-stats">
1320
+ <span><b id="hero-const-nodes">—</b> nodes</span>
1321
+ <span><b id="hero-const-edges">—</b> links</span>
1322
+ <span><b id="hero-const-mem">—</b> memories</span>
1323
+ </div>
1324
+ </div>
1325
+ <button class="btn primary lg" onclick="event.stopPropagation();switchTab('intelligence','')">Explore <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg></button>
1006
1326
  </div>
1007
1327
  </div>
1008
1328
 
1009
1329
  <div class="grid cols-3 mt-lg">
1010
1330
  <div class="card span-2">
1011
- <div class="card-header"><div class="card-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>Historical Recorded Spend by Agent (last 7 days)</div></div>
1331
+ <div class="card-header"><div class="card-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>Reported Token Usage by Tool</div></div>
1012
1332
  <div class="chart-wrap"><canvas id="chart-tokens"></canvas></div>
1013
1333
  </div>
1014
1334
  <div class="card">
@@ -1018,6 +1338,13 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1018
1338
  </div>
1019
1339
 
1020
1340
  <div class="grid cols-2 mt-lg">
1341
+ <div class="card span-full accent">
1342
+ <div class="card-header">
1343
+ <div class="card-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>Live Pulse <span class="live-badge" id="ov-pulse-badge">LIVE</span></div>
1344
+ <span class="pill cyan" id="ov-pulse-rate">0 / min</span>
1345
+ </div>
1346
+ <div id="ov-pulse" class="brain-feed" style="max-height:300px;overflow-y:auto"><div class="empty-tight">Awaiting live activity…</div></div>
1347
+ </div>
1021
1348
  <div class="card">
1022
1349
  <div class="card-header"><div class="card-title">Module Status</div></div>
1023
1350
  <div id="ov-modules"></div>
@@ -1029,106 +1356,170 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1029
1356
  </div>
1030
1357
  </section>
1031
1358
 
1359
+ <!-- ════ INTELLIGENCE (Memory Constellation + Analytics) ═════════ -->
1360
+ <section class="section" id="tab-intelligence">
1361
+ <div class="accent-bar"></div>
1362
+ <div class="section-header">
1363
+ <div>
1364
+ <h1 class="section-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><circle cx="5" cy="18" r="2"/><circle cx="19" cy="18" r="2"/><line x1="7" y1="7" x2="10" y2="10.5"/><line x1="17" y1="7" x2="14" y2="10.5"/><line x1="7" y1="17" x2="10" y2="13.5"/><line x1="17" y1="17" x2="14" y2="13.5"/></svg>Intelligence</h1>
1365
+ <div class="section-subtitle">Memory Constellation — every memory, project, agent &amp; tag as a living graph, sized by importance</div>
1366
+ </div>
1367
+ <div class="section-actions">
1368
+ <input id="const-search" class="act-search" placeholder="Search nodes…" oninput="highlightConstSearch()">
1369
+ <button class="btn" onclick="rebuildConstellation()" title="Rebuild from the latest brain state"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>Rebuild</button>
1370
+ <button class="btn" onclick="resetConstellationView()" title="Reset zoom &amp; pan"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>Reset</button>
1371
+ </div>
1372
+ </div>
1373
+
1374
+ <div class="filter-chips" id="const-chips">
1375
+ <button class="chip active" data-ntype="" onclick="setConstFilter(this,'')">All</button>
1376
+ <button class="chip" data-ntype="memory" onclick="setConstFilter(this,'memory')">Memories</button>
1377
+ <button class="chip" data-ntype="project" onclick="setConstFilter(this,'project')">Projects</button>
1378
+ <button class="chip" data-ntype="agent" onclick="setConstFilter(this,'agent')">Agents</button>
1379
+ <button class="chip" data-ntype="tag" onclick="setConstFilter(this,'tag')">Tags</button>
1380
+ </div>
1381
+
1382
+ <div class="card" style="padding:0;overflow:hidden">
1383
+ <div class="const-wrap" id="const-wrap">
1384
+ <canvas id="const-canvas"></canvas>
1385
+ <div class="const-legend">
1386
+ <div class="lg-row"><span class="lg-dot" style="background:#18ffff"></span>Memory</div>
1387
+ <div class="lg-row"><span class="lg-dot" style="background:#10e68c"></span>Project</div>
1388
+ <div class="lg-row"><span class="lg-dot" style="background:#ffb53f"></span>Agent</div>
1389
+ <div class="lg-row"><span class="lg-dot" style="background:#a855f7"></span>Tag</div>
1390
+ </div>
1391
+ <div class="const-meta" id="const-meta">—</div>
1392
+ <div class="const-hint">scroll to zoom · drag to pan · click a node</div>
1393
+ <div class="const-empty" id="const-empty">
1394
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><circle cx="5" cy="18" r="2"/><circle cx="19" cy="18" r="2"/></svg>
1395
+ <div class="ce-title">No memories yet</div>
1396
+ <div class="ce-msg">Attach Shadow Brain to your agents and let them work — memories will appear here as a living constellation.</div>
1397
+ </div>
1398
+ <div class="const-drawer" id="const-drawer">
1399
+ <button class="const-drawer-close" onclick="closeConstDrawer()" aria-label="Close">×</button>
1400
+ <div id="const-drawer-body"></div>
1401
+ </div>
1402
+ <div class="const-tip" id="const-tip"></div>
1403
+ </div>
1404
+ </div>
1405
+
1406
+ <!-- Analytics strip -->
1407
+ <div class="grid cols-4 mt-lg">
1408
+ <div class="card stat-card accent"><div class="label">Total Memories</div><div class="value accent" id="intel-total">—</div><div class="trend" id="intel-total-sub">in the brain</div></div>
1409
+ <div class="card stat-card accent"><div class="label">Source Coverage</div><div class="value" id="intel-coverage">—</div><div class="meter mt"><div class="fill" id="intel-coverage-meter" style="width:0%"></div></div></div>
1410
+ <div class="card stat-card accent"><div class="label">Fresh (24h)</div><div class="value" id="intel-recent">—</div><div class="trend up" id="intel-recent-sub">new memories</div></div>
1411
+ <div class="card stat-card accent"><div class="label">High Importance</div><div class="value" id="intel-highimp">—</div><div class="trend" id="intel-highimp-sub">score ≥ 0.75</div></div>
1412
+ </div>
1413
+ <div class="grid cols-2 mt-lg">
1414
+ <div class="card"><div class="card-header"><div class="card-title">Top Agents by Contribution</div></div><div class="chart-wrap"><canvas id="chart-intel-agents"></canvas></div></div>
1415
+ <div class="card"><div class="card-header"><div class="card-title">Memory Categories</div></div><div class="chart-wrap"><canvas id="chart-intel-cats"></canvas></div></div>
1416
+ </div>
1417
+ <div class="grid cols-2 mt-lg">
1418
+ <div class="card"><div class="card-header"><div class="card-title">Top Tags</div></div><div id="intel-tags" class="tag-cloud"></div></div>
1419
+ <div class="card"><div class="card-header"><div class="card-title">Recent Memories</div></div><div id="intel-recent-list" class="stack" style="max-height:260px;overflow-y:auto"></div></div>
1420
+ </div>
1421
+ </section>
1422
+
1032
1423
  <!-- ════ LIVE GRAPH ══════════════════════════════════════════════ -->
1033
- <!-- WW2BR BRAIN -->
1034
- <section class="section" id="tab-ww2br">
1035
- <div class="accent-bar"></div>
1036
- <div class="section-header">
1037
- <div>
1038
- <h1 class="section-title">WW2BR Brain View</h1>
1039
- <div class="section-subtitle">Live project memory, recall, and integration status for the current game-development workflow</div>
1040
- </div>
1041
- <div class="section-actions">
1042
- <button class="btn primary" onclick="refreshWW2BR()">Refresh</button>
1043
- </div>
1044
- </div>
1045
-
1046
- <div class="workflow-strip">
1047
- <div>
1048
- <div class="field" style="margin:0">
1049
- <label>Active project path</label>
1050
- <input class="workflow-path" id="ww2-project-path" value="D:\\New Game"/>
1051
- </div>
1052
- </div>
1053
- <button class="btn" onclick="refreshWW2BR()">Load</button>
1054
- </div>
1055
-
1056
- <div id="ww2-warnings" class="warning-stack"></div>
1057
-
1058
- <div class="grid cols-4">
1059
- <div class="card stat-card">
1060
- <div class="label">Global Memories</div>
1061
- <div class="value accent" id="ww2-stat-memories">0</div>
1062
- <div class="trend" id="ww2-stat-scope">project scoped</div>
1063
- </div>
1064
- <div class="card stat-card">
1065
- <div class="label">Brain Projects</div>
1066
- <div class="value" id="ww2-stat-projects">0</div>
1067
- <div class="trend">known roots</div>
1068
- </div>
1069
- <div class="card stat-card">
1070
- <div class="label">Agent Hooks</div>
1071
- <div class="value" id="ww2-stat-hooks">0</div>
1072
- <div class="trend">wired for this path</div>
1073
- </div>
1074
- <div class="card stat-card">
1075
- <div class="label">Hit Rate</div>
1076
- <div class="value" id="ww2-stat-hit">0%</div>
1077
- <div class="trend" id="ww2-stat-sync">last sync unknown</div>
1078
- </div>
1079
- </div>
1080
-
1081
- <div class="grid cols-3 mt-lg">
1082
- <div class="card">
1083
- <div class="card-header"><div class="card-title">Memory Quality</div></div>
1084
- <div id="ww2-quality" class="stack"></div>
1085
- </div>
1086
- <div class="card span-2">
1087
- <div class="card-header">
1088
- <div class="card-title">Context Continuity Pack</div>
1089
- <button class="btn sm" onclick="openWW2ResumePack()">Markdown</button>
1090
- </div>
1091
- <div id="ww2-resume-pack"></div>
1092
- </div>
1093
- </div>
1094
-
1095
- <div class="grid cols-3 mt-lg">
1096
- <div class="card span-2">
1097
- <div class="card-header">
1098
- <div class="card-title">Recent Brain Timeline</div>
1099
- <button class="btn sm" onclick="refreshWW2BR()">Refresh</button>
1100
- </div>
1101
- <div id="ww2-timeline" class="brain-feed"></div>
1102
- </div>
1103
- <div class="card">
1104
- <div class="card-header"><div class="card-title">Agent Integrations</div></div>
1105
- <div id="ww2-agents" class="ops-agent-grid"></div>
1106
- </div>
1107
- </div>
1108
-
1109
- <div class="grid cols-3 mt-lg">
1110
- <div class="card">
1111
- <div class="card-header"><div class="card-title">Memory Categories</div></div>
1112
- <div id="ww2-categories"></div>
1113
- </div>
1114
- <div class="card span-2">
1115
- <div class="card-header"><div class="card-title">Recall Query</div></div>
1116
- <div class="grid cols-3">
1117
- <div class="field span-2"><label>Query</label><input id="ww2-query" placeholder="Gate 2, operator proof, lobby blocker"/></div>
1118
- <div class="field"><label>Category</label><input id="ww2-category" placeholder="optional"/></div>
1119
- </div>
1120
- <div class="flex gap">
1121
- <button class="btn primary" onclick="runWW2Recall()">Recall</button>
1122
- <button class="btn" onclick="setWW2Query('Gate 2 visual proof')">Gate 2</button>
1123
- <button class="btn" onclick="setWW2Query('operator hands rifle hold')">Operator</button>
1124
- <button class="btn" onclick="setWW2Query('lobby verification blockers')">Lobby</button>
1125
- </div>
1126
- <div id="ww2-recall-results" class="brain-feed mt"></div>
1127
- </div>
1128
- </div>
1129
- </section>
1130
-
1131
- <section class="section" id="tab-graph">
1424
+ <!-- WW2BR BRAIN -->
1425
+ <section class="section" id="tab-ww2br">
1426
+ <div class="accent-bar"></div>
1427
+ <div class="section-header">
1428
+ <div>
1429
+ <h1 class="section-title">WW2BR Brain View</h1>
1430
+ <div class="section-subtitle">Live project memory, recall, and integration status for the current game-development workflow</div>
1431
+ </div>
1432
+ <div class="section-actions">
1433
+ <button class="btn primary" onclick="refreshWW2BR()">Refresh</button>
1434
+ </div>
1435
+ </div>
1436
+
1437
+ <div class="workflow-strip">
1438
+ <div>
1439
+ <div class="field" style="margin:0">
1440
+ <label>Active project path</label>
1441
+ <input class="workflow-path" id="ww2-project-path" value="D:\\New Game"/>
1442
+ </div>
1443
+ </div>
1444
+ <button class="btn" onclick="refreshWW2BR()">Load</button>
1445
+ </div>
1446
+
1447
+ <div id="ww2-warnings" class="warning-stack"></div>
1448
+
1449
+ <div class="grid cols-4">
1450
+ <div class="card stat-card">
1451
+ <div class="label">Global Memories</div>
1452
+ <div class="value accent" id="ww2-stat-memories">0</div>
1453
+ <div class="trend" id="ww2-stat-scope">project scoped</div>
1454
+ </div>
1455
+ <div class="card stat-card">
1456
+ <div class="label">Brain Projects</div>
1457
+ <div class="value" id="ww2-stat-projects">0</div>
1458
+ <div class="trend">known roots</div>
1459
+ </div>
1460
+ <div class="card stat-card">
1461
+ <div class="label">Agent Hooks</div>
1462
+ <div class="value" id="ww2-stat-hooks">0</div>
1463
+ <div class="trend">wired for this path</div>
1464
+ </div>
1465
+ <div class="card stat-card">
1466
+ <div class="label">Hit Rate</div>
1467
+ <div class="value" id="ww2-stat-hit">0%</div>
1468
+ <div class="trend" id="ww2-stat-sync">last sync unknown</div>
1469
+ </div>
1470
+ </div>
1471
+
1472
+ <div class="grid cols-3 mt-lg">
1473
+ <div class="card">
1474
+ <div class="card-header"><div class="card-title">Memory Quality</div></div>
1475
+ <div id="ww2-quality" class="stack"></div>
1476
+ </div>
1477
+ <div class="card span-2">
1478
+ <div class="card-header">
1479
+ <div class="card-title">Context Continuity Pack</div>
1480
+ <button class="btn sm" onclick="openWW2ResumePack()">Markdown</button>
1481
+ </div>
1482
+ <div id="ww2-resume-pack"></div>
1483
+ </div>
1484
+ </div>
1485
+
1486
+ <div class="grid cols-3 mt-lg">
1487
+ <div class="card span-2">
1488
+ <div class="card-header">
1489
+ <div class="card-title">Recent Brain Timeline</div>
1490
+ <button class="btn sm" onclick="refreshWW2BR()">Refresh</button>
1491
+ </div>
1492
+ <div id="ww2-timeline" class="brain-feed"></div>
1493
+ </div>
1494
+ <div class="card">
1495
+ <div class="card-header"><div class="card-title">Agent Integrations</div></div>
1496
+ <div id="ww2-agents" class="ops-agent-grid"></div>
1497
+ </div>
1498
+ </div>
1499
+
1500
+ <div class="grid cols-3 mt-lg">
1501
+ <div class="card">
1502
+ <div class="card-header"><div class="card-title">Memory Categories</div></div>
1503
+ <div id="ww2-categories"></div>
1504
+ </div>
1505
+ <div class="card span-2">
1506
+ <div class="card-header"><div class="card-title">Recall Query</div></div>
1507
+ <div class="grid cols-3">
1508
+ <div class="field span-2"><label>Query</label><input id="ww2-query" placeholder="Gate 2, operator proof, lobby blocker"/></div>
1509
+ <div class="field"><label>Category</label><input id="ww2-category" placeholder="optional"/></div>
1510
+ </div>
1511
+ <div class="flex gap">
1512
+ <button class="btn primary" onclick="runWW2Recall()">Recall</button>
1513
+ <button class="btn" onclick="setWW2Query('Gate 2 visual proof')">Gate 2</button>
1514
+ <button class="btn" onclick="setWW2Query('operator hands rifle hold')">Operator</button>
1515
+ <button class="btn" onclick="setWW2Query('lobby verification blockers')">Lobby</button>
1516
+ </div>
1517
+ <div id="ww2-recall-results" class="brain-feed mt"></div>
1518
+ </div>
1519
+ </div>
1520
+ </section>
1521
+
1522
+ <section class="section" id="tab-graph">
1132
1523
  <div class="accent-bar"></div>
1133
1524
  <div class="section-header">
1134
1525
  <div>
@@ -1205,7 +1596,7 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1205
1596
  <div class="section-header">
1206
1597
  <div>
1207
1598
  <h1 class="section-title">Memory Browser</h1>
1208
- <div class="section-subtitle">Full-text search plus live memory quality, provenance, freshness, and agent coverage</div>
1599
+ <div class="section-subtitle">Full-text search plus live memory quality, provenance, freshness, and agent coverage</div>
1209
1600
  </div>
1210
1601
  </div>
1211
1602
  <div class="card">
@@ -1222,137 +1613,155 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1222
1613
  <tbody id="mem-results"></tbody>
1223
1614
  </table>
1224
1615
  </div>
1225
- </section>
1226
-
1227
- <!-- ════ LIVE BRAIN ═══════════════════════════════════════════════ -->
1228
- <!-- MEMORY SEEDER -->
1229
- <section class="section" id="tab-seeder">
1230
- <div class="accent-bar"></div>
1231
- <div class="section-header">
1232
- <div>
1233
- <h1 class="section-title">Memory Seeder</h1>
1234
- <div class="section-subtitle">Research drafts, approval gates, and source-backed global brain writes</div>
1235
- </div>
1236
- <div class="section-actions">
1237
- <button class="btn" onclick="refreshSeedDrafts()">Refresh</button>
1238
- </div>
1239
- </div>
1240
-
1241
- <div class="grid cols-3">
1242
- <div class="card span-2">
1243
- <div class="card-header"><div class="card-title">Seed Research Draft</div></div>
1244
- <div class="grid cols-2">
1245
- <div class="field"><label>Project path</label><input id="seed-project-path" placeholder="C:\\path\\to\\project"/></div>
1246
- <div class="field"><label>Topic</label><input id="seed-topic" placeholder="memory routing, architecture, release, debugging"/></div>
1247
- </div>
1248
- <div class="field"><label>Objective</label><input id="seed-objective" placeholder="what the next agent must learn and reuse"/></div>
1249
- <div class="grid cols-3">
1250
- <div class="field"><label>Research agent</label><select id="seed-research-agent"><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>
1251
- <div class="field"><label>Approver</label><select id="seed-approver-agent"><option>user</option><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>
1252
- <div class="field"><label>Approval mode</label><select id="seed-approval-mode"><option>user</option><option>agent</option><option>dual</option></select></div>
1253
- </div>
1254
- <div class="grid cols-3">
1255
- <div class="field"><label>Category</label><input id="seed-category" value="project.research"/></div>
1256
- <div class="field"><label>Tags</label><input id="seed-tags" placeholder="enterprise,memory,release"/></div>
1257
- <div class="field"><label>Importance</label><input id="seed-importance" type="number" min="0.05" max="1" step="0.05" value="0.78"/></div>
1258
- </div>
1259
- <div class="field"><label>Sources</label><textarea id="seed-sources" rows="2" placeholder="README.md, CHANGELOG.md, docs/launch/RELEASE_NOTES_v6.0.3.md"></textarea></div>
1260
- <div class="field"><label>Research notes</label><textarea id="seed-content" rows="4" placeholder="facts, constraints, tradeoffs, exact commands, source-backed decisions"></textarea></div>
1261
- <div class="flex gap">
1262
- <button class="btn primary" onclick="createSeedDraft()">Create Draft</button>
1263
- <button class="btn" onclick="copyLivePathToSeeder()">Use Live Brain Path</button>
1264
- </div>
1265
- </div>
1266
- <div class="card">
1267
- <div class="card-header"><div class="card-title">Seeder Status</div></div>
1268
- <div id="seed-status" class="stack"></div>
1269
- </div>
1270
- </div>
1271
-
1272
- <div class="card mt-lg">
1273
- <div class="card-header">
1274
- <div class="card-title">Approval Queue</div>
1275
- <button class="btn sm" onclick="refreshSeedDrafts()">Refresh</button>
1276
- </div>
1277
- <div id="seed-drafts" class="brain-feed"></div>
1278
- </div>
1279
- </section>
1280
-
1281
- <section class="section" id="tab-livebrain">
1282
- <div class="accent-bar"></div>
1283
- <div class="section-header">
1284
- <div>
1285
- <h1 class="section-title">Live Brain</h1>
1286
- <div class="section-subtitle">Memory health, source coverage, recall eval, and agent briefing for the current workspace</div>
1287
- </div>
1288
- <div class="section-actions">
1289
- <button class="btn" onclick="openSeederForLiveBrain()">Seed Memory</button>
1290
- <button class="btn primary" onclick="refreshLiveBrain()">Refresh</button>
1291
- </div>
1292
- </div>
1293
- <div class="grid cols-4">
1294
- <div class="card stat-card"><div class="label">Total Memories</div><div class="value" id="mem-total">0</div><div class="trend" id="mem-sampled">sampled</div></div>
1295
- <div class="card stat-card"><div class="label">Source Coverage</div><div class="value" id="mem-source">0%</div><div class="trend">evidence-backed records</div></div>
1296
- <div class="card stat-card"><div class="label">Fresh 7 Days</div><div class="value" id="mem-fresh">0</div><div class="trend">recent learning</div></div>
1297
- <div class="card stat-card"><div class="label">Seed Queue</div><div class="value" id="mem-seed-open">0</div><div class="trend" id="mem-seed-sub">approval queue</div></div>
1298
- </div>
1299
- <div class="grid cols-3 mt">
1300
- <div class="card"><div class="card-header"><div class="card-title">Top Categories</div></div><div id="mem-categories"></div></div>
1301
- <div class="card"><div class="card-header"><div class="card-title">Agent Coverage</div></div><div id="mem-agents"></div></div>
1302
- <div class="card"><div class="card-header"><div class="card-title">Top Tags</div></div><div id="mem-tags"></div></div>
1303
- </div>
1304
- <div class="card mt">
1305
- <div class="field-row">
1306
- <div class="field"><label>Project path</label><input id="live-project-path" placeholder="C:\\path\\to\\project"/></div>
1307
- <div class="field"><label>Eval queries</label><input id="live-queries" placeholder="current blocker|do not repeat|verified command"/></div>
1308
- </div>
1309
- </div>
1310
- <div class="grid cols-4 mt">
1311
- <div class="card stat-card"><div class="label">Memories</div><div class="value" id="live-memories">0</div><div class="trend" id="live-scope">scope</div></div>
1312
- <div class="card stat-card"><div class="label">Quality</div><div class="value" id="live-quality">0</div><div class="trend">average score</div></div>
1313
- <div class="card stat-card"><div class="label">Sources</div><div class="value" id="live-sources">0%</div><div class="trend">coverage</div></div>
1314
- <div class="card stat-card"><div class="label">Recall</div><div class="value" id="live-recall">0</div><div class="trend">avg hits/query</div></div>
1315
- </div>
1316
- <div class="grid cols-2 mt-lg">
1317
- <div class="card">
1318
- <div class="card-header"><div class="card-title">Recommended Actions</div></div>
1319
- <div id="live-actions" class="stack"></div>
1320
- </div>
1321
- <div class="card">
1322
- <div class="card-header"><div class="card-title">Recall Eval</div></div>
1323
- <div id="live-eval" class="stack"></div>
1324
- </div>
1325
- <div class="card">
1326
- <div class="card-header"><div class="card-title">Top Memories</div></div>
1327
- <div id="live-top" class="stack"></div>
1328
- </div>
1329
- <div class="card">
1330
- <div class="card-header"><div class="card-title">Agent Briefing</div></div>
1331
- <pre class="mono" id="live-brief" style="max-height:360px;overflow:auto;background:var(--bg-elev-1);padding:12px;border-radius:6px;white-space:pre-wrap"></pre>
1332
- </div>
1333
- </div>
1334
- </section>
1335
-
1336
- <!-- ════ ACTIVITY LOG ════════════════════════════════════════════ -->
1337
- <section class="section" id="tab-activity">
1616
+ </section>
1617
+
1618
+ <!-- ════ LIVE BRAIN ═══════════════════════════════════════════════ -->
1619
+ <!-- MEMORY SEEDER -->
1620
+ <section class="section" id="tab-seeder">
1621
+ <div class="accent-bar"></div>
1622
+ <div class="section-header">
1623
+ <div>
1624
+ <h1 class="section-title">Memory Seeder</h1>
1625
+ <div class="section-subtitle">Research drafts, approval gates, and source-backed global brain writes</div>
1626
+ </div>
1627
+ <div class="section-actions">
1628
+ <button class="btn" onclick="refreshSeedDrafts()">Refresh</button>
1629
+ </div>
1630
+ </div>
1631
+
1632
+ <div class="grid cols-3">
1633
+ <div class="card span-2">
1634
+ <div class="card-header"><div class="card-title">Seed Research Draft</div></div>
1635
+ <div class="grid cols-2">
1636
+ <div class="field"><label>Project path</label><input id="seed-project-path" placeholder="C:\\path\\to\\project"/></div>
1637
+ <div class="field"><label>Topic</label><input id="seed-topic" placeholder="memory routing, architecture, release, debugging"/></div>
1638
+ </div>
1639
+ <div class="field"><label>Objective</label><input id="seed-objective" placeholder="what the next agent must learn and reuse"/></div>
1640
+ <div class="grid cols-3">
1641
+ <div class="field"><label>Research agent</label><select id="seed-research-agent"><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>
1642
+ <div class="field"><label>Approver</label><select id="seed-approver-agent"><option>user</option><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>
1643
+ <div class="field"><label>Approval mode</label><select id="seed-approval-mode"><option>user</option><option>agent</option><option>dual</option></select></div>
1644
+ </div>
1645
+ <div class="grid cols-3">
1646
+ <div class="field"><label>Category</label><input id="seed-category" value="project.research"/></div>
1647
+ <div class="field"><label>Tags</label><input id="seed-tags" placeholder="enterprise,memory,release"/></div>
1648
+ <div class="field"><label>Importance</label><input id="seed-importance" type="number" min="0.05" max="1" step="0.05" value="0.78"/></div>
1649
+ </div>
1650
+ <div class="field"><label>Sources</label><textarea id="seed-sources" rows="2" placeholder="README.md, CHANGELOG.md, docs/launch/RELEASE_NOTES_v6.0.3.md"></textarea></div>
1651
+ <div class="field"><label>Research notes</label><textarea id="seed-content" rows="4" placeholder="facts, constraints, tradeoffs, exact commands, source-backed decisions"></textarea></div>
1652
+ <div class="flex gap">
1653
+ <button class="btn primary" onclick="quickSeed()" title="Create, approve, and save to the brain in one step">⚡ Quick Save</button>
1654
+ <button class="btn" onclick="createSeedDraft()" title="Create a draft that waits for approval">Create Draft (review first)</button>
1655
+ <button class="btn" onclick="copyLivePathToSeeder()">Use Live Brain Path</button>
1656
+ </div>
1657
+ <div class="section-subtitle" style="margin-top:8px">Quick Save is the fast, one-click path. Use "Create Draft" when you want a review/approval step before it's saved.</div>
1658
+ </div>
1659
+ <div class="card">
1660
+ <div class="card-header"><div class="card-title">Seeder Status</div></div>
1661
+ <div id="seed-status" class="stack"></div>
1662
+ </div>
1663
+ </div>
1664
+
1665
+ <div class="card mt-lg">
1666
+ <div class="card-header">
1667
+ <div class="card-title">Approval Queue</div>
1668
+ <button class="btn sm" onclick="refreshSeedDrafts()">Refresh</button>
1669
+ </div>
1670
+ <div id="seed-drafts" class="brain-feed"></div>
1671
+ </div>
1672
+ </section>
1673
+
1674
+ <section class="section" id="tab-livebrain">
1675
+ <div class="accent-bar"></div>
1676
+ <div class="section-header">
1677
+ <div>
1678
+ <h1 class="section-title">Live Brain</h1>
1679
+ <div class="section-subtitle">Memory health, source coverage, recall eval, and agent briefing for the current workspace</div>
1680
+ </div>
1681
+ <div class="section-actions">
1682
+ <button class="btn" onclick="openSeederForLiveBrain()">Seed Memory</button>
1683
+ <button class="btn primary" onclick="refreshLiveBrain()">Refresh</button>
1684
+ </div>
1685
+ </div>
1686
+ <div class="grid cols-4">
1687
+ <div class="card stat-card"><div class="label">Total Memories</div><div class="value" id="mem-total">0</div><div class="trend" id="mem-sampled">sampled</div></div>
1688
+ <div class="card stat-card"><div class="label">Source Coverage</div><div class="value" id="mem-source">0%</div><div class="trend">evidence-backed records</div></div>
1689
+ <div class="card stat-card"><div class="label">Fresh 7 Days</div><div class="value" id="mem-fresh">0</div><div class="trend">recent learning</div></div>
1690
+ <div class="card stat-card"><div class="label">Seed Queue</div><div class="value" id="mem-seed-open">0</div><div class="trend" id="mem-seed-sub">approval queue</div></div>
1691
+ </div>
1692
+ <div class="grid cols-3 mt">
1693
+ <div class="card"><div class="card-header"><div class="card-title">Top Categories</div></div><div id="mem-categories"></div></div>
1694
+ <div class="card"><div class="card-header"><div class="card-title">Agent Coverage</div></div><div id="mem-agents"></div></div>
1695
+ <div class="card"><div class="card-header"><div class="card-title">Top Tags</div></div><div id="mem-tags"></div></div>
1696
+ </div>
1697
+ <div class="card mt">
1698
+ <div class="field-row">
1699
+ <div class="field"><label>Project path</label><input id="live-project-path" placeholder="C:\\path\\to\\project"/></div>
1700
+ <div class="field"><label>Eval queries</label><input id="live-queries" placeholder="current blocker|do not repeat|verified command"/></div>
1701
+ </div>
1702
+ </div>
1703
+ <div class="grid cols-4 mt">
1704
+ <div class="card stat-card"><div class="label">Memories</div><div class="value" id="live-memories">0</div><div class="trend" id="live-scope">scope</div></div>
1705
+ <div class="card stat-card"><div class="label">Quality</div><div class="value" id="live-quality">0</div><div class="trend">average score</div></div>
1706
+ <div class="card stat-card"><div class="label">Sources</div><div class="value" id="live-sources">0%</div><div class="trend">coverage</div></div>
1707
+ <div class="card stat-card"><div class="label">Recall</div><div class="value" id="live-recall">0</div><div class="trend">avg hits/query</div></div>
1708
+ </div>
1709
+ <div class="grid cols-2 mt-lg">
1710
+ <div class="card">
1711
+ <div class="card-header"><div class="card-title">Recommended Actions</div></div>
1712
+ <div id="live-actions" class="stack"></div>
1713
+ </div>
1714
+ <div class="card">
1715
+ <div class="card-header"><div class="card-title">Recall Eval</div></div>
1716
+ <div id="live-eval" class="stack"></div>
1717
+ </div>
1718
+ <div class="card">
1719
+ <div class="card-header"><div class="card-title">Top Memories</div></div>
1720
+ <div id="live-top" class="stack"></div>
1721
+ </div>
1722
+ <div class="card">
1723
+ <div class="card-header"><div class="card-title">Agent Briefing</div></div>
1724
+ <pre class="mono" id="live-brief" style="max-height:360px;overflow:auto;background:var(--bg-elev-1);padding:12px;border-radius:6px;white-space:pre-wrap"></pre>
1725
+ </div>
1726
+ </div>
1727
+ </section>
1728
+
1729
+ <!-- ════ ACTIVITY LOG ════════════════════════════════════════════ -->
1730
+ <section class="section" id="tab-activity">
1338
1731
  <div class="accent-bar"></div>
1339
1732
  <div class="section-header">
1340
1733
  <div>
1341
- <h1 class="section-title">Activity Log</h1>
1342
- <div class="section-subtitle">Unified stream: memory writes, SABB spawns, collisions, dreams, quarantine, firewall blocks</div>
1734
+ <h1 class="section-title">Activity Log <span class="live-badge" id="act-live-badge" title="Streaming live over WebSocket">LIVE</span></h1>
1735
+ <div class="section-subtitle">Realtime stream: memory writes, SABB spawns, collisions, dreams, seeds, consolidations, firewall blocks</div>
1343
1736
  </div>
1344
1737
  <div class="section-actions">
1345
- <select id="act-filter" style="background:var(--bg-elev-2);border:1px solid var(--border);border-radius:8px;padding:7px 10px;color:var(--text);font-size:12px">
1346
- <option value="">all types</option>
1347
- <option value="memory-write">memory-write</option>
1348
- <option value="subagent-spawn">subagent-spawn</option>
1349
- <option value="collision">collision</option>
1350
- <option value="dream">dream</option>
1351
- <option value="quarantine">quarantine</option>
1352
- </select>
1738
+ <input id="act-search" class="act-search" placeholder="Search activity…" oninput="renderActivityTable()">
1739
+ <button class="btn" id="act-pause-btn" onclick="toggleActivityPause()" title="Pause/resume the live stream">Pause</button>
1740
+ <button class="btn danger" onclick="clearActivity()" title="Clear the activity view">Clear</button>
1353
1741
  <button class="btn" onclick="refreshActivity()">Refresh</button>
1354
1742
  </div>
1355
1743
  </div>
1744
+ <div class="filter-chips" id="act-chips">
1745
+ <button class="chip active" data-type="" onclick="setActivityFilter(this,'')">All</button>
1746
+ <button class="chip" data-type="memory-write" onclick="setActivityFilter(this,'memory-write')">Memory</button>
1747
+ <button class="chip" data-type="dream" onclick="setActivityFilter(this,'dream')">Dreams</button>
1748
+ <button class="chip" data-type="seed-draft" onclick="setActivityFilter(this,'seed-draft')">Seeds</button>
1749
+ <button class="chip" data-type="collision" onclick="setActivityFilter(this,'collision')">Collisions</button>
1750
+ <button class="chip" data-type="subagent-spawn" onclick="setActivityFilter(this,'subagent-spawn')">Sub-Agents</button>
1751
+ <button class="chip" data-type="consolidation" onclick="setActivityFilter(this,'consolidation')">Consolidation</button>
1752
+ </div>
1753
+ <div class="grid cols-2 mb">
1754
+ <div class="card">
1755
+ <div class="card-header"><div class="card-title">Events / min</div><span class="pill cyan" id="act-rate-pill">0 / min</span></div>
1756
+ <div class="chart-wrap" style="height:120px"><canvas id="act-spark"></canvas></div>
1757
+ </div>
1758
+ <div class="card">
1759
+ <div class="card-header"><div class="card-title">Stream Stats</div></div>
1760
+ <div class="row"><span class="k">Events buffered</span><span class="v" id="act-stat-total">0</span></div>
1761
+ <div class="row"><span class="k">Live status</span><span class="v good" id="act-stat-live">streaming</span></div>
1762
+ <div class="row"><span class="k">Last event</span><span class="v" id="act-stat-last">—</span></div>
1763
+ </div>
1764
+ </div>
1356
1765
  <div class="card" style="padding:0">
1357
1766
  <table class="table"><thead><tr><th>Time</th><th>Source</th><th>Type</th><th>Detail</th></tr></thead><tbody id="activity-table"></tbody></table>
1358
1767
  </div>
@@ -1408,7 +1817,13 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1408
1817
  <div class="field"><label>Max depth</label><input id="causal-depth" value="8"/></div>
1409
1818
  <div class="field"><label>&nbsp;</label><div style="display:flex;gap:6px"><button class="btn primary" onclick="traceCausal()" style="flex:1">Ancestors</button><button class="btn" onclick="influenceCausal()" style="flex:1">Descendants</button></div></div>
1410
1819
  </div>
1411
- <pre class="mono" id="causal-output" style="margin-top:10px;max-height:280px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px"></pre>
1820
+ </div>
1821
+ </div>
1822
+ <div class="card mt-lg" style="padding:0">
1823
+ <div style="padding:14px 18px 6px"><div class="card-title">Causal Chain Graph</div></div>
1824
+ <div class="dag-wrap" id="causal-dag-wrap" style="margin:0 14px 14px">
1825
+ <canvas id="causal-dag"></canvas>
1826
+ <div class="dag-empty" id="causal-dag-empty">Enter a memory ID and trace ancestors or descendants to render its causal chain.</div>
1412
1827
  </div>
1413
1828
  </div>
1414
1829
  <div class="card mt-lg">
@@ -1469,6 +1884,10 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1469
1884
  <pre class="mono" id="rep-badge-output" style="margin-top:10px;background:var(--bg-elev-1);padding:10px;border-radius:6px"></pre>
1470
1885
  </div>
1471
1886
  </div>
1887
+ <div class="card mt-lg">
1888
+ <div class="card-header"><div class="card-title">Agent Trust</div></div>
1889
+ <div id="rep-trust-grid" class="trust-grid"></div>
1890
+ </div>
1472
1891
  <div class="card mt-lg" style="padding:0">
1473
1892
  <div style="padding:14px 18px 6px"><div class="card-title">Agent Leaderboard</div></div>
1474
1893
  <table class="table"><thead><tr><th>Agent</th><th>Accuracy</th><th>Decisions</th><th>Correct/Incorrect/Partial</th></tr></thead><tbody id="rep-leaderboard"></tbody></table>
@@ -1504,9 +1923,9 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1504
1923
  <div class="section-header"><div><h1 class="section-title">Attention Heatmap</h1><div class="section-subtitle">See which memories shaped a decision — weighted attribution</div></div></div>
1505
1924
  <div class="card">
1506
1925
  <div class="field"><label>Decision text</label><input id="att-decision" placeholder="e.g. use parameterized SQL queries"/></div>
1507
- <div class="field"><label>Candidate memory IDs (comma-separated)</label><input id="att-memories" placeholder="id1, id2, id3"/></div>
1926
+ <div class="field"><label>Candidate memory IDs (comma-separated, optional — blank uses recent)</label><input id="att-memories" placeholder="id1, id2, id3"/></div>
1508
1927
  <button class="btn primary" onclick="runAttention()">Compute Attention</button>
1509
- <pre class="mono" id="att-output" style="margin-top:12px;max-height:320px;overflow:auto;background:var(--bg-elev-1);padding:12px;border-radius:6px"></pre>
1928
+ <div id="att-output" style="margin-top:14px"></div>
1510
1929
  </div>
1511
1930
  </section>
1512
1931
 
@@ -1514,22 +1933,22 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1514
1933
  <section class="section" id="tab-tokens">
1515
1934
  <div class="accent-bar"></div>
1516
1935
  <div class="section-header"><div><h1 class="section-title">Token Economy Engine</h1><div class="section-subtitle">Cross-agent spend tracking · savings suggestions · routing advice</div></div></div>
1517
- <div class="grid cols-4">
1518
- <div class="card stat-card"><div class="label">Current Agent</div><div class="value" id="tok-agent">—</div><div class="trend" id="tok-runtime">runtime context</div></div>
1519
- <div class="card stat-card"><div class="label">Total Spend</div><div class="value" id="tok-total">$—</div><div class="trend">all historical records</div></div>
1520
- <div class="card stat-card"><div class="label">Last 7 Days</div><div class="value" id="tok-recent">$—</div><div class="trend">recent records only</div></div>
1521
- <div class="card stat-card"><div class="label">Total Calls</div><div class="value" id="tok-calls">—</div><div class="trend" id="tok-calls-sub"></div></div>
1522
- </div>
1523
- <div class="grid cols-2 mt">
1524
- <div class="card stat-card"><div class="label">Monthly Projection</div><div class="value" id="tok-proj">$—</div><div class="trend">from last 7 days</div></div>
1525
- <div class="card stat-card"><div class="label">Savings Available</div><div class="value" style="color:var(--success)" id="tok-save">$—</div><div class="trend up">via smart routing</div></div>
1526
- </div>
1527
- <div class="warning-banner mt" id="tok-runtime-note"><strong>Accuracy</strong><span>Token records are historical. Current agent and model spend are shown separately.</span></div>
1528
- <div class="grid cols-2 mt-lg">
1529
- <div class="card"><div class="card-header"><div class="card-title">Recent Spend by Agent</div></div><div class="chart-wrap"><canvas id="chart-tokens-agent"></canvas></div><div id="tok-agent-table" class="mt"></div></div>
1530
- <div class="card"><div class="card-header"><div class="card-title">Historical Spend by Model</div></div><div class="chart-wrap"><canvas id="chart-tokens-model"></canvas></div><div id="tok-model-table" class="mt"></div></div>
1531
- <div class="card span-2"><div class="card-header"><div class="card-title">Savings Suggestions</div></div><div id="tok-suggestions"></div></div>
1532
- </div>
1936
+ <div class="grid cols-4">
1937
+ <div class="card stat-card"><div class="label">Current Agent</div><div class="value" id="tok-agent">—</div><div class="trend" id="tok-runtime">runtime context</div></div>
1938
+ <div class="card stat-card"><div class="label">Total Spend</div><div class="value" id="tok-total">$—</div><div class="trend">all historical records</div></div>
1939
+ <div class="card stat-card"><div class="label">Last 7 Days</div><div class="value" id="tok-recent">$—</div><div class="trend">recent records only</div></div>
1940
+ <div class="card stat-card"><div class="label">Total Calls</div><div class="value" id="tok-calls">—</div><div class="trend" id="tok-calls-sub"></div></div>
1941
+ </div>
1942
+ <div class="grid cols-2 mt">
1943
+ <div class="card stat-card"><div class="label">Monthly Projection</div><div class="value" id="tok-proj">$—</div><div class="trend">from last 7 days</div></div>
1944
+ <div class="card stat-card"><div class="label">Savings Available</div><div class="value" style="color:var(--success)" id="tok-save">$—</div><div class="trend up">via smart routing</div></div>
1945
+ </div>
1946
+ <div class="warning-banner mt" id="tok-runtime-note"><strong>Accuracy</strong><span>Token records are historical. Current agent and model spend are shown separately.</span></div>
1947
+ <div class="grid cols-2 mt-lg">
1948
+ <div class="card"><div class="card-header"><div class="card-title">Recent Spend by Agent</div></div><div class="chart-wrap"><canvas id="chart-tokens-agent"></canvas></div><div id="tok-agent-table" class="mt"></div></div>
1949
+ <div class="card"><div class="card-header"><div class="card-title">Historical Spend by Model</div></div><div class="chart-wrap"><canvas id="chart-tokens-model"></canvas></div><div id="tok-model-table" class="mt"></div></div>
1950
+ <div class="card span-2"><div class="card-header"><div class="card-title">Savings Suggestions</div></div><div id="tok-suggestions"></div></div>
1951
+ </div>
1533
1952
  </section>
1534
1953
 
1535
1954
  <!-- ════ FORGET ══════════════════════════════════════════════════ -->
@@ -1564,7 +1983,11 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1564
1983
  <!-- ════ CALIBRATION ═════════════════════════════════════════════ -->
1565
1984
  <section class="section" id="tab-calibration">
1566
1985
  <div class="accent-bar"></div>
1567
- <div class="section-header"><div><h1 class="section-title">Confidence Calibration Monitor</h1><div class="section-subtitle">Brier scores per agent per category</div></div></div>
1986
+ <div class="section-header"><div><h1 class="section-title">Confidence Calibration Monitor</h1><div class="section-subtitle">Brier scores &amp; trust weight per agent per category</div></div></div>
1987
+ <div class="card mb-lg">
1988
+ <div class="card-header"><div class="card-title">Brier Score vs Trust Weight by Agent</div></div>
1989
+ <div class="chart-wrap"><canvas id="chart-calibration"></canvas></div>
1990
+ </div>
1568
1991
  <div class="card" style="padding:0">
1569
1992
  <table class="table"><thead><tr><th>Agent</th><th>Category</th><th>Brier</th><th>Cal. Error</th><th>Overconf.</th><th>Trust Weight</th><th>Samples</th></tr></thead><tbody id="calibration-table"></tbody></table>
1570
1993
  </div>
@@ -1574,7 +1997,8 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1574
1997
  <section class="section" id="tab-branch">
1575
1998
  <div class="accent-bar"></div>
1576
1999
  <div class="section-header"><div><h1 class="section-title">Branch Brains</h1><div class="section-subtitle">Git-branch-aware memory context</div></div></div>
1577
- <div class="card"><div id="branch-state"></div></div>
2000
+ <div class="grid cols-4" id="branch-cards"></div>
2001
+ <div class="card mt-lg"><div class="card-header"><div class="card-title">Branch-Specific Categories</div></div><div id="branch-cats" class="tag-cloud"></div></div>
1578
2002
  </section>
1579
2003
 
1580
2004
  <!-- ════ PRIVACY ═════════════════════════════════════════════════ -->
@@ -1785,28 +2209,38 @@ export const V6_DASHBOARD_HTML = `<!DOCTYPE html>
1785
2209
  ══════════════════════════════════════════════════════════════════════ -->
1786
2210
  <script>
1787
2211
  const FEATURES = [
1788
- ['sabb','Sub-Agent Bridge','Syncs memory to Claude Task, Cursor Composer, CrewAI, LangGraph'],
1789
- ['causal','Causal Chains','Track cause→effect for every decision'],
1790
- ['collision','Collision Detective','Real-time overlap detection'],
1791
- ['dream','Dream Engine','Idle-time reflection via local LLM'],
1792
- ['reputation','Reputation Ledger','Ed25519-signed agent receipts'],
1793
- ['debate','Swarm Debate','Multi-agent debate w/ arbiter'],
1794
- ['premortem','Pre-Mortem','Surface past failures before tasks'],
1795
- ['branch','Branch Brains','Git-branch-aware memory'],
1796
- ['attention','Attention Heatmap','Weighted memory attribution'],
1797
- ['tokens','Token Economy','Spend tracking + savings'],
1798
- ['forget','Forgetting Curve','Ebbinghaus memory decay'],
1799
- ['formal','Formal Bridge','NL → ESLint/Semgrep'],
1800
- ['calibration','Calibration Monitor','Brier score trust weighting'],
1801
- ['airgap','Air-Gap Mode','Zero outbound network'],
1802
- ['encrypt','E2E Encryption','ChaCha20-Poly1305 at rest'],
1803
- ['quarantine','Hallucination Quarantine','Isolate suspect claims'],
1804
- ['voice','Voice Mode','Transcript + intent'],
1805
- ['garden','Brain Garden','Memory visualizer'],
1806
- ['pr','PR Auto-Review','GitHub PR comments'],
1807
- ['team','Team Brain Sync','WebRTC P2P'],
1808
- ['exchange','Brain Exchange','Share brain slices'],
1809
- ['local-llm','Local-First LLM','Ollama-first default'],
2212
+ ['intelligence','Brain Map','Visual map of every memory, project and tool'],
2213
+ ['memory','Memories','Browse and search saved knowledge'],
2214
+ ['chat','Ask the Brain','Ask questions in plain language'],
2215
+ ['activity','Activity','Live feed of what the brain is doing'],
2216
+ ['agents','Connected Tools','Which AI tools are linked to the brain'],
2217
+ ['livebrain','Brain Health','Memory quality with tips to improve'],
2218
+ ['ww2br','Project Memory','Memory and status for one project'],
2219
+ ['graph','Live Network','Live animation of tools talking to the brain'],
2220
+ ['seeder','Add Knowledge','Research a topic and save it as verified knowledge'],
2221
+ ['garden','Memory Garden','Watch your memories grow over time'],
2222
+ ['sabb','Sub-Agents','Share the right context with helper agents'],
2223
+ ['causal','Decision Trails','Trace why a decision was made'],
2224
+ ['dream','Reflections','Idle-time review that finds patterns and lessons'],
2225
+ ['debate','Debates','Multiple AI viewpoints argue a tough decision'],
2226
+ ['premortem','Risk Check','See what went wrong last time before a big task'],
2227
+ ['branch','Branch Memory','Memory that follows your git branch'],
2228
+ ['forget','Memory Cleanup','Old memories fade, important ones stay'],
2229
+ ['collision','Conflicts','Catch two tools editing the same code'],
2230
+ ['reputation','Trust Scores','How reliable each AI tool has been'],
2231
+ ['calibration','Accuracy','How well confidence matches being right'],
2232
+ ['attention','Focus Map','Which memories shaped a decision'],
2233
+ ['formal','Code Rules','Turn plain rules into linter checks'],
2234
+ ['privacy','Privacy & Safety','Stay offline and block risky actions'],
2235
+ ['tokens','Cost & Usage','Track AI spend and find savings'],
2236
+ ['pr','Code Review','Generate a pull-request review'],
2237
+ ['voice','Voice','Talk to the brain hands-free'],
2238
+ ['team','Team Sync','Share memory with teammates'],
2239
+ ['exchange','Share Brain','Export or import memory packs'],
2240
+ ['features','Features','Turn individual features on or off'],
2241
+ ['models','AI Models','Choose which AI models the brain uses'],
2242
+ ['config','Settings','Set up and adjust Shadow Brain'],
2243
+ ['overview','Home','Your starting point and summary'],
1810
2244
  ];
1811
2245
 
1812
2246
  let ws = null;
@@ -1820,9 +2254,21 @@ function switchTab(id, title) {
1820
2254
  document.querySelectorAll('.section').forEach(el => el.classList.toggle('active', el.id === 'tab-' + id));
1821
2255
  if (window.innerWidth <= 768) document.getElementById('sidebar').classList.remove('open');
1822
2256
  onTabOpen(id);
2257
+ animateSectionIn(id);
2258
+ }
2259
+ // Subtle staggered card entrance on real tab switches. Guarded so it no-ops
2260
+ // if GSAP's CDN failed to load. NOT called from the 5s refresh loop, so cards
2261
+ // don't re-fade on every poll.
2262
+ function animateSectionIn(id) {
2263
+ if (!window.gsap) return;
2264
+ const sec = document.getElementById('tab-' + id);
2265
+ if (!sec) return;
2266
+ const cards = sec.querySelectorAll('.card');
2267
+ if (!cards.length) return;
2268
+ try { window.gsap.from(cards, { opacity: 0, y: 12, duration: 0.34, stagger: 0.035, ease: 'power2.out', clearProps: 'opacity,transform' }); } catch (e) {}
1823
2269
  }
1824
- function onTabOpen(tab) {
1825
- const fn = { overview: refreshOverview, ww2br: refreshWW2BR, graph: startGraph, agents: refreshAgents, memory: () => { refreshMemoryIntelligence(); searchMemory(); }, seeder: refreshSeedDrafts, livebrain: refreshLiveBrain, chat: refreshChat, activity: refreshActivity,
2270
+ function onTabOpen(tab) {
2271
+ const fn = { overview: refreshOverview, intelligence: refreshIntelligence, ww2br: refreshWW2BR, graph: startGraph, agents: refreshAgents, memory: () => { refreshMemoryIntelligence(); searchMemory(); }, seeder: refreshSeedDrafts, livebrain: refreshLiveBrain, chat: refreshChat, activity: refreshActivity,
1826
2272
  sabb: refreshSABB, causal: refreshCausal, collision: refreshCollision, dream: refreshDream, reputation: refreshReputation,
1827
2273
  tokens: refreshTokens, forget: refreshForget, formal: null, calibration: refreshCalibration, branch: refreshBranch,
1828
2274
  privacy: refreshPrivacy, garden: refreshGarden, team: refreshTeam, exchange: refreshExchange, features: refreshFeatures,
@@ -1849,6 +2295,24 @@ function toggleTheme() {
1849
2295
  } catch { document.documentElement.dataset.theme = 'light'; }
1850
2296
  })();
1851
2297
 
2298
+ // ──── Simple / Advanced navigation mode ────────────────────────────────
2299
+ function setMode(mode) {
2300
+ const m = mode === 'advanced' ? 'advanced' : 'simple';
2301
+ document.documentElement.dataset.navmode = m;
2302
+ try { localStorage.setItem('hive.navmode', m); } catch {}
2303
+ document.querySelectorAll('.mode-btn').forEach(b => b.classList.toggle('active', b.dataset.mode === m));
2304
+ // If we just hid the tab the user is on, gently return them to Home.
2305
+ if (m === 'simple') {
2306
+ const active = document.querySelector('.nav-item.active');
2307
+ if (active && active.closest('[data-adv]')) switchTab('overview', 'Home');
2308
+ }
2309
+ }
2310
+ (function initMode(){
2311
+ let m = 'simple';
2312
+ try { m = localStorage.getItem('hive.navmode') || 'simple'; } catch {}
2313
+ setMode(m);
2314
+ })();
2315
+
1852
2316
  // ──── API helpers ──────────────────────────────────────────────────────
1853
2317
  async function api(path, body) {
1854
2318
  try {
@@ -1857,22 +2321,41 @@ async function api(path, body) {
1857
2321
  if (!res.ok) { toast('API error', res.status + ' ' + path, 'error'); return null; }
1858
2322
  const ct = res.headers.get('content-type') || '';
1859
2323
  return ct.includes('application/json') ? await res.json() : await res.text();
1860
- } catch (err) { toast('Network error', err.message, 'error'); return null; }
1861
- }
1862
- function htmlEscape(value) {
1863
- return String(value ?? '')
1864
- .replace(/&/g, '&amp;')
1865
- .replace(/</g, '&lt;')
1866
- .replace(/>/g, '&gt;')
1867
- .replace(/"/g, '&quot;')
1868
- .replace(/'/g, '&#39;');
1869
- }
1870
- function fmtDate(value) {
1871
- const d = new Date(value);
1872
- return Number.isNaN(d.getTime()) ? 'unknown' : d.toLocaleString();
1873
- }
1874
-
1875
- // ──── Toasts ───────────────────────────────────────────────────────────
2324
+ } catch (err) { toast('Network error', err.message, 'error'); return null; }
2325
+ }
2326
+ function htmlEscape(value) {
2327
+ return String(value ?? '')
2328
+ .replace(/&/g, '&amp;')
2329
+ .replace(/</g, '&lt;')
2330
+ .replace(/>/g, '&gt;')
2331
+ .replace(/"/g, '&quot;')
2332
+ .replace(/'/g, '&#39;');
2333
+ }
2334
+ function fmtDate(value) {
2335
+ const d = new Date(value);
2336
+ return Number.isNaN(d.getTime()) ? 'unknown' : d.toLocaleString();
2337
+ }
2338
+ // Reusable animated empty-state. cta = {label, onclick} or null.
2339
+ // title/msg are inserted as-is (allow pre-escaped entities) — author-controlled strings only.
2340
+ function emptyState(icon, title, msg, cta) {
2341
+ const icons = {
2342
+ shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>',
2343
+ check: '<polyline points="20 6 9 17 4 12"/>',
2344
+ target: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
2345
+ 'git-branch': '<line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>',
2346
+ brain: '<circle cx="12" cy="12" r="3"/><circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><circle cx="5" cy="18" r="2"/><circle cx="19" cy="18" r="2"/>',
2347
+ inbox: '<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>',
2348
+ };
2349
+ const path = icons[icon] || icons.inbox;
2350
+ let btn = '';
2351
+ if (cta && cta.label) {
2352
+ const handler = cta.onclick ? ' onclick="' + cta.onclick + '"' : '';
2353
+ btn = '<button class="btn primary es-cta"' + handler + '>' + htmlEscape(cta.label) + '</button>';
2354
+ }
2355
+ return '<div class="empty-state"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">' + path + '</svg><div class="es-title">' + title + '</div><div class="es-msg">' + msg + '</div>' + btn + '</div>';
2356
+ }
2357
+
2358
+ // ──── Toasts ───────────────────────────────────────────────────────────
1876
2359
  function toast(title, body, variant='success') {
1877
2360
  const c = document.getElementById('toast-container');
1878
2361
  const icons = { success:'<path d="M20 6 9 17l-5-5"/>', warn:'<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>', error:'<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>', info:'<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>' };
@@ -1932,7 +2415,7 @@ async function refresh() {
1932
2415
  animateNum('stat-agents', hive.totalAgentsConnected ?? 0);
1933
2416
  animateNum('stat-memories', hive.totalMemoriesStored ?? 0);
1934
2417
  animateNum('stat-dreams', hive.modules.dream?.totalDreams ?? 0);
1935
- animateMoney('stat-savings', hive.modules.tokenEconomy?.savingsOpportunitiesUsd ?? 0);
2418
+ animateNum('stat-tools', hive.totalAgentsConnected ?? 0);
1936
2419
  document.getElementById('badge-sabb').textContent = hive.modules.sabb?.totalSpawns ?? 0;
1937
2420
  document.getElementById('badge-causal').textContent = hive.modules.causal?.totalLinks ?? 0;
1938
2421
  document.getElementById('badge-collision').textContent = hive.modules.collision?.collisionsDetected ?? 0;
@@ -1949,8 +2432,10 @@ async function refreshOverview() {
1949
2432
  animateNum('ov-memories', hive.totalMemoriesStored ?? 0);
1950
2433
  animateNum('ov-subagents', hive.modules.sabb?.totalSpawns ?? 0);
1951
2434
  document.getElementById('ov-subagents-sub').textContent = \`grad: \${hive.modules.sabb?.graduated ?? 0} · quar: \${hive.modules.sabb?.quarantined ?? 0}\`;
1952
- animateMoney('ov-savings', hive.modules.tokenEconomy?.savingsOpportunitiesUsd ?? 0);
1953
- document.getElementById('ov-savings-sub').textContent = 'projection: $' + (hive.modules.tokenEconomy?.monthlyProjectionUsd?.toFixed?.(2) ?? '0.00');
2435
+ const rt = hive.runtime || {};
2436
+ document.getElementById('ov-current-agent').textContent = rt.currentAgentDisplay || 'Unknown';
2437
+ document.getElementById('ov-current-agent-sub').textContent = rt.provider ? (rt.provider + (rt.model ? ' / ' + rt.model : '')) : 'using the brain right now';
2438
+ loadConstHeroStats();
1954
2439
 
1955
2440
  const moduleRows = [
1956
2441
  ['SABB spawns', hive.modules.sabb?.totalSpawns ?? 0, 'good'],
@@ -1964,19 +2449,19 @@ async function refreshOverview() {
1964
2449
  \`<div class="row"><span class="k">\${k}</span><span class="v \${cls || ''}">\${v}</span></div>\`
1965
2450
  ).join('');
1966
2451
 
1967
- const health = [
1968
- ['Local-first', hive.localFirst ? 'ON' : 'OFF', hive.localFirst ? 'good' : 'warn'],
1969
- ['Current agent', hive.runtime?.currentAgentDisplay || 'Unknown', hive.runtime?.currentAgentTool === 'codex' ? 'good' : ''],
1970
- ['Air-gap', hive.modules.airGap?.enabled ? 'ENABLED' : 'disabled', hive.modules.airGap?.enabled ? 'good' : ''],
1971
- ['Version', hive.version, ''],
1972
- ['Monthly cost', '$' + (hive.modules.tokenEconomy?.monthlyProjectionUsd?.toFixed?.(2) ?? '0.00'), 'warn'],
2452
+ const health = [
2453
+ ['Local-first', hive.localFirst ? 'ON' : 'OFF', hive.localFirst ? 'good' : 'warn'],
2454
+ ['Current agent', hive.runtime?.currentAgentDisplay || 'Unknown', hive.runtime?.currentAgentTool === 'codex' ? 'good' : ''],
2455
+ ['Air-gap', hive.modules.airGap?.enabled ? 'ENABLED' : 'disabled', hive.modules.airGap?.enabled ? 'good' : ''],
2456
+ ['Version', hive.version, ''],
2457
+ ['Monthly cost', '$' + (hive.modules.tokenEconomy?.monthlyProjectionUsd?.toFixed?.(2) ?? '0.00'), 'warn'],
1973
2458
  ['Cost saved/mo', '$' + (hive.modules.tokenEconomy?.savingsOpportunitiesUsd?.toFixed?.(2) ?? '0.00'), 'good'],
1974
2459
  ];
1975
2460
  document.getElementById('ov-health').innerHTML = health.map(([k, v, cls]) => \`<div class="row"><span class="k">\${k}</span><span class="v \${cls}">\${v}</span></div>\`).join('');
1976
2461
 
1977
2462
  // Tokens chart
1978
- const tok = hive.modules.tokenEconomy;
1979
- if (tok) drawTokensChart(tok.recentByAgent || tok.byAgent || {});
2463
+ const tok = hive.modules.tokenEconomy;
2464
+ if (tok) drawTokensChart(tok.recentByAgent || tok.byAgent || {});
1980
2465
 
1981
2466
  // Timeline
1982
2467
  const timeline = await api('/api/v6/activity-log?limit=15') ?? [];
@@ -1984,191 +2469,191 @@ async function refreshOverview() {
1984
2469
  ? timeline.slice(0, 10).map(e => \`<div style="padding:5px 0;border-bottom:1px solid var(--border);display:flex;gap:8px"><span style="color:var(--text-dim);flex-shrink:0">\${new Date(e.timestamp).toLocaleTimeString()}</span><span class="pill \${typeToPill(e.type)}">\${e.type}</span><span style="color:var(--text-2);font-size:11px">\${(e.detail||'').slice(0,90)}</span></div>\`).join('')
1985
2470
  : '<div class="empty">No activity yet</div>';
1986
2471
  }
1987
- function typeToPill(type) {
1988
- if (type === 'memory-write') return 'green';
1989
- if (type === 'subagent-spawn') return 'cyan';
1990
- if (type === 'collision') return 'red';
1991
- if (type === 'quarantine') return 'yellow';
1992
- if (type === 'seed-draft') return 'yellow';
1993
- if (type === 'memory-seeded') return 'green';
1994
- if (['dream','revisit','counterfactual','consolidation','pattern-discovery','contradiction'].includes(type)) return 'purple';
1995
- return '';
1996
- }
1997
-
1998
- function getWW2ProjectPath() {
1999
- return document.getElementById('ww2-project-path')?.value?.trim() || 'D:\\\\New Game';
2000
- }
2001
- async function refreshWW2BR() {
2002
- const projectDir = getWW2ProjectPath();
2003
- const data = await api('/api/v6/ww2br-ops?projectDir=' + encodeURIComponent(projectDir));
2004
- if (!data) return;
2005
-
2006
- const stats = data.stats || {};
2007
- const hooked = (data.agents || []).filter(a => a.hookInstalled).length;
2008
- document.getElementById('badge-ww2br').textContent = data.recent?.length ?? 0;
2009
- animateNum('ww2-stat-memories', stats.totalEntries ?? 0);
2010
- animateNum('ww2-stat-projects', stats.totalProjects ?? 0);
2011
- animateNum('ww2-stat-hooks', hooked);
2012
- document.getElementById('ww2-stat-hit').textContent = Math.round((stats.hitRate ?? 0) * 100) + '%';
2013
- document.getElementById('ww2-stat-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';
2014
- document.getElementById('ww2-stat-sync').textContent = stats.lastSync ? 'sync ' + fmtDate(stats.lastSync) : 'last sync unknown';
2015
-
2016
- const warnings = data.warnings || [];
2017
- document.getElementById('ww2-warnings').innerHTML = warnings.length
2018
- ? warnings.map(w => '<div class="warning-banner"><strong>Warning</strong><span>' + htmlEscape(w) + '</span></div>').join('')
2019
- : '';
2020
-
2021
- document.getElementById('ww2-timeline').innerHTML = renderMemoryItems(data.recent || [], 'No memories found for this workflow yet.');
2022
- document.getElementById('ww2-agents').innerHTML = renderWW2Agents(data.agents || []);
2023
- document.getElementById('ww2-categories').innerHTML = renderWW2Categories(data.categories || {});
2024
- document.getElementById('ww2-quality').innerHTML = renderWW2Quality(data.quality);
2025
- document.getElementById('ww2-resume-pack').innerHTML = renderWW2ResumePack(data.resumePack);
2026
-
2027
- const query = document.getElementById('ww2-query');
2028
- if (query && !query.value) query.value = 'Gate 2 visual proof';
2029
- if (!document.getElementById('ww2-recall-results').innerHTML.trim()) {
2030
- document.getElementById('ww2-recall-results').innerHTML = '<div class="empty-tight">Run a recall query to inspect relevant brain memory.</div>';
2031
- }
2032
- }
2033
- function renderMemoryItems(items, emptyMessage) {
2034
- if (!items || !items.length) return '<div class="empty-tight">' + htmlEscape(emptyMessage) + '</div>';
2035
- return items.map(item => {
2036
- const confidence = item.importance !== undefined ? Math.round((item.importance || 0) * 100) + '%' : '';
2037
- const sources = item.sources || (item.metadata && item.metadata.sources) || [];
2038
- const tags = item.tags || (item.metadata && item.metadata.tags) || [];
2039
- const quality = item.qualityScore !== undefined ? '<span class="pill">Q ' + htmlEscape(item.qualityScore) + '</span>' : '';
2040
- const sourceLine = sources.length
2041
- ? '<div class="content" style="font-size:10.5px;color:var(--text-3);margin-top:6px">Sources: ' + sources.slice(0, 3).map(s => '<code>' + htmlEscape(s) + '</code>').join(' · ') + '</div>'
2042
- : '<div class="content" style="font-size:10.5px;color:var(--warning);margin-top:6px">Sources: none recorded</div>';
2043
- const tagLine = tags.length ? '<span class="pill">' + tags.slice(0, 3).map(htmlEscape).join(', ') + '</span>' : '';
2044
- return '<div class="memory-item">' +
2045
- '<div class="meta">' +
2046
- '<span class="pill cyan">' + htmlEscape(item.agentTool || item.agent || 'brain') + '</span>' +
2047
- '<span class="pill">' + htmlEscape(item.category || 'memory') + '</span>' +
2048
- (confidence ? '<span class="pill green">' + confidence + '</span>' : '') +
2049
- quality +
2050
- tagLine +
2051
- '<span>' + fmtDate(item.createdAt || item.lastAccessed) + '</span>' +
2052
- '</div>' +
2053
- '<div class="content">' + htmlEscape(item.content || item.detail || '').slice(0, 420) + '</div>' +
2054
- sourceLine +
2055
- '</div>';
2056
- }).join('');
2057
- }
2058
- function renderWW2Quality(quality) {
2059
- if (!quality) return '<div class="empty-tight">No memory quality data.</div>';
2060
- const sourcePct = Math.round((quality.sourceCoverage || 0) * 100);
2061
- const scoreCls = quality.avgScore >= 75 ? 'good' : quality.avgScore >= 55 ? 'warn' : 'bad';
2062
- return [
2063
- '<div class="row"><span class="k">Average score</span><span class="v ' + scoreCls + '">' + htmlEscape(quality.avgScore ?? 0) + '/100</span></div>',
2064
- '<div class="row"><span class="k">Source coverage</span><span class="v ' + (sourcePct >= 70 ? 'good' : 'warn') + '">' + sourcePct + '%</span></div>',
2065
- '<div class="row"><span class="k">Stale memories</span><span class="v ' + (quality.stale ? 'warn' : 'good') + '">' + htmlEscape(quality.stale || 0) + '</span></div>',
2066
- '<div class="row"><span class="k">Duplicate clusters</span><span class="v ' + (quality.duplicates?.length ? 'warn' : 'good') + '">' + htmlEscape(quality.duplicates?.length || 0) + '</span></div>',
2067
- ].join('');
2068
- }
2069
- function renderWW2ResumePack(pack) {
2070
- if (!pack) return '<div class="empty-tight">No resume pack generated.</div>';
2071
- const warnings = pack.warnings && pack.warnings.length
2072
- ? '<div class="warning-stack" style="margin:10px 0 0">' + pack.warnings.slice(0, 3).map(w => '<div class="warning-banner"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'
2073
- : '';
2074
- const blockers = pack.blockers && pack.blockers.length
2075
- ? '<div class="brain-feed mt">' + renderMemoryItems(pack.blockers.slice(0, 3), 'No blockers in pack.') + '</div>'
2076
- : '<div class="empty-tight">No blockers in pack.</div>';
2077
- const stats = pack.stats || {};
2078
- return '<div class="grid cols-3">' +
2079
- '<div class="row"><span class="k">Docs</span><span class="v">' + htmlEscape(stats.docCount ?? 0) + '</span></div>' +
2080
- '<div class="row"><span class="k">Memories</span><span class="v">' + htmlEscape(stats.memoryCount ?? 0) + '</span></div>' +
2081
- '<div class="row"><span class="k">Quality</span><span class="v">' + htmlEscape(stats.avgQualityScore ?? 0) + '/100</span></div>' +
2082
- '</div>' +
2083
- '<div class="mt" style="font-size:12px;color:var(--text-2)">Generated ' + htmlEscape(fmtDate(pack.generatedAt)) + '. Uses visible docs and stored memories only.</div>' +
2084
- warnings +
2085
- '<div class="mt">' + blockers + '</div>';
2086
- }
2087
- function openWW2ResumePack() {
2088
- const url = '/api/v6/resume-pack?format=markdown&projectDir=' + encodeURIComponent(getWW2ProjectPath());
2089
- window.open(url, '_blank', 'noopener');
2090
- }
2091
- function renderWW2Agents(agents) {
2092
- if (!agents.length) return '<div class="empty-tight">No known agent integrations returned.</div>';
2093
- return agents.map(agent => {
2094
- const cls = agent.hookInstalled ? 'green' : agent.detected ? 'cyan' : '';
2095
- const hook = agent.hookInstalled ? 'hooked' : 'not wired';
2096
- const detected = agent.detected ? 'detected' : 'not found';
2097
- return '<div class="ops-agent">' +
2098
- '<div class="name">' + htmlEscape(agent.displayName || agent.name) + '</div>' +
2099
- '<div class="status">' +
2100
- '<span class="pill ' + cls + '">' + hook + '</span>' +
2101
- '<span class="pill">' + detected + '</span>' +
2102
- '</div>' +
2103
- '</div>';
2104
- }).join('');
2105
- }
2106
- function renderWW2Categories(categories) {
2107
- const rows = Object.entries(categories).sort((a, b) => b[1] - a[1]);
2108
- if (!rows.length) return '<div class="empty-tight">No categories yet.</div>';
2109
- return rows.map(([name, count]) => '<div class="row"><span class="k">' + htmlEscape(name) + '</span><span class="v">' + count + '</span></div>').join('');
2110
- }
2111
- function setWW2Query(query) {
2112
- document.getElementById('ww2-query').value = query;
2113
- runWW2Recall();
2114
- }
2115
- async function runWW2Recall() {
2116
- const resultsEl = document.getElementById('ww2-recall-results');
2117
- const query = document.getElementById('ww2-query').value.trim();
2118
- const category = document.getElementById('ww2-category').value.trim();
2119
- resultsEl.innerHTML = '<div class="empty-tight">Searching brain memory...</div>';
2120
- const data = await api('/api/v6/ww2br-recall', {
2121
- projectDir: getWW2ProjectPath(),
2122
- query,
2123
- category: category || undefined,
2124
- limit: 12,
2125
- });
2126
- if (!data) return;
2127
- const prefix = data.projectScoped ? '' : '<div class="warning-banner"><strong>Fallback</strong><span>No project-scoped matches; showing global brain recall results.</span></div>';
2128
- resultsEl.innerHTML = prefix + renderMemoryItems(data.results || [], 'No recall matches for that query.');
2129
- }
2130
-
2131
- // ──── Charts ───────────────────────────────────────────────────────────
2472
+ function typeToPill(type) {
2473
+ if (type === 'memory-write') return 'green';
2474
+ if (type === 'subagent-spawn') return 'cyan';
2475
+ if (type === 'collision') return 'red';
2476
+ if (type === 'quarantine') return 'yellow';
2477
+ if (type === 'seed-draft') return 'yellow';
2478
+ if (type === 'memory-seeded') return 'green';
2479
+ if (['dream','revisit','counterfactual','consolidation','pattern-discovery','contradiction'].includes(type)) return 'purple';
2480
+ return '';
2481
+ }
2482
+
2483
+ function getWW2ProjectPath() {
2484
+ return document.getElementById('ww2-project-path')?.value?.trim() || 'D:\\\\New Game';
2485
+ }
2486
+ async function refreshWW2BR() {
2487
+ const projectDir = getWW2ProjectPath();
2488
+ const data = await api('/api/v6/ww2br-ops?projectDir=' + encodeURIComponent(projectDir));
2489
+ if (!data) return;
2490
+
2491
+ const stats = data.stats || {};
2492
+ const hooked = (data.agents || []).filter(a => a.hookInstalled).length;
2493
+ document.getElementById('badge-ww2br').textContent = data.recent?.length ?? 0;
2494
+ animateNum('ww2-stat-memories', stats.totalEntries ?? 0);
2495
+ animateNum('ww2-stat-projects', stats.totalProjects ?? 0);
2496
+ animateNum('ww2-stat-hooks', hooked);
2497
+ document.getElementById('ww2-stat-hit').textContent = Math.round((stats.hitRate ?? 0) * 100) + '%';
2498
+ document.getElementById('ww2-stat-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';
2499
+ document.getElementById('ww2-stat-sync').textContent = stats.lastSync ? 'sync ' + fmtDate(stats.lastSync) : 'last sync unknown';
2500
+
2501
+ const warnings = data.warnings || [];
2502
+ document.getElementById('ww2-warnings').innerHTML = warnings.length
2503
+ ? warnings.map(w => '<div class="warning-banner"><strong>Warning</strong><span>' + htmlEscape(w) + '</span></div>').join('')
2504
+ : '';
2505
+
2506
+ document.getElementById('ww2-timeline').innerHTML = renderMemoryItems(data.recent || [], 'No memories found for this workflow yet.');
2507
+ document.getElementById('ww2-agents').innerHTML = renderWW2Agents(data.agents || []);
2508
+ document.getElementById('ww2-categories').innerHTML = renderWW2Categories(data.categories || {});
2509
+ document.getElementById('ww2-quality').innerHTML = renderWW2Quality(data.quality);
2510
+ document.getElementById('ww2-resume-pack').innerHTML = renderWW2ResumePack(data.resumePack);
2511
+
2512
+ const query = document.getElementById('ww2-query');
2513
+ if (query && !query.value) query.value = 'Gate 2 visual proof';
2514
+ if (!document.getElementById('ww2-recall-results').innerHTML.trim()) {
2515
+ document.getElementById('ww2-recall-results').innerHTML = '<div class="empty-tight">Run a recall query to inspect relevant brain memory.</div>';
2516
+ }
2517
+ }
2518
+ function renderMemoryItems(items, emptyMessage) {
2519
+ if (!items || !items.length) return '<div class="empty-tight">' + htmlEscape(emptyMessage) + '</div>';
2520
+ return items.map(item => {
2521
+ const confidence = item.importance !== undefined ? Math.round((item.importance || 0) * 100) + '%' : '';
2522
+ const sources = item.sources || (item.metadata && item.metadata.sources) || [];
2523
+ const tags = item.tags || (item.metadata && item.metadata.tags) || [];
2524
+ const quality = item.qualityScore !== undefined ? '<span class="pill">Q ' + htmlEscape(item.qualityScore) + '</span>' : '';
2525
+ const sourceLine = sources.length
2526
+ ? '<div class="content" style="font-size:10.5px;color:var(--text-3);margin-top:6px">Sources: ' + sources.slice(0, 3).map(s => '<code>' + htmlEscape(s) + '</code>').join(' · ') + '</div>'
2527
+ : '<div class="content" style="font-size:10.5px;color:var(--warning);margin-top:6px">Sources: none recorded</div>';
2528
+ const tagLine = tags.length ? '<span class="pill">' + tags.slice(0, 3).map(htmlEscape).join(', ') + '</span>' : '';
2529
+ return '<div class="memory-item">' +
2530
+ '<div class="meta">' +
2531
+ '<span class="pill cyan">' + htmlEscape(item.agentTool || item.agent || 'brain') + '</span>' +
2532
+ '<span class="pill">' + htmlEscape(item.category || 'memory') + '</span>' +
2533
+ (confidence ? '<span class="pill green">' + confidence + '</span>' : '') +
2534
+ quality +
2535
+ tagLine +
2536
+ '<span>' + fmtDate(item.createdAt || item.lastAccessed) + '</span>' +
2537
+ '</div>' +
2538
+ '<div class="content">' + htmlEscape(item.content || item.detail || '').slice(0, 420) + '</div>' +
2539
+ sourceLine +
2540
+ '</div>';
2541
+ }).join('');
2542
+ }
2543
+ function renderWW2Quality(quality) {
2544
+ if (!quality) return '<div class="empty-tight">No memory quality data.</div>';
2545
+ const sourcePct = Math.round((quality.sourceCoverage || 0) * 100);
2546
+ const scoreCls = quality.avgScore >= 75 ? 'good' : quality.avgScore >= 55 ? 'warn' : 'bad';
2547
+ return [
2548
+ '<div class="row"><span class="k">Average score</span><span class="v ' + scoreCls + '">' + htmlEscape(quality.avgScore ?? 0) + '/100</span></div>',
2549
+ '<div class="row"><span class="k">Source coverage</span><span class="v ' + (sourcePct >= 70 ? 'good' : 'warn') + '">' + sourcePct + '%</span></div>',
2550
+ '<div class="row"><span class="k">Stale memories</span><span class="v ' + (quality.stale ? 'warn' : 'good') + '">' + htmlEscape(quality.stale || 0) + '</span></div>',
2551
+ '<div class="row"><span class="k">Duplicate clusters</span><span class="v ' + (quality.duplicates?.length ? 'warn' : 'good') + '">' + htmlEscape(quality.duplicates?.length || 0) + '</span></div>',
2552
+ ].join('');
2553
+ }
2554
+ function renderWW2ResumePack(pack) {
2555
+ if (!pack) return '<div class="empty-tight">No resume pack generated.</div>';
2556
+ const warnings = pack.warnings && pack.warnings.length
2557
+ ? '<div class="warning-stack" style="margin:10px 0 0">' + pack.warnings.slice(0, 3).map(w => '<div class="warning-banner"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'
2558
+ : '';
2559
+ const blockers = pack.blockers && pack.blockers.length
2560
+ ? '<div class="brain-feed mt">' + renderMemoryItems(pack.blockers.slice(0, 3), 'No blockers in pack.') + '</div>'
2561
+ : '<div class="empty-tight">No blockers in pack.</div>';
2562
+ const stats = pack.stats || {};
2563
+ return '<div class="grid cols-3">' +
2564
+ '<div class="row"><span class="k">Docs</span><span class="v">' + htmlEscape(stats.docCount ?? 0) + '</span></div>' +
2565
+ '<div class="row"><span class="k">Memories</span><span class="v">' + htmlEscape(stats.memoryCount ?? 0) + '</span></div>' +
2566
+ '<div class="row"><span class="k">Quality</span><span class="v">' + htmlEscape(stats.avgQualityScore ?? 0) + '/100</span></div>' +
2567
+ '</div>' +
2568
+ '<div class="mt" style="font-size:12px;color:var(--text-2)">Generated ' + htmlEscape(fmtDate(pack.generatedAt)) + '. Uses visible docs and stored memories only.</div>' +
2569
+ warnings +
2570
+ '<div class="mt">' + blockers + '</div>';
2571
+ }
2572
+ function openWW2ResumePack() {
2573
+ const url = '/api/v6/resume-pack?format=markdown&projectDir=' + encodeURIComponent(getWW2ProjectPath());
2574
+ window.open(url, '_blank', 'noopener');
2575
+ }
2576
+ function renderWW2Agents(agents) {
2577
+ if (!agents.length) return '<div class="empty-tight">No known agent integrations returned.</div>';
2578
+ return agents.map(agent => {
2579
+ const cls = agent.hookInstalled ? 'green' : agent.detected ? 'cyan' : '';
2580
+ const hook = agent.hookInstalled ? 'hooked' : 'not wired';
2581
+ const detected = agent.detected ? 'detected' : 'not found';
2582
+ return '<div class="ops-agent">' +
2583
+ '<div class="name">' + htmlEscape(agent.displayName || agent.name) + '</div>' +
2584
+ '<div class="status">' +
2585
+ '<span class="pill ' + cls + '">' + hook + '</span>' +
2586
+ '<span class="pill">' + detected + '</span>' +
2587
+ '</div>' +
2588
+ '</div>';
2589
+ }).join('');
2590
+ }
2591
+ function renderWW2Categories(categories) {
2592
+ const rows = Object.entries(categories).sort((a, b) => b[1] - a[1]);
2593
+ if (!rows.length) return '<div class="empty-tight">No categories yet.</div>';
2594
+ return rows.map(([name, count]) => '<div class="row"><span class="k">' + htmlEscape(name) + '</span><span class="v">' + count + '</span></div>').join('');
2595
+ }
2596
+ function setWW2Query(query) {
2597
+ document.getElementById('ww2-query').value = query;
2598
+ runWW2Recall();
2599
+ }
2600
+ async function runWW2Recall() {
2601
+ const resultsEl = document.getElementById('ww2-recall-results');
2602
+ const query = document.getElementById('ww2-query').value.trim();
2603
+ const category = document.getElementById('ww2-category').value.trim();
2604
+ resultsEl.innerHTML = '<div class="empty-tight">Searching brain memory...</div>';
2605
+ const data = await api('/api/v6/ww2br-recall', {
2606
+ projectDir: getWW2ProjectPath(),
2607
+ query,
2608
+ category: category || undefined,
2609
+ limit: 12,
2610
+ });
2611
+ if (!data) return;
2612
+ const prefix = data.projectScoped ? '' : '<div class="warning-banner"><strong>Fallback</strong><span>No project-scoped matches; showing global brain recall results.</span></div>';
2613
+ resultsEl.innerHTML = prefix + renderMemoryItems(data.results || [], 'No recall matches for that query.');
2614
+ }
2615
+
2616
+ // ──── Charts ───────────────────────────────────────────────────────────
2132
2617
  function chartTheme() {
2133
2618
  const dark = document.documentElement.dataset.theme !== 'light';
2134
2619
  return { grid: dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)', text: dark ? '#a4afc4' : '#3d4864' };
2135
2620
  }
2136
- function spendLabels(buckets) {
2137
- return Object.keys(buckets || {}).sort((a, b) => (buckets[b]?.spendUsd || 0) - (buckets[a]?.spendUsd || 0));
2138
- }
2139
- function drawSpendBarChart(canvasId, chartKey, buckets) {
2140
- const ctx = document.getElementById(canvasId);
2141
- if (!ctx) return;
2142
- const labels = spendLabels(buckets);
2143
- const data = labels.map(l => buckets[l].spendUsd || 0);
2144
- const t = chartTheme();
2145
- if (charts[chartKey]) charts[chartKey].destroy();
2146
- charts[chartKey] = new Chart(ctx, { type: 'bar', data: { labels, datasets: [{ label: 'Spend (USD)', data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderRadius: 6 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{display:false}}, scales:{x:{grid:{color:t.grid},ticks:{color:t.text}},y:{grid:{color:t.grid},ticks:{color:t.text,callback:v=>'$'+Number(v).toFixed(2)}}} } });
2147
- }
2148
- function drawTokensChart(byAgent) {
2149
- drawSpendBarChart('chart-tokens', 'tokens', byAgent || {});
2150
- }
2151
- function drawTokensAgentChart(byAgent) {
2152
- drawSpendBarChart('chart-tokens-agent', 'tokensAgent', byAgent || {});
2153
- }
2154
- function drawTokensModelChart(byModel) {
2155
- const ctx = document.getElementById('chart-tokens-model');
2156
- if (!ctx) return;
2157
- const labels = spendLabels(byModel);
2158
- const data = labels.map(l => byModel[l].spendUsd || 0);
2159
- const t = chartTheme();
2160
- if (charts.tokensModel) charts.tokensModel.destroy();
2161
- charts.tokensModel = new Chart(ctx, { type: 'doughnut', data: { labels, datasets: [{ data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderWidth:0 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{position:'right',labels:{color:t.text,boxWidth:12}}} } });
2162
- }
2163
- function renderSpendRows(buckets, emptyMessage) {
2164
- const labels = spendLabels(buckets).slice(0, 8);
2165
- if (!labels.length) return '<div class="empty-tight">' + htmlEscape(emptyMessage) + '</div>';
2166
- return labels.map(label => {
2167
- const b = buckets[label] || {};
2168
- const last = b.lastSeen ? new Date(b.lastSeen).toLocaleString() : '—';
2169
- return \`<div class="row"><span class="k">\${htmlEscape(label)} <span class="tag-code">\${b.calls || 0} calls</span></span><span class="v">$\${Number(b.spendUsd || 0).toFixed(4)} <span class="mono" style="color:var(--text-3)">\${last}</span></span></div>\`;
2170
- }).join('');
2171
- }
2621
+ function spendLabels(buckets) {
2622
+ return Object.keys(buckets || {}).sort((a, b) => (buckets[b]?.spendUsd || 0) - (buckets[a]?.spendUsd || 0));
2623
+ }
2624
+ function drawSpendBarChart(canvasId, chartKey, buckets) {
2625
+ const ctx = document.getElementById(canvasId);
2626
+ if (!ctx) return;
2627
+ const labels = spendLabels(buckets);
2628
+ const data = labels.map(l => buckets[l].spendUsd || 0);
2629
+ const t = chartTheme();
2630
+ if (charts[chartKey]) charts[chartKey].destroy();
2631
+ charts[chartKey] = new Chart(ctx, { type: 'bar', data: { labels, datasets: [{ label: 'Spend (USD)', data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderRadius: 6 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{display:false}}, scales:{x:{grid:{color:t.grid},ticks:{color:t.text}},y:{grid:{color:t.grid},ticks:{color:t.text,callback:v=>'$'+Number(v).toFixed(2)}}} } });
2632
+ }
2633
+ function drawTokensChart(byAgent) {
2634
+ drawSpendBarChart('chart-tokens', 'tokens', byAgent || {});
2635
+ }
2636
+ function drawTokensAgentChart(byAgent) {
2637
+ drawSpendBarChart('chart-tokens-agent', 'tokensAgent', byAgent || {});
2638
+ }
2639
+ function drawTokensModelChart(byModel) {
2640
+ const ctx = document.getElementById('chart-tokens-model');
2641
+ if (!ctx) return;
2642
+ const labels = spendLabels(byModel);
2643
+ const data = labels.map(l => byModel[l].spendUsd || 0);
2644
+ const t = chartTheme();
2645
+ if (charts.tokensModel) charts.tokensModel.destroy();
2646
+ charts.tokensModel = new Chart(ctx, { type: 'doughnut', data: { labels, datasets: [{ data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderWidth:0 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{position:'right',labels:{color:t.text,boxWidth:12}}} } });
2647
+ }
2648
+ function renderSpendRows(buckets, emptyMessage) {
2649
+ const labels = spendLabels(buckets).slice(0, 8);
2650
+ if (!labels.length) return '<div class="empty-tight">' + htmlEscape(emptyMessage) + '</div>';
2651
+ return labels.map(label => {
2652
+ const b = buckets[label] || {};
2653
+ const last = b.lastSeen ? new Date(b.lastSeen).toLocaleString() : '—';
2654
+ return \`<div class="row"><span class="k">\${htmlEscape(label)} <span class="tag-code">\${b.calls || 0} calls</span></span><span class="v">$\${Number(b.spendUsd || 0).toFixed(4)} <span class="mono" style="color:var(--text-3)">\${last}</span></span></div>\`;
2655
+ }).join('');
2656
+ }
2172
2657
  function drawDreamChart(byType) {
2173
2658
  const ctx = document.getElementById('chart-dreams');
2174
2659
  if (!ctx) return;
@@ -2232,13 +2717,64 @@ async function traceCausal() {
2232
2717
  const id = document.getElementById('causal-mem-id').value;
2233
2718
  if (!id) return toast('Missing', 'Enter a memory ID', 'warn');
2234
2719
  const res = await api('/api/v6/causal-trace', { memoryId: id, maxDepth: parseInt(document.getElementById('causal-depth').value || '8') });
2235
- if (res) document.getElementById('causal-output').textContent = res.dot;
2720
+ if (res) renderCausalDag(res, id);
2236
2721
  }
2237
2722
  async function influenceCausal() {
2238
2723
  const id = document.getElementById('causal-mem-id').value;
2239
- if (!id) return;
2724
+ if (!id) return toast('Missing', 'Enter a memory ID', 'warn');
2240
2725
  const res = await api('/api/v6/causal-influence', { memoryId: id, maxDepth: 6 });
2241
- if (res) document.getElementById('causal-output').textContent = res.dot;
2726
+ if (res) renderCausalDag(res, id);
2727
+ }
2728
+ function renderCausalDag(chain, rootId) {
2729
+ const wrap = document.getElementById('causal-dag-wrap');
2730
+ const cv = document.getElementById('causal-dag');
2731
+ const empty = document.getElementById('causal-dag-empty');
2732
+ if (!cv || !wrap) return;
2733
+ const nodes = (chain && chain.nodes) || [];
2734
+ if (!nodes.length) { if (empty) { empty.style.display = 'flex'; empty.textContent = 'No causal links recorded for this memory yet.'; } return; }
2735
+ if (empty) empty.style.display = 'none';
2736
+ cv.width = wrap.clientWidth || 800; cv.height = wrap.clientHeight || 420;
2737
+ const W = cv.width, H = cv.height, ctx = cv.getContext('2d');
2738
+ // Layer nodes by depth
2739
+ const byDepth = {};
2740
+ let maxDepth = 0;
2741
+ for (const n of nodes) { const d = n.depth || 0; (byDepth[d] = byDepth[d] || []).push(n); if (d > maxDepth) maxDepth = d; }
2742
+ const pos = new Map();
2743
+ const layers = maxDepth + 1;
2744
+ for (let d = 0; d <= maxDepth; d++) {
2745
+ const row = byDepth[d] || [];
2746
+ const y = layers === 1 ? H / 2 : 40 + (d / Math.max(1, maxDepth)) * (H - 80);
2747
+ row.forEach((n, i) => { pos.set(n.id, { x: (i + 1) / (row.length + 1) * W, y: y, n: n }); });
2748
+ }
2749
+ ctx.clearRect(0, 0, W, H);
2750
+ // Links
2751
+ for (const l of (chain.links || [])) {
2752
+ const a = pos.get(l.causeId), b = pos.get(l.effectId);
2753
+ if (!a || !b) continue;
2754
+ ctx.strokeStyle = 'rgba(24,255,255,' + (0.25 + 0.5 * (l.strength || 0.5)) + ')';
2755
+ ctx.lineWidth = 1 + (l.strength || 0.5) * 2;
2756
+ ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
2757
+ // arrowhead
2758
+ const ang = Math.atan2(b.y - a.y, b.x - a.x);
2759
+ ctx.fillStyle = 'rgba(24,255,255,0.7)';
2760
+ ctx.beginPath(); ctx.moveTo(b.x - 11 * Math.cos(ang - 0.4), b.y - 11 * Math.sin(ang - 0.4)); ctx.lineTo(b.x, b.y); ctx.lineTo(b.x - 11 * Math.cos(ang + 0.4), b.y - 11 * Math.sin(ang + 0.4)); ctx.fill();
2761
+ }
2762
+ // Nodes
2763
+ for (const p of pos.values()) {
2764
+ const isRoot = p.n.id === rootId;
2765
+ const col = isRoot ? '#ec4899' : '#a855f7';
2766
+ const r = isRoot ? 13 : 9;
2767
+ const halo = ctx.createRadialGradient(p.x, p.y, r * 0.4, p.x, p.y, r * 2.4);
2768
+ halo.addColorStop(0, col + '88'); halo.addColorStop(1, 'rgba(0,0,0,0)');
2769
+ ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(p.x, p.y, r * 2.4, 0, Math.PI * 2); ctx.fill();
2770
+ ctx.fillStyle = '#0b1020'; ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, Math.PI * 2); ctx.fill();
2771
+ ctx.strokeStyle = col; ctx.lineWidth = 2; ctx.stroke();
2772
+ ctx.fillStyle = document.documentElement.dataset.theme === 'light' ? '#1a2030' : '#e8ecf4';
2773
+ ctx.font = '10px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
2774
+ const lbl = String(p.n.content || p.n.category || p.n.id).slice(0, 22);
2775
+ ctx.fillText(lbl, p.x, p.y + r + 10);
2776
+ }
2777
+ toast('Causal chain', nodes.length + ' nodes · ' + ((chain.links || []).length) + ' links');
2242
2778
  }
2243
2779
  async function linkCausal() {
2244
2780
  const r = await api('/api/v6/causal-link', {
@@ -2287,6 +2823,28 @@ async function refreshReputation() {
2287
2823
  document.getElementById('rep-leaderboard').innerHTML = top.length ? top.map(a =>
2288
2824
  \`<tr><td><span class="pill cyan">\${a.agentTool}</span></td><td>\${((a.accuracy ?? 0) * 100).toFixed(1)}%</td><td>\${a.decisions}</td><td class="mono">—</td></tr>\`
2289
2825
  ).join('') : '<tr><td colspan="4"><div class="empty">No signed decisions yet</div></td></tr>';
2826
+ const grid = document.getElementById('rep-trust-grid');
2827
+ if (grid) {
2828
+ if (!top.length) {
2829
+ grid.innerHTML = emptyState('shield', 'No trust receipts yet', 'Agents build a portable trust score by signing decision receipts. Run: shadow-brain reputation sign --agent &lt;name&gt;', null);
2830
+ } else {
2831
+ grid.innerHTML = top.map(function (a, i) {
2832
+ return '<div class="trust-card"><div class="trust-ring"><canvas id="trust-ring-' + i + '"></canvas><div class="tr-pct">' + Math.round((a.accuracy || 0) * 100) + '</div></div><div class="tc-name">' + htmlEscape(a.agentTool) + '</div><div class="tc-sub">' + a.decisions + ' decisions</div></div>';
2833
+ }).join('');
2834
+ top.forEach(function (a, i) { drawTrustRing('trust-ring-' + i, a.accuracy || 0); });
2835
+ }
2836
+ }
2837
+ }
2838
+ function drawTrustRing(id, frac) {
2839
+ const cv = document.getElementById(id); if (!cv || !cv.getContext) return;
2840
+ cv.width = 92; cv.height = 92;
2841
+ const ctx = cv.getContext('2d'), cx = 46, cy = 46, r = 38;
2842
+ ctx.clearRect(0, 0, 92, 92);
2843
+ ctx.lineWidth = 8; ctx.strokeStyle = 'rgba(120,140,180,0.18)';
2844
+ ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke();
2845
+ const col = frac >= 0.8 ? '#10e68c' : frac >= 0.5 ? '#ffb53f' : '#ff5864';
2846
+ ctx.strokeStyle = col; ctx.lineCap = 'round';
2847
+ ctx.beginPath(); ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + frac * Math.PI * 2); ctx.stroke();
2290
2848
  }
2291
2849
  async function genBadge() {
2292
2850
  const res = await api('/api/v6/reputation-badge', { agentTool: document.getElementById('rep-agent').value, agentVersion: document.getElementById('rep-ver').value || undefined });
@@ -2308,40 +2866,91 @@ async function runDebate() {
2308
2866
 
2309
2867
  // ──── Pre-mortem ───────────────────────────────────────────────────────
2310
2868
  async function runPremortem() {
2311
- const res = await api('/api/v6/premortem-run', { taskDescription: document.getElementById('pm-task').value, projectDir: '' });
2869
+ const task = document.getElementById('pm-task').value;
2870
+ if (!task.trim()) return toast('Missing', 'Describe the task first', 'warn');
2871
+ const res = await api('/api/v6/premortem-run', { taskDescription: task, projectDir: '' });
2312
2872
  if (!res) return;
2313
- document.getElementById('pm-output').innerHTML = \`
2314
- <div style="margin-bottom:10px">Risk: <strong style="font-size:16px;color:\${res.riskScore > 0.6 ? 'var(--danger)' : res.riskScore > 0.3 ? 'var(--warning)' : 'var(--success)'}">\${Math.round(res.riskScore * 100)}%</strong></div>
2315
- <div class="mono mb-lg">\${res.summary}</div>
2316
- \${res.failures.map(f => \`<div style="padding:12px;background:var(--bg-elev-2);border-left:3px solid var(--danger);margin-bottom:8px;border-radius:6px"><div style="display:flex;gap:6px;align-items:center;margin-bottom:4px"><span class="pill red">\${f.severity}</span><span class="pill">p=\${f.probability.toFixed(2)}</span><span class="pill">\${f.source}</span></div><div>\${f.description}</div><div class="mono" style="color:var(--text-2);margin-top:6px">→ \${f.mitigation}</div></div>\`).join('')}\`;
2873
+ const out = document.getElementById('pm-output');
2874
+ const sevColor = { critical: '#ff5864', high: '#ffb53f', medium: '#ffca28', low: '#10e68c' };
2875
+ const riskCol = res.riskScore > 0.6 ? '#ff5864' : res.riskScore > 0.3 ? '#ffb53f' : '#10e68c';
2876
+ const failures = res.failures || [];
2877
+ // Risk matrix buckets: rows = severity (crit..low), cols = probability quartile
2878
+ const sevOrder = ['critical', 'high', 'medium', 'low'];
2879
+ const cells = {};
2880
+ for (const f of failures) {
2881
+ const col = Math.min(3, Math.floor((f.probability || 0) * 4));
2882
+ const key = (f.severity || 'low') + ':' + col;
2883
+ cells[key] = (cells[key] || 0) + 1;
2884
+ }
2885
+ const cellColor = (sev, col) => {
2886
+ const sevW = { critical: 3, high: 2, medium: 1, low: 0 }[sev];
2887
+ const risk = (sevW + col) / 6;
2888
+ return risk > 0.6 ? 'rgba(255,88,100,0.85)' : risk > 0.35 ? 'rgba(255,181,63,0.8)' : 'rgba(16,230,140,0.7)';
2889
+ };
2890
+ let matrix = '<div class="risk-matrix"><div class="rm-h"></div><div class="rm-h">P 0-25%</div><div class="rm-h">25-50%</div><div class="rm-h">50-75%</div><div class="rm-h">75-100%</div>';
2891
+ for (const sev of sevOrder) {
2892
+ matrix += '<div class="rm-yl">' + sev + '</div>';
2893
+ for (let col = 0; col < 4; col++) {
2894
+ const c = cells[sev + ':' + col] || 0;
2895
+ matrix += c ? '<div class="rm-cell" style="background:' + cellColor(sev, col) + '">' + c + '</div>' : '<div class="rm-cell empty">·</div>';
2896
+ }
2897
+ }
2898
+ matrix += '</div>';
2899
+ let html = '<div class="risk-head"><div class="risk-gauge"><canvas id="pm-gauge"></canvas><div class="rg-num" style="color:' + riskCol + '">' + Math.round(res.riskScore * 100) + '</div></div>';
2900
+ html += '<div><div style="font-size:13px;font-weight:700;margin-bottom:4px">Overall Risk Score</div><div style="font-size:12px;color:var(--text-2);max-width:520px;line-height:1.5">' + htmlEscape(res.summary || '') + '</div></div></div>';
2901
+ html += matrix;
2902
+ html += failures.map(function (f) {
2903
+ const col = sevColor[f.severity] || '#10e68c';
2904
+ return '<div class="fail-card" style="border-left-color:' + col + '"><div class="fc-head"><span class="pill" style="color:' + col + ';border-color:' + col + '">' + htmlEscape(f.severity) + '</span><span class="pill">p=' + (f.probability || 0).toFixed(2) + '</span><span class="pill">' + htmlEscape(f.source || '') + '</span></div><div class="fc-desc">' + htmlEscape(f.description || '') + '</div>' + (f.mitigation ? '<div class="fc-mit">' + htmlEscape(f.mitigation) + '</div>' : '') + '</div>';
2905
+ }).join('') || emptyState('check', 'No predicted failures', 'The pre-mortem surfaced no significant risks for this task based on past incidents and similar projects.', null);
2906
+ out.innerHTML = html;
2907
+ drawRiskGauge('pm-gauge', res.riskScore, riskCol);
2908
+ }
2909
+ function drawRiskGauge(id, frac, col) {
2910
+ const cv = document.getElementById(id); if (!cv || !cv.getContext) return;
2911
+ cv.width = 96; cv.height = 96;
2912
+ const ctx = cv.getContext('2d'), cx = 48, cy = 48, r = 38;
2913
+ ctx.clearRect(0, 0, 96, 96);
2914
+ ctx.lineWidth = 9; ctx.strokeStyle = 'rgba(120,140,180,0.18)';
2915
+ ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke();
2916
+ ctx.strokeStyle = col; ctx.lineCap = 'round';
2917
+ ctx.beginPath(); ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + Math.max(0.02, frac) * Math.PI * 2); ctx.stroke();
2317
2918
  }
2318
2919
 
2319
2920
  // ──── Attention ────────────────────────────────────────────────────────
2320
2921
  async function runAttention() {
2321
2922
  const ids = document.getElementById('att-memories').value.split(',').map(s => s.trim()).filter(Boolean);
2322
2923
  const res = await api('/api/v6/attention-heatmap', { decisionText: document.getElementById('att-decision').value, candidateMemoryIds: ids });
2323
- if (res) document.getElementById('att-output').textContent = res.weights.map(w => (w.weight * 100).toFixed(1).padStart(5) + '% ' + '█'.repeat(Math.min(30, Math.round(w.weight * 50))) + ' [' + w.category + '] ' + (w.memoryContent || '').slice(0, 80)).join('\\n');
2924
+ const out = document.getElementById('att-output');
2925
+ if (!res) return;
2926
+ const weights = res.weights || [];
2927
+ if (!weights.length) { out.innerHTML = emptyState('target', 'No attention weights', 'Provide a decision and some candidate memory IDs (or let it use recent memories) to see which ones carried the most weight.', null); return; }
2928
+ out.innerHTML = '<div style="font-size:11.5px;color:var(--text-3);margin-bottom:10px">' + res.totalMemoriesConsidered + ' memories considered for: <strong style="color:var(--text-2)">' + htmlEscape(res.decisionText || '') + '</strong></div>' + weights.map(function (w) {
2929
+ const pct = (w.weight || 0) * 100;
2930
+ const col = pct > 60 ? '#ff5864' : pct > 30 ? '#ffb53f' : '#18ffff';
2931
+ return '<div class="heat-row"><div class="hr-top"><span>' + htmlEscape((w.memoryContent || '').slice(0, 80)) + '</span><span class="hr-pct" style="color:' + col + '">' + pct.toFixed(1) + '%</span></div><div class="hr-track"><div class="hr-fill" style="width:' + Math.max(2, pct) + '%;background:' + col + '"></div></div>' + (w.reasoning ? '<div class="hr-reason">' + htmlEscape(w.reasoning) + '</div>' : '') + '</div>';
2932
+ }).join('');
2324
2933
  }
2325
2934
 
2326
2935
  // ──── Tokens ───────────────────────────────────────────────────────────
2327
- async function refreshTokens() {
2328
- const res = await api('/api/v6/tokens-report');
2329
- if (!res) return;
2330
- const runtime = res.runtime || {};
2331
- document.getElementById('tok-agent').textContent = runtime.currentAgentDisplay || 'Unknown';
2332
- document.getElementById('tok-runtime').textContent = (runtime.provider || 'provider') + (runtime.model ? ' / ' + runtime.model : '');
2333
- document.getElementById('tok-runtime-note').innerHTML = \`<strong>Accuracy</strong><span>\${htmlEscape(runtime.note || 'Token records are historical. Current agent and model spend are shown separately.')}</span>\`;
2334
- document.getElementById('tok-total').textContent = '$' + (res.totalSpendUsd || 0).toFixed(4);
2335
- document.getElementById('tok-recent').textContent = '$' + (res.recentSpendUsd || 0).toFixed(4);
2336
- document.getElementById('tok-proj').textContent = '$' + (res.monthlyProjectionUsd || 0).toFixed(2);
2936
+ async function refreshTokens() {
2937
+ const res = await api('/api/v6/tokens-report');
2938
+ if (!res) return;
2939
+ const runtime = res.runtime || {};
2940
+ document.getElementById('tok-agent').textContent = runtime.currentAgentDisplay || 'Unknown';
2941
+ document.getElementById('tok-runtime').textContent = (runtime.provider || 'provider') + (runtime.model ? ' / ' + runtime.model : '');
2942
+ document.getElementById('tok-runtime-note').innerHTML = \`<strong>Accuracy</strong><span>\${htmlEscape(runtime.note || 'Token records are historical. Current agent and model spend are shown separately.')}</span>\`;
2943
+ document.getElementById('tok-total').textContent = '$' + (res.totalSpendUsd || 0).toFixed(4);
2944
+ document.getElementById('tok-recent').textContent = '$' + (res.recentSpendUsd || 0).toFixed(4);
2945
+ document.getElementById('tok-proj').textContent = '$' + (res.monthlyProjectionUsd || 0).toFixed(2);
2337
2946
  document.getElementById('tok-save').textContent = '$' + (res.savingsOpportunitiesUsd || 0).toFixed(2);
2338
2947
  const calls = Object.values(res.byAgent || {}).reduce((a, b) => a + b.calls, 0);
2339
2948
  document.getElementById('tok-calls').textContent = calls;
2340
2949
  document.getElementById('tok-calls-sub').textContent = \`\${(res.totalInputTokens || 0).toLocaleString()} in · \${(res.totalOutputTokens || 0).toLocaleString()} out\`;
2341
- drawTokensAgentChart(res.recentByAgent || res.byAgent || {});
2342
- drawTokensModelChart(res.byModel || {});
2343
- document.getElementById('tok-agent-table').innerHTML = renderSpendRows(res.recentByAgent || res.byAgent || {}, 'No recent agent spend records.');
2344
- document.getElementById('tok-model-table').innerHTML = renderSpendRows(res.byModel || {}, 'No model spend records.');
2950
+ drawTokensAgentChart(res.recentByAgent || res.byAgent || {});
2951
+ drawTokensModelChart(res.byModel || {});
2952
+ document.getElementById('tok-agent-table').innerHTML = renderSpendRows(res.recentByAgent || res.byAgent || {}, 'No recent agent spend records.');
2953
+ document.getElementById('tok-model-table').innerHTML = renderSpendRows(res.byModel || {}, 'No model spend records.');
2345
2954
  document.getElementById('tok-suggestions').innerHTML = (res.suggestions || []).map(s => \`<div style="padding:8px 0;border-bottom:1px solid var(--border);display:flex;gap:8px"><span style="color:var(--success);flex-shrink:0">💡</span><span style="font-size:12px">\${s}</span></div>\`).join('') || '<div class="empty">Running lean — no suggestions</div>';
2346
2955
  }
2347
2956
 
@@ -2382,18 +2991,39 @@ async function refreshCalibration() {
2382
2991
  document.getElementById('calibration-table').innerHTML = scores.length ? scores.map(s =>
2383
2992
  \`<tr><td><span class="pill cyan">\${s.agentTool}</span></td><td>\${s.category}</td><td>\${s.brierScore}</td><td>\${s.calibrationError}</td><td>\${(s.overconfidenceRatio * 100).toFixed(0)}%</td><td class="v \${s.trustWeight > 0.8 ? 'good' : s.trustWeight > 0.5 ? '' : 'warn'}">\${s.trustWeight}</td><td>\${s.sampleSize}</td></tr>\`
2384
2993
  ).join('') : '<tr><td colspan="7"><div class="empty">No calibration data yet</div></td></tr>';
2994
+ drawCalibrationChart(scores);
2995
+ }
2996
+ function drawCalibrationChart(scores) {
2997
+ const cv = document.getElementById('chart-calibration'); if (!cv || !window.Chart) return;
2998
+ const t = chartTheme();
2999
+ if (charts.calibration) charts.calibration.destroy();
3000
+ const rows = (scores || []).slice(0, 8);
3001
+ const labels = rows.map(function (s) { return s.agentTool + '/' + String(s.category).slice(0, 10); });
3002
+ charts.calibration = new Chart(cv, {
3003
+ type: 'bar',
3004
+ data: { labels: labels, datasets: [
3005
+ { label: 'Brier (lower better)', data: rows.map(function (s) { return s.brierScore; }), backgroundColor: '#ff5864', borderRadius: 4 },
3006
+ { label: 'Trust weight', data: rows.map(function (s) { return s.trustWeight; }), backgroundColor: '#10e68c', borderRadius: 4 },
3007
+ ] },
3008
+ options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: t.text } } }, scales: { x: { grid: { color: t.grid }, ticks: { color: t.text } }, y: { beginAtZero: true, max: 1, grid: { color: t.grid }, ticks: { color: t.text } } } },
3009
+ });
2385
3010
  }
2386
3011
 
2387
3012
  // ──── Branch ───────────────────────────────────────────────────────────
2388
3013
  async function refreshBranch() {
2389
3014
  const r = await api('/api/v6/branch-state');
2390
- if (!r) return;
2391
- document.getElementById('branch-state').innerHTML = \`
2392
- <div class="row"><span class="k">Current branch</span><span class="v"><code>\${r.currentBranch}</code></span></div>
2393
- <div class="row"><span class="k">Branch memories</span><span class="v">\${r.branchMemoryCount}</span></div>
2394
- <div class="row"><span class="k">Global memories</span><span class="v">\${r.globalMemoryCount}</span></div>
2395
- <div class="row"><span class="k">Active IDs</span><span class="v">\${r.activeMemoryIds.length}</span></div>
2396
- <div class="row"><span class="k">Categories</span><span class="v mono">\${r.branchSpecificCategories.join(', ') || '—'}</span></div>\`;
3015
+ const cards = document.getElementById('branch-cards');
3016
+ const cats = document.getElementById('branch-cats');
3017
+ if (!r) { if (cards) cards.innerHTML = emptyState('git-branch', 'No branch data', 'Branch-aware memory activates inside a git repository. Open a project with branches to see scoped memory here.', null); return; }
3018
+ if (cards) cards.innerHTML =
3019
+ '<div class="card stat-card accent"><div class="label">Current Branch</div><div class="value" style="font-size:20px" id="br-cur">' + htmlEscape(r.currentBranch || '—') + '</div><div class="trend">active context</div></div>' +
3020
+ '<div class="card stat-card accent"><div class="label">Branch Memories</div><div class="value">' + (r.branchMemoryCount ?? 0) + '</div><div class="trend">scoped to branch</div></div>' +
3021
+ '<div class="card stat-card accent"><div class="label">Global Memories</div><div class="value">' + (r.globalMemoryCount ?? 0) + '</div><div class="trend">all branches</div></div>' +
3022
+ '<div class="card stat-card accent"><div class="label">Active IDs</div><div class="value">' + ((r.activeMemoryIds || []).length) + '</div><div class="trend">in working set</div></div>';
3023
+ if (cats) {
3024
+ const list = r.branchSpecificCategories || [];
3025
+ cats.innerHTML = list.length ? list.map(function (c) { return '<span class="tc">' + htmlEscape(String(c)) + '</span>'; }).join('') : '<div class="empty-tight">No branch-specific categories.</div>';
3026
+ }
2397
3027
  }
2398
3028
 
2399
3029
  // ──── Privacy ──────────────────────────────────────────────────────────
@@ -2713,26 +3343,26 @@ async function attachAll() { const r = await api('/api/v6/agents-attach-all', {}
2713
3343
  async function detachAll() { if (!confirm('Detach Shadow Brain hooks from ALL agents?')) return; const r = await api('/api/v6/agents-detach-all', {}); if (r) { toast('Detach All', (r.attached?.length ?? 0) + ' agents', 'warn'); refreshAgents(); } }
2714
3344
 
2715
3345
  // ──── Memory browser ───────────────────────────────────────────────────
2716
- function renderCountRows(rows, emptyMessage) {
2717
- if (!rows || !rows.length) return '<div class="empty-tight">' + htmlEscape(emptyMessage) + '</div>';
2718
- return rows.slice(0, 8).map(row =>
2719
- '<div class="row"><span class="k">' + htmlEscape(row.name) + '</span><span class="v">' + htmlEscape(row.count) + '</span></div>'
2720
- ).join('');
2721
- }
2722
- async function refreshMemoryIntelligence() {
2723
- const data = await api('/api/v6/memory-intelligence?limit=500');
2724
- if (!data) return;
2725
- document.getElementById('mem-total').textContent = data.totalEntries ?? 0;
2726
- document.getElementById('mem-sampled').textContent = (data.sampledEntries ?? 0) + ' shown in intelligence sample';
2727
- document.getElementById('mem-source').textContent = Math.round((data.sourceCoverage || 0) * 100) + '%';
2728
- document.getElementById('mem-fresh').textContent = data.recent7d ?? 0;
2729
- document.getElementById('mem-seed-open').textContent = data.openSeedDrafts ?? 0;
2730
- document.getElementById('mem-seed-sub').textContent = (data.pendingApprovals ?? 0) + ' need approval';
2731
- document.getElementById('mem-categories').innerHTML = renderCountRows(data.byCategory, 'No categories yet.');
2732
- document.getElementById('mem-agents').innerHTML = renderCountRows(data.byAgent, 'No agent memory writes yet.');
2733
- document.getElementById('mem-tags').innerHTML = renderCountRows(data.topTags, 'No tags yet.');
2734
- }
2735
- async function searchMemory() {
3346
+ function renderCountRows(rows, emptyMessage) {
3347
+ if (!rows || !rows.length) return '<div class="empty-tight">' + htmlEscape(emptyMessage) + '</div>';
3348
+ return rows.slice(0, 8).map(row =>
3349
+ '<div class="row"><span class="k">' + htmlEscape(row.name) + '</span><span class="v">' + htmlEscape(row.count) + '</span></div>'
3350
+ ).join('');
3351
+ }
3352
+ async function refreshMemoryIntelligence() {
3353
+ const data = await api('/api/v6/memory-intelligence?limit=500');
3354
+ if (!data) return;
3355
+ document.getElementById('mem-total').textContent = data.totalEntries ?? 0;
3356
+ document.getElementById('mem-sampled').textContent = (data.sampledEntries ?? 0) + ' shown in intelligence sample';
3357
+ document.getElementById('mem-source').textContent = Math.round((data.sourceCoverage || 0) * 100) + '%';
3358
+ document.getElementById('mem-fresh').textContent = data.recent7d ?? 0;
3359
+ document.getElementById('mem-seed-open').textContent = data.openSeedDrafts ?? 0;
3360
+ document.getElementById('mem-seed-sub').textContent = (data.pendingApprovals ?? 0) + ' need approval';
3361
+ document.getElementById('mem-categories').innerHTML = renderCountRows(data.byCategory, 'No categories yet.');
3362
+ document.getElementById('mem-agents').innerHTML = renderCountRows(data.byAgent, 'No agent memory writes yet.');
3363
+ document.getElementById('mem-tags').innerHTML = renderCountRows(data.topTags, 'No tags yet.');
3364
+ }
3365
+ async function searchMemory() {
2736
3366
  const r = await api('/api/v6/memory-browser', {
2737
3367
  query: document.getElementById('mem-query').value,
2738
3368
  agent: document.getElementById('mem-agent').value,
@@ -2746,197 +3376,352 @@ async function searchMemory() {
2746
3376
  }
2747
3377
 
2748
3378
  // ──── Activity ─────────────────────────────────────────────────────────
2749
- // Live Brain
2750
- // Memory Seeder
2751
- function seedProjectPath() {
2752
- return document.getElementById('seed-project-path')?.value?.trim()
2753
- || document.getElementById('live-project-path')?.value?.trim()
2754
- || document.getElementById('ww2-project-path')?.value?.trim()
2755
- || '';
2756
- }
2757
- function copyLivePathToSeeder() {
2758
- const value = document.getElementById('live-project-path')?.value?.trim()
2759
- || document.getElementById('ww2-project-path')?.value?.trim()
2760
- || '';
2761
- if (value) document.getElementById('seed-project-path').value = value;
2762
- }
2763
- function openSeederForLiveBrain() {
2764
- const projectDir = liveProjectPath() || document.getElementById('ww2-project-path')?.value?.trim() || '';
2765
- switchTab('seeder', 'Memory Seeder');
2766
- if (projectDir) document.getElementById('seed-project-path').value = projectDir;
2767
- const topic = document.getElementById('seed-topic');
2768
- const objective = document.getElementById('seed-objective');
2769
- if (topic && !topic.value) topic.value = 'Source-backed live brain improvement';
2770
- if (objective && !objective.value) objective.value = 'Capture verified project knowledge that future agents should recall before complex work.';
2771
- refreshSeedDrafts();
2772
- }
2773
- function seedDraftStatusClass(status) {
2774
- if (status === 'seeded') return 'green';
2775
- if (status === 'approved') return 'cyan';
2776
- if (status === 'rejected') return 'red';
2777
- return 'yellow';
2778
- }
2779
- function renderSeedStatus(drafts) {
2780
- const counts = { draft: 0, approved: 0, seeded: 0, rejected: 0 };
2781
- for (const d of drafts || []) counts[d.status] = (counts[d.status] || 0) + 1;
2782
- return [
2783
- '<div class="row"><span class="k">Open drafts</span><span class="v warn">' + counts.draft + '</span></div>',
2784
- '<div class="row"><span class="k">Approved</span><span class="v good">' + counts.approved + '</span></div>',
2785
- '<div class="row"><span class="k">Seeded</span><span class="v good">' + counts.seeded + '</span></div>',
2786
- '<div class="row"><span class="k">Rejected</span><span class="v bad">' + counts.rejected + '</span></div>',
2787
- '<div class="row"><span class="k">Current project</span><span class="v mono">' + htmlEscape(seedProjectPath() || 'dashboard default') + '</span></div>',
2788
- ].join('');
2789
- }
2790
- function seedJsArg(value) {
2791
- return JSON.stringify(String(value || ''));
2792
- }
2793
- function renderSeedDrafts(drafts) {
2794
- if (!drafts || !drafts.length) return '<div class="empty-tight">No seed drafts yet.</div>';
2795
- return drafts.map(d => {
2796
- const approvedBy = new Set((d.approvals || []).map(a => a.by));
2797
- const required = d.requiredApprovals || [];
2798
- const pending = required.filter(a => !approvedBy.has(a));
2799
- const agentApprover = d.approverAgent && d.approverAgent !== 'user' ? d.approverAgent : d.researchAgent;
2800
- const warnings = (d.warnings || []).length
2801
- ? '<div class="warning-stack" style="margin-top:8px">' + d.warnings.map(w => '<div class="warning-banner"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'
2802
- : '';
2803
- const sources = (d.sources || []).slice(0, 5).map(s => '<code>' + htmlEscape(s) + '</code>').join(' ');
2804
- let actions = '';
2805
- if (d.status === 'draft') {
2806
- actions += '<button class="btn sm success" onclick="approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg('user') + ')">User Approve</button> ';
2807
- actions += '<button class="btn sm" onclick="approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg(agentApprover) + ')">Agent Approve</button> ';
2808
- actions += '<button class="btn sm danger" onclick="rejectSeedDraft(' + seedJsArg(d.id) + ')">Reject</button>';
2809
- }
2810
- if (d.status === 'approved') {
2811
- actions += '<button class="btn sm primary" onclick="commitSeedDraft(' + seedJsArg(d.id) + ')">Seed to Brain</button> ';
2812
- actions += '<button class="btn sm danger" onclick="rejectSeedDraft(' + seedJsArg(d.id) + ')">Reject</button>';
2813
- }
2814
- if (d.status === 'seeded') {
2815
- actions += '<span class="pill green">memory ' + htmlEscape((d.seededMemoryId || '').slice(0, 10)) + '</span>';
2816
- }
2817
- return '<div class="memory-item">' +
2818
- '<div class="meta">' +
2819
- '<span class="pill ' + seedDraftStatusClass(d.status) + '">' + htmlEscape(d.status) + '</span>' +
2820
- '<span class="pill purple">' + htmlEscape(d.researchAgent) + '</span>' +
2821
- '<span class="pill cyan">' + htmlEscape(d.approvalMode) + '</span>' +
2822
- '<span>' + htmlEscape(new Date(d.updatedAt || d.createdAt).toLocaleString()) + '</span>' +
2823
- '</div>' +
2824
- '<div style="display:flex;gap:12px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap">' +
2825
- '<div style="min-width:260px;flex:1">' +
2826
- '<div style="font-weight:700;margin-bottom:4px">' + htmlEscape(d.topic) + '</div>' +
2827
- '<div class="content">' + htmlEscape(d.objective || '').slice(0, 260) + '</div>' +
2828
- '<div class="mono" style="font-size:11px;color:var(--text-3);margin-top:6px">pending: ' + htmlEscape(pending.join(', ') || 'none') + ' | category: ' + htmlEscape(d.category) + '</div>' +
2829
- '<div style="font-size:11px;color:var(--text-3);margin-top:6px">' + (sources || 'No sources') + '</div>' +
2830
- '</div>' +
2831
- '<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end">' + actions + '</div>' +
2832
- '</div>' +
2833
- warnings +
2834
- '<pre class="mono" style="margin-top:10px;max-height:180px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px;white-space:pre-wrap">' + htmlEscape(d.content || '') + '</pre>' +
2835
- '</div>';
2836
- }).join('');
2837
- }
2838
- async function refreshSeedDrafts() {
2839
- const input = document.getElementById('seed-project-path');
2840
- if (input && !input.value) input.value = seedProjectPath();
2841
- const drafts = await api('/api/v6/seed-drafts?limit=50') ?? [];
2842
- const open = drafts.filter(d => d.status === 'draft' || d.status === 'approved').length;
2843
- const badge = document.getElementById('badge-seeder');
2844
- if (badge) badge.textContent = open;
2845
- const status = document.getElementById('seed-status');
2846
- if (status) status.innerHTML = renderSeedStatus(drafts);
2847
- const list = document.getElementById('seed-drafts');
2848
- if (list) list.innerHTML = renderSeedDrafts(drafts);
2849
- }
2850
- async function createSeedDraft() {
2851
- const topic = document.getElementById('seed-topic').value.trim();
2852
- if (!topic) { toast('Topic required', 'Add a seed topic before creating a draft.', 'warn'); return; }
2853
- const draft = await api('/api/v6/seed-draft', {
2854
- projectDir: seedProjectPath(),
2855
- topic,
2856
- objective: document.getElementById('seed-objective').value,
2857
- researchAgent: document.getElementById('seed-research-agent').value,
2858
- approverAgent: document.getElementById('seed-approver-agent').value,
2859
- approvalMode: document.getElementById('seed-approval-mode').value,
2860
- category: document.getElementById('seed-category').value,
2861
- tags: document.getElementById('seed-tags').value,
2862
- importance: parseFloat(document.getElementById('seed-importance').value || '0.78'),
2863
- sources: document.getElementById('seed-sources').value,
2864
- content: document.getElementById('seed-content').value,
2865
- });
2866
- if (draft) {
2867
- toast('Seed draft created', draft.status);
2868
- refreshSeedDrafts();
2869
- }
2870
- }
2871
- async function approveSeedDraft(id, approvedBy) {
2872
- const draft = await api('/api/v6/seed-approve', { id, approvedBy });
2873
- if (draft) {
2874
- toast('Approval recorded', draft.status);
2875
- refreshSeedDrafts();
2876
- }
2877
- }
2878
- async function rejectSeedDraft(id) {
2879
- const reason = prompt('Reject reason', 'Not verified enough');
2880
- if (reason === null) return;
2881
- const draft = await api('/api/v6/seed-reject', { id, reason, rejectedBy: 'user' });
2882
- if (draft) {
2883
- toast('Draft rejected', draft.topic, 'warn');
2884
- refreshSeedDrafts();
2885
- }
2886
- }
2887
- async function commitSeedDraft(id) {
2888
- const draft = await api('/api/v6/seed-commit', { id, seededBy: 'user' });
2889
- if (draft) {
2890
- toast('Seeded to brain', (draft.seededMemoryId || '').slice(0, 10));
2891
- refreshSeedDrafts();
2892
- refreshLiveBrain();
2893
- }
2894
- }
2895
-
2896
- function liveProjectPath() {
2897
- return document.getElementById('live-project-path')?.value?.trim() || '';
2898
- }
2899
- function liveActionPill(priority) {
2900
- if (priority === 'critical' || priority === 'high') return 'red';
2901
- if (priority === 'medium') return 'yellow';
2902
- return 'cyan';
2903
- }
2904
- async function refreshLiveBrain() {
2905
- const projectDir = liveProjectPath();
2906
- const queries = document.getElementById('live-queries')?.value?.trim() || '';
2907
- const url = '/api/v6/live-brain?projectDir=' + encodeURIComponent(projectDir) + (queries ? '&queries=' + encodeURIComponent(queries) : '');
2908
- const data = await api(url);
2909
- if (!data) return;
2910
-
2911
- const quality = data.memoryQuality || {};
2912
- document.getElementById('live-memories').textContent = quality.totalEntries ?? 0;
2913
- document.getElementById('live-quality').textContent = (quality.avgScore ?? 0) + '/100';
2914
- document.getElementById('live-sources').textContent = Math.round((quality.sourceCoverage ?? 0) * 100) + '%';
2915
- document.getElementById('live-recall').textContent = data.recallEval?.avgHitCount ?? 0;
2916
- document.getElementById('live-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';
2917
-
2918
- document.getElementById('live-actions').innerHTML = (data.learningActions || []).length
2919
- ? data.learningActions.map(a => \`<div class="memory-item"><div class="meta"><span class="pill \${liveActionPill(a.priority)}">\${a.priority}</span><span>\${a.title}</span></div><div class="content">\${a.reason}</div>\${a.command ? '<code>' + a.command + '</code>' : ''}</div>\`).join('')
2920
- : '<div class="empty">No immediate hygiene actions.</div>';
2921
-
2922
- document.getElementById('live-eval').innerHTML = (data.recallEval?.cases || []).map(c =>
2923
- \`<div class="row"><span class="k">\${c.query}</span><span class="v \${c.hitCount ? 'good' : 'bad'}">\${c.hitCount} hits - \${c.sourceBackedHits} sourced</span></div>\`
2924
- ).join('') || '<div class="empty">No eval cases.</div>';
2925
-
2926
- document.getElementById('live-top').innerHTML = (data.topMemories || []).slice(0, 8).map(m =>
2927
- \`<div class="memory-item"><div class="meta"><span class="pill purple">\${m.agentTool}</span><span>\${m.category}</span><span>score \${m.qualityScore}/100</span></div><div class="content">\${(m.content || '').slice(0, 260)}</div></div>\`
2928
- ).join('') || '<div class="empty">No memories.</div>';
2929
-
2930
- document.getElementById('live-brief').textContent = data.agentBriefing || '';
2931
- }
2932
-
2933
- async function refreshActivity() {
2934
- const filter = document.getElementById('act-filter').value;
2935
- const log = await api('/api/v6/activity-log?limit=200') ?? [];
2936
- const filtered = filter ? log.filter(e => e.type === filter) : log;
2937
- document.getElementById('activity-table').innerHTML = filtered.length ? filtered.map(e => \`<tr><td class="mono">\${new Date(e.timestamp).toLocaleString()}</td><td><span class="pill purple">\${e.source}</span></td><td><span class="pill \${typeToPill(e.type)}">\${e.type}</span></td><td class="mono">\${(e.detail || '').slice(0, 200)}</td></tr>\`).join('') : '<tr><td colspan="4"><div class="empty">No activity</div></td></tr>';
2938
- }
2939
- document.getElementById('act-filter').addEventListener('change', refreshActivity);
3379
+ // Live Brain
3380
+ // Memory Seeder
3381
+ function seedProjectPath() {
3382
+ return document.getElementById('seed-project-path')?.value?.trim()
3383
+ || document.getElementById('live-project-path')?.value?.trim()
3384
+ || document.getElementById('ww2-project-path')?.value?.trim()
3385
+ || '';
3386
+ }
3387
+ function copyLivePathToSeeder() {
3388
+ const value = document.getElementById('live-project-path')?.value?.trim()
3389
+ || document.getElementById('ww2-project-path')?.value?.trim()
3390
+ || '';
3391
+ if (value) document.getElementById('seed-project-path').value = value;
3392
+ }
3393
+ function openSeederForLiveBrain() {
3394
+ const projectDir = liveProjectPath() || document.getElementById('ww2-project-path')?.value?.trim() || '';
3395
+ switchTab('seeder', 'Memory Seeder');
3396
+ if (projectDir) document.getElementById('seed-project-path').value = projectDir;
3397
+ const topic = document.getElementById('seed-topic');
3398
+ const objective = document.getElementById('seed-objective');
3399
+ if (topic && !topic.value) topic.value = 'Source-backed live brain improvement';
3400
+ if (objective && !objective.value) objective.value = 'Capture verified project knowledge that future agents should recall before complex work.';
3401
+ refreshSeedDrafts();
3402
+ }
3403
+ function seedDraftStatusClass(status) {
3404
+ if (status === 'seeded') return 'green';
3405
+ if (status === 'approved') return 'cyan';
3406
+ if (status === 'rejected') return 'red';
3407
+ return 'yellow';
3408
+ }
3409
+ function renderSeedStatus(drafts) {
3410
+ const counts = { draft: 0, approved: 0, seeded: 0, rejected: 0 };
3411
+ for (const d of drafts || []) counts[d.status] = (counts[d.status] || 0) + 1;
3412
+ return [
3413
+ '<div class="row"><span class="k">Open drafts</span><span class="v warn">' + counts.draft + '</span></div>',
3414
+ '<div class="row"><span class="k">Approved</span><span class="v good">' + counts.approved + '</span></div>',
3415
+ '<div class="row"><span class="k">Seeded</span><span class="v good">' + counts.seeded + '</span></div>',
3416
+ '<div class="row"><span class="k">Rejected</span><span class="v bad">' + counts.rejected + '</span></div>',
3417
+ '<div class="row"><span class="k">Current project</span><span class="v mono">' + htmlEscape(seedProjectPath() || 'dashboard default') + '</span></div>',
3418
+ ].join('');
3419
+ }
3420
+ function seedJsArg(value) {
3421
+ return JSON.stringify(String(value || ''));
3422
+ }
3423
+ function renderSeedDrafts(drafts) {
3424
+ if (!drafts || !drafts.length) return '<div class="empty-tight">No seed drafts yet.</div>';
3425
+ return drafts.map(d => {
3426
+ const approvedBy = new Set((d.approvals || []).map(a => a.by));
3427
+ const required = d.requiredApprovals || [];
3428
+ const pending = required.filter(a => !approvedBy.has(a));
3429
+ const agentApprover = d.approverAgent && d.approverAgent !== 'user' ? d.approverAgent : d.researchAgent;
3430
+ const warnings = (d.warnings || []).length
3431
+ ? '<div class="warning-stack" style="margin-top:8px">' + d.warnings.map(w => '<div class="warning-banner"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'
3432
+ : '';
3433
+ const sources = (d.sources || []).slice(0, 5).map(s => '<code>' + htmlEscape(s) + '</code>').join(' ');
3434
+ let actions = '';
3435
+ if (d.status === 'draft') {
3436
+ actions += '<button class="btn sm success" onclick="approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg('user') + ')">User Approve</button> ';
3437
+ actions += '<button class="btn sm" onclick="approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg(agentApprover) + ')">Agent Approve</button> ';
3438
+ actions += '<button class="btn sm danger" onclick="rejectSeedDraft(' + seedJsArg(d.id) + ')">Reject</button>';
3439
+ }
3440
+ if (d.status === 'approved') {
3441
+ actions += '<button class="btn sm primary" onclick="commitSeedDraft(' + seedJsArg(d.id) + ')">Seed to Brain</button> ';
3442
+ actions += '<button class="btn sm danger" onclick="rejectSeedDraft(' + seedJsArg(d.id) + ')">Reject</button>';
3443
+ }
3444
+ if (d.status === 'seeded') {
3445
+ actions += '<span class="pill green">memory ' + htmlEscape((d.seededMemoryId || '').slice(0, 10)) + '</span> ';
3446
+ actions += '<button class="btn sm" onclick="handoffToAgent(' + seedJsArg(d.researchAgent) + ')" title="Generate a briefing to hand this knowledge back to an agent">↩ Hand to Agent</button>';
3447
+ }
3448
+ return '<div class="memory-item">' +
3449
+ '<div class="meta">' +
3450
+ '<span class="pill ' + seedDraftStatusClass(d.status) + '">' + htmlEscape(d.status) + '</span>' +
3451
+ '<span class="pill purple">' + htmlEscape(d.researchAgent) + '</span>' +
3452
+ '<span class="pill cyan">' + htmlEscape(d.approvalMode) + '</span>' +
3453
+ '<span>' + htmlEscape(new Date(d.updatedAt || d.createdAt).toLocaleString()) + '</span>' +
3454
+ '</div>' +
3455
+ '<div style="display:flex;gap:12px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap">' +
3456
+ '<div style="min-width:260px;flex:1">' +
3457
+ '<div style="font-weight:700;margin-bottom:4px">' + htmlEscape(d.topic) + '</div>' +
3458
+ '<div class="content">' + htmlEscape(d.objective || '').slice(0, 260) + '</div>' +
3459
+ '<div class="mono" style="font-size:11px;color:var(--text-3);margin-top:6px">pending: ' + htmlEscape(pending.join(', ') || 'none') + ' | category: ' + htmlEscape(d.category) + '</div>' +
3460
+ '<div style="font-size:11px;color:var(--text-3);margin-top:6px">' + (sources || 'No sources') + '</div>' +
3461
+ '</div>' +
3462
+ '<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end">' + actions + '</div>' +
3463
+ '</div>' +
3464
+ warnings +
3465
+ '<pre class="mono" style="margin-top:10px;max-height:180px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px;white-space:pre-wrap">' + htmlEscape(d.content || '') + '</pre>' +
3466
+ '</div>';
3467
+ }).join('');
3468
+ }
3469
+ async function refreshSeedDrafts() {
3470
+ const input = document.getElementById('seed-project-path');
3471
+ if (input && !input.value) input.value = seedProjectPath();
3472
+ const drafts = await api('/api/v6/seed-drafts?limit=50') ?? [];
3473
+ const open = drafts.filter(d => d.status === 'draft' || d.status === 'approved').length;
3474
+ const badge = document.getElementById('badge-seeder');
3475
+ if (badge) badge.textContent = open;
3476
+ const status = document.getElementById('seed-status');
3477
+ if (status) status.innerHTML = renderSeedStatus(drafts);
3478
+ const list = document.getElementById('seed-drafts');
3479
+ if (list) list.innerHTML = renderSeedDrafts(drafts);
3480
+ }
3481
+ async function createSeedDraft() {
3482
+ const topic = document.getElementById('seed-topic').value.trim();
3483
+ if (!topic) { toast('Topic required', 'Add a seed topic before creating a draft.', 'warn'); return; }
3484
+ const draft = await api('/api/v6/seed-draft', {
3485
+ projectDir: seedProjectPath(),
3486
+ topic,
3487
+ objective: document.getElementById('seed-objective').value,
3488
+ researchAgent: document.getElementById('seed-research-agent').value,
3489
+ approverAgent: document.getElementById('seed-approver-agent').value,
3490
+ approvalMode: document.getElementById('seed-approval-mode').value,
3491
+ category: document.getElementById('seed-category').value,
3492
+ tags: document.getElementById('seed-tags').value,
3493
+ importance: parseFloat(document.getElementById('seed-importance').value || '0.78'),
3494
+ sources: document.getElementById('seed-sources').value,
3495
+ content: document.getElementById('seed-content').value,
3496
+ });
3497
+ if (draft) {
3498
+ toast('Seed draft created', draft.status);
3499
+ refreshSeedDrafts();
3500
+ }
3501
+ }
3502
+ // One-click path: create → approve → save to brain. The "single seed".
3503
+ async function quickSeed() {
3504
+ const topic = document.getElementById('seed-topic').value.trim();
3505
+ if (!topic) { toast('Topic required', 'Add a topic before quick-saving.', 'warn'); return; }
3506
+ const draft = await api('/api/v6/seed-draft', {
3507
+ projectDir: seedProjectPath(),
3508
+ topic,
3509
+ objective: document.getElementById('seed-objective').value,
3510
+ researchAgent: document.getElementById('seed-research-agent').value,
3511
+ approverAgent: 'user',
3512
+ approvalMode: 'user',
3513
+ category: document.getElementById('seed-category').value,
3514
+ tags: document.getElementById('seed-tags').value,
3515
+ importance: parseFloat(document.getElementById('seed-importance').value || '0.78'),
3516
+ sources: document.getElementById('seed-sources').value,
3517
+ content: document.getElementById('seed-content').value,
3518
+ });
3519
+ if (!draft || !draft.id) return;
3520
+ const approved = await api('/api/v6/seed-approve', { id: draft.id, approvedBy: 'user' });
3521
+ if (!approved) { toast('Saved as draft', 'Approve it in the queue to finish.', 'warn'); refreshSeedDrafts(); return; }
3522
+ const committed = await api('/api/v6/seed-commit', { id: draft.id, seededBy: 'user' });
3523
+ if (committed) {
3524
+ toast('Saved to brain ⚡', 'Knowledge is now in memory.', 'success');
3525
+ refreshSeedDrafts();
3526
+ try { refreshLiveBrain(); } catch (e) {}
3527
+ } else {
3528
+ toast('Approved', 'Couldn\\'t finalize — finish from the queue.', 'warn');
3529
+ refreshSeedDrafts();
3530
+ }
3531
+ }
3532
+ async function approveSeedDraft(id, approvedBy) {
3533
+ const draft = await api('/api/v6/seed-approve', { id, approvedBy });
3534
+ if (draft) {
3535
+ toast('Approval recorded', draft.status);
3536
+ refreshSeedDrafts();
3537
+ }
3538
+ }
3539
+ async function rejectSeedDraft(id) {
3540
+ const reason = prompt('Reject reason', 'Not verified enough');
3541
+ if (reason === null) return;
3542
+ const draft = await api('/api/v6/seed-reject', { id, reason, rejectedBy: 'user' });
3543
+ if (draft) {
3544
+ toast('Draft rejected', draft.topic, 'warn');
3545
+ refreshSeedDrafts();
3546
+ }
3547
+ }
3548
+ async function commitSeedDraft(id) {
3549
+ const draft = await api('/api/v6/seed-commit', { id, seededBy: 'user' });
3550
+ if (draft) {
3551
+ toast('Seeded to brain', (draft.seededMemoryId || '').slice(0, 10));
3552
+ refreshSeedDrafts();
3553
+ refreshLiveBrain();
3554
+ }
3555
+ }
3556
+ // Two-way: hand the brain's knowledge back to an agent as a ready briefing.
3557
+ async function handoffToAgent(toAgent) {
3558
+ const md = await api('/api/v6/agent-handoff', { projectDir: seedProjectPath(), fromAgent: 'user', toAgent: toAgent || 'claude-code', format: 'markdown' });
3559
+ if (!md) return;
3560
+ try {
3561
+ await navigator.clipboard.writeText(typeof md === 'string' ? md : JSON.stringify(md, null, 2));
3562
+ toast('Handoff copied', 'Briefing for ' + (toAgent || 'agent') + ' is on your clipboard — paste it into the agent.', 'success');
3563
+ } catch (e) {
3564
+ toast('Handoff ready', 'Copy it from the Activity log if clipboard is blocked.', 'warn');
3565
+ }
3566
+ }
3567
+
3568
+ function liveProjectPath() {
3569
+ return document.getElementById('live-project-path')?.value?.trim() || '';
3570
+ }
3571
+ function liveActionPill(priority) {
3572
+ if (priority === 'critical' || priority === 'high') return 'red';
3573
+ if (priority === 'medium') return 'yellow';
3574
+ return 'cyan';
3575
+ }
3576
+ async function refreshLiveBrain() {
3577
+ const projectDir = liveProjectPath();
3578
+ const queries = document.getElementById('live-queries')?.value?.trim() || '';
3579
+ const url = '/api/v6/live-brain?projectDir=' + encodeURIComponent(projectDir) + (queries ? '&queries=' + encodeURIComponent(queries) : '');
3580
+ const data = await api(url);
3581
+ if (!data) return;
3582
+
3583
+ const quality = data.memoryQuality || {};
3584
+ document.getElementById('live-memories').textContent = quality.totalEntries ?? 0;
3585
+ document.getElementById('live-quality').textContent = (quality.avgScore ?? 0) + '/100';
3586
+ document.getElementById('live-sources').textContent = Math.round((quality.sourceCoverage ?? 0) * 100) + '%';
3587
+ document.getElementById('live-recall').textContent = data.recallEval?.avgHitCount ?? 0;
3588
+ document.getElementById('live-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';
3589
+
3590
+ document.getElementById('live-actions').innerHTML = (data.learningActions || []).length
3591
+ ? data.learningActions.map(a => \`<div class="memory-item"><div class="meta"><span class="pill \${liveActionPill(a.priority)}">\${a.priority}</span><span>\${a.title}</span></div><div class="content">\${a.reason}</div>\${a.command ? '<code>' + a.command + '</code>' : ''}</div>\`).join('')
3592
+ : '<div class="empty">No immediate hygiene actions.</div>';
3593
+
3594
+ document.getElementById('live-eval').innerHTML = (data.recallEval?.cases || []).map(c =>
3595
+ \`<div class="row"><span class="k">\${c.query}</span><span class="v \${c.hitCount ? 'good' : 'bad'}">\${c.hitCount} hits - \${c.sourceBackedHits} sourced</span></div>\`
3596
+ ).join('') || '<div class="empty">No eval cases.</div>';
3597
+
3598
+ document.getElementById('live-top').innerHTML = (data.topMemories || []).slice(0, 8).map(m =>
3599
+ \`<div class="memory-item"><div class="meta"><span class="pill purple">\${m.agentTool}</span><span>\${m.category}</span><span>score \${m.qualityScore}/100</span></div><div class="content">\${(m.content || '').slice(0, 260)}</div></div>\`
3600
+ ).join('') || '<div class="empty">No memories.</div>';
3601
+
3602
+ document.getElementById('live-brief').textContent = data.agentBriefing || '';
3603
+ }
3604
+
3605
+ // ──── Realtime Activity Stream (v6.1) ──────────────────────────────────
3606
+ let activityBuffer = [];
3607
+ const activitySeen = new Set();
3608
+ const activitySeenOrder = [];
3609
+ let activityFilter = '';
3610
+ let activityPaused = false;
3611
+ let activityRate = [];
3612
+ let lastIngestFresh = false;
3613
+
3614
+ function activityKey(e) { return (e.type || '') + '|' + (e.source || '') + '|' + String(e.detail || '').slice(0, 60); }
3615
+ function actEpoch(e) {
3616
+ if (typeof e.ts === 'number') return e.ts;
3617
+ if (e.timestamp) { const t = Date.parse(e.timestamp); if (!isNaN(t)) return t; }
3618
+ return Date.now();
3619
+ }
3620
+ function ingestActivity(raw, fresh) {
3621
+ const e = { ts: actEpoch(raw), type: raw.type || 'event', source: raw.source || 'brain', detail: String(raw.detail || '') };
3622
+ const key = activityKey(e);
3623
+ if (activitySeen.has(key)) return false;
3624
+ activitySeen.add(key); activitySeenOrder.push(key);
3625
+ if (activitySeenOrder.length > 1200) { const drop = activitySeenOrder.splice(0, activitySeenOrder.length - 1200); for (const k of drop) activitySeen.delete(k); }
3626
+ activityBuffer.unshift(e);
3627
+ if (activityBuffer.length > 500) activityBuffer.length = 500;
3628
+ activityRate.push(e.ts);
3629
+ const cutoff = Date.now() - 30 * 60 * 1000;
3630
+ while (activityRate.length && activityRate[0] < cutoff) activityRate.shift();
3631
+ if (!activityPaused) {
3632
+ appendLog(e.source, e.type, e.detail);
3633
+ if (fresh) {
3634
+ try {
3635
+ if (typeof graphTimer !== 'undefined' && graphTimer) {
3636
+ const col = signalColors[e.type] || '#18ffff';
3637
+ const srcExists = graphNodes.find(n => n.id === e.source);
3638
+ graphSignals.push({ fromId: srcExists ? e.source : 'brain', toId: 'brain', type: e.type, color: col, progress: 0, spawnedAt: Date.now() });
3639
+ if (graphSignals.length > 100) graphSignals = graphSignals.slice(-100);
3640
+ }
3641
+ } catch (err) {}
3642
+ lastIngestFresh = true;
3643
+ }
3644
+ }
3645
+ return true;
3646
+ }
3647
+ function typeMatchesFilter(type, filter) {
3648
+ if (!filter) return true;
3649
+ if (filter === 'seed-draft') return type === 'seed-draft' || type === 'seed-commit';
3650
+ return type === filter;
3651
+ }
3652
+ function renderActivityTable() {
3653
+ const tbody = document.getElementById('activity-table');
3654
+ if (!tbody) return;
3655
+ const searchEl = document.getElementById('act-search');
3656
+ const q = (searchEl ? searchEl.value : '').toLowerCase().trim();
3657
+ let rows = activityBuffer.filter(e => typeMatchesFilter(e.type, activityFilter));
3658
+ if (q) rows = rows.filter(e => (e.source + ' ' + e.type + ' ' + e.detail).toLowerCase().includes(q));
3659
+ const fresh = lastIngestFresh; lastIngestFresh = false;
3660
+ rows = rows.slice(0, 200);
3661
+ tbody.innerHTML = rows.length
3662
+ ? rows.map((e, i) => '<tr' + (i === 0 && fresh ? ' class="act-fresh"' : '') + '><td class="mono">' + new Date(e.ts).toLocaleTimeString() + '</td><td><span class="pill purple">' + htmlEscape(e.source) + '</span></td><td><span class="pill ' + typeToPill(e.type) + '">' + htmlEscape(e.type) + '</span></td><td class="mono">' + htmlEscape(e.detail.slice(0, 200)) + '</td></tr>').join('')
3663
+ : '<tr><td colspan="4"><div class="empty">No activity</div></td></tr>';
3664
+ const total = document.getElementById('act-stat-total'); if (total) total.textContent = activityBuffer.length;
3665
+ const last = document.getElementById('act-stat-last'); if (last) last.textContent = activityBuffer[0] ? new Date(activityBuffer[0].ts).toLocaleTimeString() : '—';
3666
+ }
3667
+ function eventsPerMin() { const c = Date.now() - 60000; return activityRate.filter(t => t >= c).length; }
3668
+ function renderSparkline() {
3669
+ const rate = eventsPerMin();
3670
+ const ratePill = document.getElementById('act-rate-pill'); if (ratePill) ratePill.textContent = rate + ' / min';
3671
+ const ovRate = document.getElementById('ov-pulse-rate'); if (ovRate) ovRate.textContent = rate + ' / min';
3672
+ const cv = document.getElementById('act-spark');
3673
+ if (!cv || !cv.getContext) return;
3674
+ const now = Date.now();
3675
+ const buckets = new Array(10).fill(0);
3676
+ for (const t of activityRate) { const age = now - t; if (age < 0 || age > 10 * 60000) continue; const idx = 9 - Math.floor(age / 60000); if (idx >= 0 && idx < 10) buckets[idx]++; }
3677
+ const pw = (cv.parentElement ? cv.parentElement.clientWidth : 0) || 600;
3678
+ cv.width = pw; cv.height = 120;
3679
+ const ctx = cv.getContext('2d'); const W = cv.width, H = cv.height; ctx.clearRect(0, 0, W, H);
3680
+ let max = 1; for (const b of buckets) if (b > max) max = b;
3681
+ const bw = W / 10;
3682
+ for (let i = 0; i < 10; i++) {
3683
+ const h = (buckets[i] / max) * (H - 12);
3684
+ const x = i * bw + 3, y = H - h;
3685
+ ctx.fillStyle = i === 9 ? '#18ffff' : 'rgba(168,85,247,0.55)';
3686
+ ctx.fillRect(x, y, bw - 6, Math.max(0, h));
3687
+ }
3688
+ }
3689
+ function renderLivePulse() {
3690
+ const el = document.getElementById('ov-pulse');
3691
+ if (!el) return;
3692
+ const items = activityBuffer.slice(0, 8);
3693
+ el.innerHTML = items.length
3694
+ ? items.map(e => '<div class="memory-item"><div class="meta"><span class="pill ' + typeToPill(e.type) + '">' + htmlEscape(e.type) + '</span><span class="pill purple">' + htmlEscape(e.source) + '</span><span>' + new Date(e.ts).toLocaleTimeString() + '</span></div><div class="content">' + htmlEscape(e.detail.slice(0, 200)) + '</div></div>').join('')
3695
+ : '<div class="empty-tight">Awaiting live activity…</div>';
3696
+ }
3697
+ function setActivityFilter(btn, type) {
3698
+ activityFilter = type;
3699
+ const chips = document.querySelectorAll('#act-chips .chip');
3700
+ chips.forEach(c => c.classList.toggle('active', c === btn));
3701
+ renderActivityTable();
3702
+ }
3703
+ function toggleActivityPause() {
3704
+ activityPaused = !activityPaused;
3705
+ const btn = document.getElementById('act-pause-btn'); if (btn) btn.textContent = activityPaused ? 'Resume' : 'Pause';
3706
+ const badge = document.getElementById('act-live-badge'); if (badge) { badge.classList.toggle('paused', activityPaused); badge.textContent = activityPaused ? 'PAUSED' : 'LIVE'; }
3707
+ const live = document.getElementById('act-stat-live'); if (live) { live.textContent = activityPaused ? 'paused' : 'streaming'; live.className = 'v ' + (activityPaused ? 'warn' : 'good'); }
3708
+ if (!activityPaused) renderActivityTable();
3709
+ }
3710
+ function clearActivity() {
3711
+ activityBuffer = []; activitySeen.clear(); activitySeenOrder.length = 0; activityRate = [];
3712
+ renderActivityTable(); renderSparkline(); renderLivePulse();
3713
+ }
3714
+ function setConn(online) {
3715
+ const d = document.getElementById('conn-dot'); if (d) { d.classList.toggle('offline', !online); d.title = 'Realtime connection: ' + (online ? 'live' : 'reconnecting'); }
3716
+ if (activityPaused) return;
3717
+ const badge = document.getElementById('act-live-badge'); if (badge) { badge.classList.toggle('paused', !online); badge.textContent = online ? 'LIVE' : 'OFFLINE'; }
3718
+ const live = document.getElementById('act-stat-live'); if (live) { live.textContent = online ? 'streaming' : 'reconnecting'; live.className = 'v ' + (online ? 'good' : 'bad'); }
3719
+ }
3720
+ async function refreshActivity() {
3721
+ const log = await api('/api/v6/activity-log?limit=200');
3722
+ if (Array.isArray(log)) for (let i = log.length - 1; i >= 0; i--) ingestActivity(log[i], false);
3723
+ renderActivityTable(); renderSparkline();
3724
+ }
2940
3725
 
2941
3726
  // ──── Live Graph ───────────────────────────────────────────────────────
2942
3727
  let graphFrame = 0, graphNodes = [], graphEdges = [], graphSignals = [], graphTimer = null, graphPullTimer = null;
@@ -2980,64 +3765,449 @@ function graphLoop() {
2980
3765
  const c = document.getElementById('graph-canvas');
2981
3766
  if (!c) return;
2982
3767
  const ctx = c.getContext('2d'), W = c.width, H = c.height;
3768
+ const now = Date.now();
2983
3769
  ctx.clearRect(0, 0, W, H);
2984
- const grd = ctx.createRadialGradient(W/2, H/2, 40, W/2, H/2, 500);
2985
- grd.addColorStop(0, 'rgba(168,85,247,0.08)'); grd.addColorStop(1, 'rgba(0,0,0,0)');
3770
+ // Ambient backdrop with a soft vignette.
3771
+ const grd = ctx.createRadialGradient(W/2, H/2, 40, W/2, H/2, Math.max(W,H)/1.4);
3772
+ grd.addColorStop(0, 'rgba(168,85,247,0.10)'); grd.addColorStop(0.5, 'rgba(24,255,255,0.03)'); grd.addColorStop(1, 'rgba(0,0,0,0)');
2986
3773
  ctx.fillStyle = grd; ctx.fillRect(0, 0, W, H);
3774
+
2987
3775
  for (const n of graphNodes) if (n.targetX != null) { n.x += (n.targetX - n.x) * 0.08; n.y += (n.targetY - n.y) * 0.08; }
2988
- ctx.lineWidth = 1;
3776
+
3777
+ // ── Edges: curved, with a gentle animated dash flow on active links ──────
2989
3778
  for (const e of graphEdges) {
2990
3779
  const a = graphNodes.find(n => n.id === e.from), b = graphNodes.find(n => n.id === e.to);
2991
3780
  if (!a || !b) continue;
2992
- ctx.strokeStyle = e.active ? 'rgba(24,255,255,0.3)' : 'rgba(86,96,128,0.15)';
2993
- ctx.setLineDash(e.active ? [] : [4, 4]);
2994
- ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
3781
+ const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2;
3782
+ // Curve control point nudged perpendicular for an organic arc.
3783
+ const dx = b.x - a.x, dy = b.y - a.y, len = Math.sqrt(dx*dx + dy*dy) || 1;
3784
+ const cxp = mx - (dy / len) * 26, cyp = my + (dx / len) * 26;
3785
+ if (e.active) {
3786
+ const g = ctx.createLinearGradient(a.x, a.y, b.x, b.y);
3787
+ g.addColorStop(0, 'rgba(24,255,255,0.05)'); g.addColorStop(0.5, 'rgba(24,255,255,0.34)'); g.addColorStop(1, 'rgba(168,85,247,0.18)');
3788
+ ctx.strokeStyle = g; ctx.lineWidth = 1.6;
3789
+ ctx.setLineDash([5, 7]); ctx.lineDashOffset = -(now / 45) % 12;
3790
+ } else {
3791
+ ctx.strokeStyle = 'rgba(96,108,140,0.14)'; ctx.lineWidth = 1; ctx.setLineDash([3, 5]); ctx.lineDashOffset = 0;
3792
+ }
3793
+ ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.quadraticCurveTo(cxp, cyp, b.x, b.y); ctx.stroke();
2995
3794
  }
2996
- ctx.setLineDash([]);
2997
- const now = Date.now();
3795
+ ctx.setLineDash([]); ctx.lineDashOffset = 0;
3796
+
3797
+ // ── Signals: glowing head + fading motion trail ──────────────────────────
2998
3798
  graphSignals = graphSignals.filter(s => now - s.spawnedAt < 3500);
2999
3799
  for (const s of graphSignals) {
3000
3800
  const from = graphNodes.find(n => n.id === s.fromId), to = graphNodes.find(n => n.id === s.toId);
3001
3801
  if (!from || !to) continue;
3002
3802
  s.progress = Math.min(1, (now - s.spawnedAt) / 2400);
3003
- const t = s.progress, x = from.x + (to.x - from.x) * t, y = from.y + (to.y - from.y) * t;
3004
- const tail = ctx.createRadialGradient(x, y, 0, x, y, 18);
3803
+ const t = s.progress;
3804
+ const ease = t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t+2, 2)/2;
3805
+ // Trail: several fading dots behind the head.
3806
+ for (let k = 0; k < 5; k++) {
3807
+ const tt = Math.max(0, ease - k * 0.045);
3808
+ const x = from.x + (to.x - from.x) * tt, y = from.y + (to.y - from.y) * tt;
3809
+ const alpha = (1 - k / 5) * (1 - Math.abs(t - 0.5)) * 0.9;
3810
+ ctx.globalAlpha = Math.max(0, alpha);
3811
+ ctx.fillStyle = s.color;
3812
+ ctx.beginPath(); ctx.arc(x, y, 3.2 - k * 0.4, 0, Math.PI * 2); ctx.fill();
3813
+ }
3814
+ ctx.globalAlpha = 1;
3815
+ const hx = from.x + (to.x - from.x) * ease, hy = from.y + (to.y - from.y) * ease;
3816
+ const tail = ctx.createRadialGradient(hx, hy, 0, hx, hy, 20);
3005
3817
  tail.addColorStop(0, s.color); tail.addColorStop(1, 'rgba(0,0,0,0)');
3006
- ctx.fillStyle = tail; ctx.beginPath(); ctx.arc(x, y, 16, 0, Math.PI * 2); ctx.fill();
3007
- ctx.fillStyle = s.color;
3008
- ctx.beginPath(); ctx.arc(x, y, 3.5, 0, Math.PI * 2); ctx.fill();
3818
+ ctx.fillStyle = tail; ctx.beginPath(); ctx.arc(hx, hy, 18, 0, Math.PI * 2); ctx.fill();
3819
+ ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(hx, hy, 2.2, 0, Math.PI * 2); ctx.fill();
3009
3820
  }
3821
+
3822
+ // ── Nodes: layered glow, ring, core, label chip ──────────────────────────
3010
3823
  for (const n of graphNodes) {
3011
- const pulse = n.type === 'brain' ? Math.sin(graphFrame / 15) * 4 : 0, r = n.r + pulse;
3012
- const halo = ctx.createRadialGradient(n.x, n.y, r * 0.5, n.x, n.y, r * 2.5);
3013
- halo.addColorStop(0, n.color + '66'); halo.addColorStop(1, 'rgba(0,0,0,0)');
3014
- ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(n.x, n.y, r * 2.5, 0, Math.PI * 2); ctx.fill();
3015
- ctx.fillStyle = n.type === 'brain' ? '#12152a' : '#0b0f1c';
3016
- ctx.strokeStyle = n.color; ctx.lineWidth = 2;
3017
- ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
3824
+ const isBrain = n.type === 'brain';
3825
+ const pulse = isBrain ? Math.sin(graphFrame / 15) * 4 : Math.sin((graphFrame + (n.x|0)) / 22) * 1.5;
3826
+ const r = n.r + pulse;
3827
+ const halo = ctx.createRadialGradient(n.x, n.y, r * 0.4, n.x, n.y, r * 2.8);
3828
+ halo.addColorStop(0, n.color + '77'); halo.addColorStop(0.6, n.color + '1a'); halo.addColorStop(1, 'rgba(0,0,0,0)');
3829
+ ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(n.x, n.y, r * 2.8, 0, Math.PI * 2); ctx.fill();
3830
+ // Rotating accent ring on the brain hub.
3831
+ if (isBrain) {
3832
+ ctx.save(); ctx.translate(n.x, n.y); ctx.rotate((now / 1600) % (Math.PI * 2));
3833
+ ctx.strokeStyle = n.color + '88'; ctx.lineWidth = 1.5; ctx.setLineDash([3, 9]);
3834
+ ctx.beginPath(); ctx.arc(0, 0, r + 10, 0, Math.PI * 2); ctx.stroke();
3835
+ ctx.restore(); ctx.setLineDash([]);
3836
+ }
3837
+ const fill = ctx.createRadialGradient(n.x - r*0.3, n.y - r*0.3, r*0.2, n.x, n.y, r);
3838
+ fill.addColorStop(0, isBrain ? '#1b2142' : '#121a30'); fill.addColorStop(1, isBrain ? '#0a0e1f' : '#080c16');
3839
+ ctx.fillStyle = fill;
3840
+ ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); ctx.fill();
3841
+ ctx.strokeStyle = n.color; ctx.lineWidth = isBrain ? 2.5 : 1.8; ctx.stroke();
3842
+ ctx.fillStyle = n.color; ctx.beginPath(); ctx.arc(n.x, n.y, Math.max(2, r * 0.34), 0, Math.PI * 2); ctx.fill();
3843
+ // Label
3018
3844
  ctx.fillStyle = n.color;
3019
- ctx.font = n.type === 'brain' ? 'bold 13px Inter, sans-serif' : '11px Inter, sans-serif';
3845
+ ctx.font = isBrain ? '800 13px Inter, sans-serif' : '600 11px Inter, sans-serif';
3020
3846
  ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
3021
- ctx.fillText(n.label, n.x, n.y + (n.type === 'brain' ? 0 : r + 14));
3847
+ ctx.fillText(n.label, n.x, isBrain ? n.y : n.y + r + 13);
3022
3848
  }
3023
3849
  graphFrame++;
3024
3850
  graphTimer = requestAnimationFrame(graphLoop);
3025
3851
  }
3026
3852
 
3853
+ // ──── Memory Constellation (Intelligence hub) ──────────────────────────
3854
+ let constNodes = [], constEdges = [], constStats = null, constIdMap = new Map();
3855
+ let constView = { zoom: 1, panX: 0, panY: 0 };
3856
+ let constSim = { running: false, alpha: 1, raf: null };
3857
+ let constFilter = '', constFocus = null, constHover = null;
3858
+ let constDragNode = null, constPanning = false, constLoaded = false, constMoved = false;
3859
+ let constLast = { x: 0, y: 0 };
3860
+ let constLiveTimer = null;
3861
+ const NODE_COLORS = { memory: '#18ffff', project: '#10e68c', agent: '#ffb53f', tag: '#a855f7' };
3862
+
3863
+ async function refreshIntelligence() {
3864
+ bindConstellation();
3865
+ refreshIntelAnalytics();
3866
+ if (!constLoaded) { await loadConstellation(); }
3867
+ else { sizeConstCanvas(); startConstSim(); }
3868
+ }
3869
+
3870
+ async function loadConstellation() {
3871
+ const snap = await api('/api/v6/memory-graph?limit=400&maxNodes=180');
3872
+ applyConstellation(snap);
3873
+ constLoaded = true;
3874
+ }
3875
+
3876
+ function applyConstellation(snap) {
3877
+ const cv = document.getElementById('const-canvas');
3878
+ const empty = document.getElementById('const-empty');
3879
+ constNodes = []; constEdges = []; constIdMap = new Map();
3880
+ const nodes = (snap && snap.nodes) || [];
3881
+ constStats = (snap && snap.stats) || null;
3882
+ if (empty) empty.classList.toggle('show', nodes.length === 0);
3883
+ const badge = document.getElementById('badge-intelligence');
3884
+ if (badge) badge.textContent = constStats ? constStats.memories : 0;
3885
+ if (!nodes.length) { drawConstellation(); return; }
3886
+ sizeConstCanvas();
3887
+ const W = cv ? cv.width : 800, H = cv ? cv.height : 600, cx = W / 2, cy = H / 2;
3888
+ for (let i = 0; i < nodes.length; i++) {
3889
+ const n = nodes[i];
3890
+ const ang = (i / nodes.length) * Math.PI * 2;
3891
+ const rad = 60 + Math.random() * Math.min(W, H) * 0.32;
3892
+ constNodes.push({
3893
+ id: n.id, label: n.label || '', type: n.type, score: n.score || 0,
3894
+ size: n.size || 9, degree: n.degree || 0, meta: n.meta || {},
3895
+ x: cx + Math.cos(ang) * rad, y: cy + Math.sin(ang) * rad, vx: 0, vy: 0,
3896
+ });
3897
+ }
3898
+ for (const node of constNodes) constIdMap.set(node.id, node);
3899
+ for (const e of (snap.edges || [])) {
3900
+ const sn = constIdMap.get(e.source), tn = constIdMap.get(e.target);
3901
+ if (sn && tn) constEdges.push({ sn: sn, tn: tn, w: e.weight || 1, type: e.type });
3902
+ }
3903
+ const meta = document.getElementById('const-meta');
3904
+ if (meta && constStats) meta.textContent = constStats.memories + ' mem · ' + constStats.projects + ' proj · ' + constStats.agents + ' agent · ' + constStats.tags + ' tag · ' + constStats.edges + ' links' + (constStats.droppedNodes ? ' (+' + constStats.droppedNodes + ' hidden)' : '');
3905
+ updateConstHero();
3906
+ constView = { zoom: 1, panX: 0, panY: 0 };
3907
+ constSim.alpha = 1;
3908
+ startConstSim();
3909
+ }
3910
+
3911
+ function sizeConstCanvas() {
3912
+ const cv = document.getElementById('const-canvas'), wrap = document.getElementById('const-wrap');
3913
+ if (!cv || !wrap) return;
3914
+ cv.width = wrap.clientWidth || 800;
3915
+ cv.height = wrap.clientHeight || 600;
3916
+ }
3917
+
3918
+ function startConstSim() {
3919
+ if (constSim.running || !constNodes.length) return;
3920
+ constSim.running = true;
3921
+ constSim.alpha = Math.max(constSim.alpha, 0.5);
3922
+ constLoop();
3923
+ }
3924
+
3925
+ function constLoop() {
3926
+ const sec = document.getElementById('tab-intelligence');
3927
+ if (!sec || !sec.classList.contains('active')) { constSim.running = false; constSim.raf = null; return; }
3928
+ stepConstPhysics();
3929
+ drawConstellation();
3930
+ constSim.raf = requestAnimationFrame(constLoop);
3931
+ }
3932
+
3933
+ function stepConstPhysics() {
3934
+ const cv = document.getElementById('const-canvas'); if (!cv) return;
3935
+ const W = cv.width, H = cv.height, cx = W / 2, cy = H / 2;
3936
+ const a = constSim.alpha;
3937
+ const N = constNodes.length;
3938
+ if (a > 0.01) {
3939
+ for (let i = 0; i < N; i++) {
3940
+ const ni = constNodes[i];
3941
+ for (let j = i + 1; j < N; j++) {
3942
+ const nj = constNodes[j];
3943
+ let dx = ni.x - nj.x, dy = ni.y - nj.y;
3944
+ let d2 = dx * dx + dy * dy; if (d2 < 1) d2 = 1;
3945
+ const d = Math.sqrt(d2);
3946
+ let rep = (3600 * a) / d2;
3947
+ // Hard separation: keep node circles from visually overlapping.
3948
+ const minD = ni.size + nj.size + 12;
3949
+ if (d < minD) rep += (minD - d) * 0.28 * a;
3950
+ const ux = dx / d, uy = dy / d;
3951
+ ni.vx += ux * rep; ni.vy += uy * rep;
3952
+ nj.vx -= ux * rep; nj.vy -= uy * rep;
3953
+ }
3954
+ }
3955
+ for (const e of constEdges) {
3956
+ let dx = e.tn.x - e.sn.x, dy = e.tn.y - e.sn.y;
3957
+ let d = Math.sqrt(dx * dx + dy * dy) || 1;
3958
+ const ideal = 94;
3959
+ const f = ((d - ideal) * 0.015 * a) * Math.min(2, e.w);
3960
+ const ux = dx / d, uy = dy / d;
3961
+ e.sn.vx += ux * f; e.sn.vy += uy * f;
3962
+ e.tn.vx -= ux * f; e.tn.vy -= uy * f;
3963
+ }
3964
+ for (const n of constNodes) {
3965
+ if (n === constDragNode) continue;
3966
+ n.vx += (cx - n.x) * 0.010 * a;
3967
+ n.vy += (cy - n.y) * 0.010 * a;
3968
+ n.vx *= 0.86; n.vy *= 0.86;
3969
+ const sp = Math.sqrt(n.vx * n.vx + n.vy * n.vy);
3970
+ if (sp > 20) { n.vx = n.vx / sp * 20; n.vy = n.vy / sp * 20; }
3971
+ n.x += n.vx; n.y += n.vy;
3972
+ }
3973
+ constSim.alpha *= 0.99;
3974
+ if (constSim.alpha < 0.01) constSim.alpha = 0.01;
3975
+ }
3976
+ }
3977
+
3978
+ function constNodeOpacity(n) {
3979
+ if (constFilter && n.type !== constFilter) return 0.12;
3980
+ if (constFocus) {
3981
+ if (n.id === constFocus) return 1;
3982
+ const f = constIdMap.get(constFocus);
3983
+ if (f && f._neighbors && f._neighbors.has(n.id)) return 0.95;
3984
+ return 0.14;
3985
+ }
3986
+ return 1;
3987
+ }
3988
+
3989
+ function drawConstellation() {
3990
+ const cv = document.getElementById('const-canvas'); if (!cv || !cv.getContext) return;
3991
+ const ctx = cv.getContext('2d'), W = cv.width, H = cv.height;
3992
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
3993
+ ctx.clearRect(0, 0, W, H);
3994
+ ctx.translate(constView.panX, constView.panY);
3995
+ ctx.scale(constView.zoom, constView.zoom);
3996
+
3997
+ for (const e of constEdges) {
3998
+ const oa = Math.min(constNodeOpacity(e.sn), constNodeOpacity(e.tn));
3999
+ const lit = constFocus && (e.sn.id === constFocus || e.tn.id === constFocus);
4000
+ ctx.strokeStyle = lit ? 'rgba(24,255,255,' + (0.55 * oa) + ')' : 'rgba(120,140,180,' + (0.16 * oa) + ')';
4001
+ ctx.lineWidth = lit ? 1.6 : 0.7;
4002
+ ctx.beginPath(); ctx.moveTo(e.sn.x, e.sn.y); ctx.lineTo(e.tn.x, e.tn.y); ctx.stroke();
4003
+ }
4004
+ for (const n of constNodes) {
4005
+ const op = constNodeOpacity(n);
4006
+ const col = NODE_COLORS[n.type] || '#9aa7c0';
4007
+ const r = n.size;
4008
+ const halo = ctx.createRadialGradient(n.x, n.y, r * 0.4, n.x, n.y, r * 2.4);
4009
+ halo.addColorStop(0, col + Math.round(op * 90).toString(16).padStart(2, '0'));
4010
+ halo.addColorStop(1, 'rgba(0,0,0,0)');
4011
+ ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(n.x, n.y, r * 2.4, 0, Math.PI * 2); ctx.fill();
4012
+ ctx.globalAlpha = op;
4013
+ ctx.fillStyle = '#0b1020'; ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); ctx.fill();
4014
+ ctx.strokeStyle = col; ctx.lineWidth = (n.id === constFocus || n.id === constHover) ? 3 : 1.6;
4015
+ ctx.stroke();
4016
+ ctx.fillStyle = col; ctx.beginPath(); ctx.arc(n.x, n.y, Math.max(2, r * 0.42), 0, Math.PI * 2); ctx.fill();
4017
+ const showLabel = r > 15 || n.id === constHover || n.id === constFocus || n.type !== 'memory';
4018
+ if (showLabel && op > 0.4) {
4019
+ ctx.globalAlpha = op;
4020
+ ctx.fillStyle = document.documentElement.dataset.theme === 'light' ? '#1a2030' : '#e8ecf4';
4021
+ ctx.font = (n.id === constFocus ? 'bold ' : '') + Math.max(10, Math.min(14, 9 + r * 0.18)) + 'px Inter, sans-serif';
4022
+ ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
4023
+ const lbl = n.label.length > 26 ? n.label.slice(0, 25) + '…' : n.label;
4024
+ ctx.fillText(lbl, n.x, n.y + r + 9);
4025
+ }
4026
+ ctx.globalAlpha = 1;
4027
+ }
4028
+ }
4029
+
4030
+ function constMouseWorld(e) {
4031
+ const cv = document.getElementById('const-canvas'); const rect = cv.getBoundingClientRect();
4032
+ const sx = (e.clientX - rect.left) * (cv.width / rect.width);
4033
+ const sy = (e.clientY - rect.top) * (cv.height / rect.height);
4034
+ return { sx: sx, sy: sy, wx: (sx - constView.panX) / constView.zoom, wy: (sy - constView.panY) / constView.zoom };
4035
+ }
4036
+ function constHitTest(wx, wy) {
4037
+ for (let i = constNodes.length - 1; i >= 0; i--) {
4038
+ const n = constNodes[i];
4039
+ const dx = wx - n.x, dy = wy - n.y;
4040
+ if (dx * dx + dy * dy <= (n.size + 4) * (n.size + 4)) return n;
4041
+ }
4042
+ return null;
4043
+ }
4044
+ function computeNeighbors(id) {
4045
+ const n = constIdMap.get(id); if (!n) return;
4046
+ if (n._neighbors) return;
4047
+ const set = new Set();
4048
+ for (const e of constEdges) { if (e.sn.id === id) set.add(e.tn.id); if (e.tn.id === id) set.add(e.sn.id); }
4049
+ n._neighbors = set;
4050
+ }
4051
+ function openConstDrawer(n) {
4052
+ const drawer = document.getElementById('const-drawer'), body = document.getElementById('const-drawer-body');
4053
+ if (!drawer || !body) return;
4054
+ const m = n.meta || {};
4055
+ let h = '<div class="cd-type" style="color:' + (NODE_COLORS[n.type] || '#9aa7c0') + '">' + n.type + '</div>';
4056
+ h += '<div class="cd-title">' + htmlEscape(n.label) + '</div>';
4057
+ h += '<div class="row"><span class="k">Importance</span><span class="v">' + (n.score * 100).toFixed(0) + '/100</span></div>';
4058
+ h += '<div class="row"><span class="k">Connections</span><span class="v">' + n.degree + '</span></div>';
4059
+ if (n.type === 'memory') {
4060
+ if (m.category) h += '<div class="row"><span class="k">Category</span><span class="v">' + htmlEscape(String(m.category)) + '</span></div>';
4061
+ if (m.agentTool) h += '<div class="row"><span class="k">Agent</span><span class="v">' + htmlEscape(String(m.agentTool)) + '</span></div>';
4062
+ if (m.projectName) h += '<div class="row"><span class="k">Project</span><span class="v">' + htmlEscape(String(m.projectName)) + '</span></div>';
4063
+ if (m.content) h += '<div class="cd-section"><div class="cd-label">Content</div><div class="cd-content">' + htmlEscape(String(m.content)) + '</div></div>';
4064
+ const tags = Array.isArray(m.tags) ? m.tags : [];
4065
+ if (tags.length) h += '<div class="cd-section"><div class="cd-label">Tags</div><div class="tag-cloud">' + tags.map(function (t) { return '<span class="tc">#' + htmlEscape(String(t)) + '</span>'; }).join('') + '</div></div>';
4066
+ const src = Array.isArray(m.sources) ? m.sources : [];
4067
+ if (src.length) h += '<div class="cd-section"><div class="cd-label">Sources</div><div class="cd-content mono">' + src.map(function (s) { return htmlEscape(String(s)); }).join('<br>') + '</div></div>';
4068
+ } else {
4069
+ h += '<div class="cd-section"><div class="cd-content">This ' + n.type + ' node links ' + n.degree + ' memories in the constellation. Click connected nodes to explore.</div></div>';
4070
+ }
4071
+ body.innerHTML = h;
4072
+ drawer.classList.add('open');
4073
+ }
4074
+ function closeConstDrawer() {
4075
+ const drawer = document.getElementById('const-drawer'); if (drawer) drawer.classList.remove('open');
4076
+ constFocus = null;
4077
+ }
4078
+
4079
+ function bindConstellation() {
4080
+ const wrap = document.getElementById('const-wrap'); if (!wrap || wrap._bound) return;
4081
+ wrap._bound = true;
4082
+ const cv = document.getElementById('const-canvas');
4083
+ wrap.addEventListener('wheel', function (e) {
4084
+ e.preventDefault();
4085
+ const p = constMouseWorld(e);
4086
+ const factor = e.deltaY < 0 ? 1.12 : 0.89;
4087
+ const nz = Math.max(0.25, Math.min(4, constView.zoom * factor));
4088
+ constView.panX = p.sx - p.wx * nz;
4089
+ constView.panY = p.sy - p.wy * nz;
4090
+ constView.zoom = nz;
4091
+ if (!constSim.running) drawConstellation();
4092
+ }, { passive: false });
4093
+ wrap.addEventListener('mousedown', function (e) {
4094
+ const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);
4095
+ constMoved = false; constLast = { x: e.clientX, y: e.clientY };
4096
+ if (hit) { constDragNode = hit; constSim.alpha = Math.max(constSim.alpha, 0.4); startConstSim(); }
4097
+ else { constPanning = true; wrap.classList.add('dragging'); }
4098
+ });
4099
+ window.addEventListener('mousemove', function (e) {
4100
+ if (constDragNode) {
4101
+ const p = constMouseWorld(e); constDragNode.x = p.wx; constDragNode.y = p.wy; constDragNode.vx = 0; constDragNode.vy = 0; constMoved = true;
4102
+ return;
4103
+ }
4104
+ if (constPanning) {
4105
+ constView.panX += e.clientX - constLast.x; constView.panY += e.clientY - constLast.y;
4106
+ constLast = { x: e.clientX, y: e.clientY }; constMoved = true;
4107
+ if (!constSim.running) drawConstellation();
4108
+ return;
4109
+ }
4110
+ const sec = document.getElementById('tab-intelligence');
4111
+ if (!sec || !sec.classList.contains('active')) return;
4112
+ const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);
4113
+ const tip = document.getElementById('const-tip');
4114
+ constHover = hit ? hit.id : null;
4115
+ if (hit && tip) {
4116
+ tip.innerHTML = '<b style="color:' + (NODE_COLORS[hit.type] || '#fff') + '">' + htmlEscape(hit.label) + '</b><br>' + hit.type + ' · ' + (hit.score * 100).toFixed(0) + '/100 · ' + hit.degree + ' links';
4117
+ const rect = cv.getBoundingClientRect();
4118
+ tip.style.left = (e.clientX - rect.left) + 'px'; tip.style.top = (e.clientY - rect.top) + 'px';
4119
+ tip.classList.add('show');
4120
+ } else if (tip) tip.classList.remove('show');
4121
+ });
4122
+ window.addEventListener('mouseup', function (e) {
4123
+ if (constDragNode && !constMoved) { constFocus = constDragNode.id; computeNeighbors(constFocus); openConstDrawer(constDragNode); }
4124
+ else if (constPanning && !constMoved) {
4125
+ const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);
4126
+ if (!hit) { constFocus = null; closeConstDrawer(); }
4127
+ }
4128
+ constDragNode = null; constPanning = false; wrap.classList.remove('dragging');
4129
+ });
4130
+ }
4131
+ function setConstFilter(btn, type) {
4132
+ constFilter = type;
4133
+ const chips = document.querySelectorAll('#const-chips .chip');
4134
+ chips.forEach(function (c) { c.classList.toggle('active', c === btn); });
4135
+ if (!constSim.running) drawConstellation();
4136
+ }
4137
+ function highlightConstSearch() {
4138
+ const q = (document.getElementById('const-search').value || '').toLowerCase().trim();
4139
+ if (!q) { constFocus = null; if (!constSim.running) drawConstellation(); return; }
4140
+ const hit = constNodes.find(function (n) { return n.label.toLowerCase().includes(q); });
4141
+ if (hit) { constFocus = hit.id; computeNeighbors(hit.id); openConstDrawer(hit); }
4142
+ if (!constSim.running) drawConstellation();
4143
+ }
4144
+ function rebuildConstellation() { constLoaded = false; toast('Rebuilding', 'Recomputing the memory constellation…', 'info'); loadConstellation(); }
4145
+ function resetConstellationView() { constView = { zoom: 1, panX: 0, panY: 0 }; constFocus = null; closeConstDrawer(); constSim.alpha = 0.6; startConstSim(); }
4146
+ function constLiveBump() {
4147
+ if (!constLoaded) return;
4148
+ constSim.alpha = Math.max(constSim.alpha, 0.5); startConstSim();
4149
+ if (constLiveTimer) clearTimeout(constLiveTimer);
4150
+ constLiveTimer = setTimeout(function () { constLoaded = false; loadConstellation(); }, 4000);
4151
+ }
4152
+
4153
+ // ──── Intelligence analytics strip ─────────────────────────────────────
4154
+ async function refreshIntelAnalytics() {
4155
+ const d = await api('/api/v6/memory-intelligence');
4156
+ if (!d) return;
4157
+ animateNum('intel-total', d.totalEntries || 0);
4158
+ const cov = Math.round((d.sourceCoverage || 0) * 100);
4159
+ const covEl = document.getElementById('intel-coverage'); if (covEl) covEl.textContent = cov + '%';
4160
+ const covM = document.getElementById('intel-coverage-meter'); if (covM) covM.style.width = cov + '%';
4161
+ animateNum('intel-recent', d.recent24h || 0);
4162
+ animateNum('intel-highimp', d.highImportance || 0);
4163
+ drawIntelAgents(d.byAgent || []);
4164
+ drawIntelCats(d.byCategory || []);
4165
+ renderTagCloud(d.topTags || []);
4166
+ renderIntelRecent(d.recent || []);
4167
+ }
4168
+ function drawIntelAgents(rows) {
4169
+ const cv = document.getElementById('chart-intel-agents'); if (!cv || !window.Chart) return;
4170
+ const t = chartTheme(); const top = rows.slice(0, 6);
4171
+ if (charts.intelAgents) charts.intelAgents.destroy();
4172
+ charts.intelAgents = new Chart(cv, { type: 'bar', data: { labels: top.map(function (r) { return r.name; }), datasets: [{ data: top.map(function (r) { return r.count; }), backgroundColor: '#18ffff', borderRadius: 5 }] }, options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { color: t.grid }, ticks: { color: t.text } }, y: { grid: { display: false }, ticks: { color: t.text } } } } });
4173
+ }
4174
+ function drawIntelCats(rows) {
4175
+ const cv = document.getElementById('chart-intel-cats'); if (!cv || !window.Chart) return;
4176
+ const t = chartTheme(); const top = rows.slice(0, 6);
4177
+ if (charts.intelCats) charts.intelCats.destroy();
4178
+ charts.intelCats = new Chart(cv, { type: 'doughnut', data: { labels: top.map(function (r) { return r.name; }), datasets: [{ data: top.map(function (r) { return r.count; }), backgroundColor: ['#18ffff', '#a855f7', '#ec4899', '#10e68c', '#ffb53f', '#4fb6ff'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: t.text, boxWidth: 12 } } } } });
4179
+ }
4180
+ function renderTagCloud(rows) {
4181
+ const el = document.getElementById('intel-tags'); if (!el) return;
4182
+ el.innerHTML = rows.length ? rows.slice(0, 18).map(function (r) { return '<span class="tc">#' + htmlEscape(r.name) + ' <b>' + r.count + '</b></span>'; }).join('') : '<div class="empty-tight">No tags yet.</div>';
4183
+ }
4184
+ function renderIntelRecent(rows) {
4185
+ const el = document.getElementById('intel-recent-list'); if (!el) return;
4186
+ el.innerHTML = rows.length ? rows.slice(0, 10).map(function (e) { return '<div class="memory-item"><div class="meta"><span class="pill purple">' + htmlEscape(e.agentTool || 'agent') + '</span><span>' + htmlEscape(e.category || '') + '</span></div><div class="content">' + htmlEscape(String(e.content || '').slice(0, 160)) + '</div></div>'; }).join('') : '<div class="empty-tight">No memories yet.</div>';
4187
+ }
4188
+
4189
+ // ──── Overview constellation hero ──────────────────────────────────────
4190
+ function updateConstHero() {
4191
+ if (!constStats) return;
4192
+ const nodes = constStats.memories + constStats.projects + constStats.agents + constStats.tags;
4193
+ const n = document.getElementById('hero-const-nodes'); if (n) n.textContent = nodes;
4194
+ const e = document.getElementById('hero-const-edges'); if (e) e.textContent = constStats.edges;
4195
+ const m = document.getElementById('hero-const-mem'); if (m) m.textContent = constStats.memories;
4196
+ }
4197
+ async function loadConstHeroStats() {
4198
+ if (constStats) { updateConstHero(); return; }
4199
+ const snap = await api('/api/v6/memory-graph?limit=400&maxNodes=180');
4200
+ if (snap && snap.stats) { constStats = snap.stats; updateConstHero(); }
4201
+ }
4202
+
3027
4203
  // ──── Command Palette ──────────────────────────────────────────────────
3028
4204
  const commands = [
3029
- ...FEATURES.map(([id, name]) => ({ type: 'nav', label: name, target: id, hint: 'Go to' })),
3030
- { type: 'nav', label: 'Overview', target: 'overview' },
3031
- { type: 'nav', label: 'Live Graph', target: 'graph' },
3032
- { type: 'nav', label: 'Agents', target: 'agents' },
3033
- { type: 'nav', label: 'Memory Browser', target: 'memory' },
3034
- { type: 'nav', label: 'Activity Log', target: 'activity' },
3035
- { type: 'action', label: 'Run Dream Cycle', fn: 'runDream' },
3036
- { type: 'action', label: 'Run Consolidation', fn: 'runForget' },
3037
- { type: 'action', label: 'Toggle Air-Gap', fn: 'toggleAirGap' },
3038
- { type: 'action', label: 'Toggle Theme', fn: 'toggleTheme' },
3039
- { type: 'action', label: 'Attach All Agents', fn: 'attachAll' },
3040
- { type: 'action', label: 'Refresh All', fn: 'refresh' },
4205
+ ...FEATURES.map(([id, name, desc]) => ({ type: 'nav', label: name, target: id, hint: desc || 'Go to' })),
4206
+ { type: 'action', label: 'Run Reflections now', fn: 'runDream' },
4207
+ { type: 'action', label: 'Clean up memory now', fn: 'runForget' },
4208
+ { type: 'action', label: 'Toggle dark / light theme', fn: 'toggleTheme' },
4209
+ { type: 'action', label: 'Connect all tools', fn: 'attachAll' },
4210
+ { type: 'action', label: 'Refresh everything', fn: 'refresh' },
3041
4211
  ];
3042
4212
  let cmdSelected = 0;
3043
4213
  function openCmdPalette() {
@@ -3103,21 +4273,36 @@ function morphBrainState(type) {
3103
4273
  stateTimer = setTimeout(() => { el.textContent = 'learning · reflecting · charging'; }, 6000);
3104
4274
  }
3105
4275
  async function pullRecentActivity() {
3106
- const log = await api('/api/v6/activity-log?limit=40');
3107
- if (!log) return;
3108
- const seen = new Set(logEntries.map(e => e.t.toISOString() + '|' + e.type + '|' + e.detail.slice(0, 40)));
3109
- for (const e of log.reverse()) {
3110
- const key = (e.timestamp || '') + '|' + e.type + '|' + (e.detail || '').slice(0, 40);
3111
- if (!seen.has(key)) appendLog(e.source || 'brain', e.type || 'event', e.detail || '');
3112
- }
4276
+ // Reconciliation safety net: pick up anything the WebSocket missed
4277
+ // (e.g. a dropped frame). ingestActivity dedups, so live events that
4278
+ // already arrived over the socket are ignored here.
4279
+ const log = await api('/api/v6/activity-log?limit=60');
4280
+ if (!Array.isArray(log)) return;
4281
+ for (let i = log.length - 1; i >= 0; i--) ingestActivity(log[i], false);
4282
+ renderSparkline(); renderLivePulse();
4283
+ if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();
3113
4284
  }
3114
4285
 
3115
4286
  // ──── WebSocket ────────────────────────────────────────────────────────
3116
4287
  function connectWS() {
3117
4288
  const proto = location.protocol === 'https:' ? 'wss' : 'ws';
3118
4289
  ws = new WebSocket(\`\${proto}://\${location.host}\`);
3119
- ws.onopen = () => { appendLog('ws','connected','dashboard WebSocket online'); toast('Connected', 'live updates enabled', 'success'); };
4290
+ ws.onopen = () => { appendLog('ws','connected','dashboard WebSocket online'); setConn(true); toast('Connected', 'live updates enabled', 'success'); };
3120
4291
  ws.onmessage = e => { try { const m = JSON.parse(e.data);
4292
+ if (m.type === 'activity' && m.event) {
4293
+ if (ingestActivity(m.event, true)) {
4294
+ renderSparkline(); renderLivePulse();
4295
+ if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();
4296
+ if (m.event.type === 'memory-write' && document.getElementById('tab-intelligence')?.classList.contains('active')) constLiveBump();
4297
+ }
4298
+ return;
4299
+ }
4300
+ if (m.type === 'activity-backlog' && Array.isArray(m.events)) {
4301
+ for (let i = m.events.length - 1; i >= 0; i--) ingestActivity(m.events[i], false);
4302
+ renderSparkline(); renderLivePulse();
4303
+ if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();
4304
+ return;
4305
+ }
3121
4306
  if (m.type === 'insights' || m.type === 'modules') refresh();
3122
4307
  if (m.type === 'insights' && m.insights) for (const ins of m.insights.slice(0,5)) appendLog(ins.sourceAgent || 'brain','insight',ins.title || ins.content);
3123
4308
  if (m.type === 'injection') appendLog(m.adapter || 'agent','injection', m.insightTitle || 'context injected');
@@ -3125,19 +4310,21 @@ function connectWS() {
3125
4310
  if (m.type === 'error') { appendLog('brain','error', m.error); toast('Brain error', m.error, 'error'); }
3126
4311
  if (m.type === 'agents-detected') appendLog('brain','agents', (m.agents || []).join(', '));
3127
4312
  } catch {} };
3128
- ws.onclose = () => { appendLog('ws','disconnected','reconnecting'); setTimeout(connectWS, 2000); };
4313
+ ws.onclose = () => { appendLog('ws','disconnected','reconnecting'); setConn(false); setTimeout(connectWS, 2000); };
4314
+ ws.onerror = () => { setConn(false); };
3129
4315
  }
3130
4316
 
3131
4317
  // ──── Bootstrapping ────────────────────────────────────────────────────
3132
4318
  connectWS();
3133
4319
  refresh();
3134
4320
  pullRecentActivity();
3135
- setInterval(refresh, 5000); // faster heartbeat — real-time feel
3136
- setInterval(pullRecentActivity, 3000); // faster log stream
4321
+ setInterval(refresh, 5000); // dashboard cards heartbeat
4322
+ setInterval(pullRecentActivity, 15000); // reconciliation only — live events arrive via WebSocket
4323
+ setInterval(renderSparkline, 10000); // keep the events/min window current
3137
4324
  if (window.lucide) try { window.lucide.createIcons(); } catch {}
3138
4325
 
3139
4326
  // Welcome toast
3140
- setTimeout(() => toast('Hive Mind v6.0', 'Press ⌘K or Ctrl+K to open the command palette', 'info'), 800);
4327
+ setTimeout(() => toast('Welcome to Shadow Brain', 'Showing the essentials. Flip to Advanced (top-left) for every feature, or press ⌘K to search.', 'info'), 800);
3141
4328
  </script>
3142
4329
  </body>
3143
4330
  </html>`;