@dadado/agent-kit-cli 4.8.2 → 4.8.4

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.
@@ -503,6 +503,9 @@ a:focus-visible,
503
503
  background: var(--border);
504
504
  }
505
505
  .nav-more-group-label {
506
+ display: flex;
507
+ align-items: center;
508
+ gap: var(--mc-chrome-label-gap);
506
509
  padding: 6px 10px 2px;
507
510
  font-size: var(--mc-chrome-subtitle-size);
508
511
  font-weight: var(--mc-chrome-subtitle-weight);
@@ -800,25 +803,122 @@ a:focus-visible,
800
803
  }
801
804
  .terminal-item:hover { background: var(--bg-card-hover); }
802
805
 
803
- /* ===== Health ===== */
806
+ /* ===== Healthcenter ===== */
807
+ .healthcenter {
808
+ display: flex;
809
+ flex-direction: column;
810
+ gap: 10px;
811
+ }
812
+ .healthcenter-presence {
813
+ display: flex;
814
+ align-items: center;
815
+ gap: 8px;
816
+ font-size: 11px;
817
+ color: var(--text-muted);
818
+ }
819
+ .healthcenter-presence .dot-pulse {
820
+ animation: pulse 1.6s ease-in-out infinite;
821
+ }
804
822
  .health-grid {
805
823
  display: grid;
806
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
824
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
807
825
  gap: 8px;
808
826
  }
827
+ .health-card {
828
+ display: flex;
829
+ flex-direction: column;
830
+ gap: 0;
831
+ border-radius: var(--mc-radius-sm, 6px);
832
+ background: var(--bg-card);
833
+ border: 1px solid var(--border);
834
+ overflow: hidden;
835
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
836
+ }
837
+ .health-card:hover,
838
+ .health-card.is-expanded {
839
+ border-color: var(--border-active);
840
+ }
841
+ .health-card[data-severity="ok"] { border-left: 3px solid var(--green, #3fb950); }
842
+ .health-card[data-severity="warning"] { border-left: 3px solid var(--yellow, #d29922); }
843
+ .health-card[data-severity="degraded"] { border-left: 3px solid var(--orange, #db6d28); }
844
+ .health-card[data-severity="error"] { border-left: 3px solid var(--red, #f85149); }
809
845
  .health-item {
810
846
  display: flex;
811
847
  align-items: center;
812
848
  gap: 8px;
813
- padding: 8px 10px;
814
- border-radius: 6px;
815
- background: var(--bg-card);
816
- border: 1px solid var(--border);
849
+ padding: 10px 12px;
817
850
  font-size: 12px;
818
851
  color: var(--text-secondary);
819
- transition: opacity 0.3s ease, transform 0.3s ease, border-color 0.2s;
852
+ cursor: pointer;
853
+ background: transparent;
854
+ border: none;
855
+ width: 100%;
856
+ text-align: left;
857
+ font-family: inherit;
858
+ transition: opacity 0.3s ease, transform 0.3s ease, background 0.15s ease;
859
+ }
860
+ .health-item:hover { background: var(--bg-card-hover); }
861
+ .health-item-label {
862
+ flex: 1;
863
+ min-width: 0;
864
+ color: var(--text-primary);
865
+ }
866
+ .health-item-sev {
867
+ font-size: 10px;
868
+ text-transform: uppercase;
869
+ letter-spacing: 0.04em;
870
+ color: var(--text-muted);
871
+ flex-shrink: 0;
872
+ }
873
+ .health-item-chevron {
874
+ color: var(--text-muted);
875
+ font-size: 10px;
876
+ transition: transform 0.15s ease;
877
+ flex-shrink: 0;
878
+ }
879
+ .health-card.is-expanded .health-item-chevron {
880
+ transform: rotate(180deg);
881
+ }
882
+ .health-detail {
883
+ display: none;
884
+ padding: 0 12px 12px;
885
+ border-top: 1px solid var(--border);
886
+ background: var(--bg-secondary);
887
+ }
888
+ .health-card.is-expanded .health-detail {
889
+ display: block;
890
+ animation: fadeIn 0.2s ease;
891
+ }
892
+ .health-detail-copy {
893
+ font-size: 11px;
894
+ color: var(--text-secondary);
895
+ margin: 10px 0 8px;
896
+ line-height: 1.4;
897
+ }
898
+ .health-detail-actions {
899
+ display: flex;
900
+ flex-wrap: wrap;
901
+ gap: 6px;
902
+ }
903
+ .health-autofix-btn {
904
+ font-size: 11px;
905
+ padding: 4px 10px;
906
+ border-radius: 4px;
907
+ border: 1px solid var(--border-active);
908
+ background: var(--bg-card);
909
+ color: var(--text-primary);
910
+ cursor: pointer;
911
+ font-family: inherit;
912
+ }
913
+ .health-autofix-btn:hover {
914
+ background: var(--bg-card-hover);
915
+ }
916
+ html[data-dashboard-skin="cursor"] .health-card {
917
+ border-radius: 8px;
918
+ }
919
+ html[data-dashboard-skin="legacy"] .health-card {
920
+ border-radius: 4px;
820
921
  }
821
- .health-item:hover { border-color: var(--border-active); }
822
922
 
823
923
  /* ===== Git ===== */
824
924
  .git-info {
@@ -1448,7 +1548,7 @@ a:focus-visible,
1448
1548
  vertical-align: middle;
1449
1549
  }
1450
1550
 
1451
- /* ===== Health Investigation Message ===== */
1551
+ /* ===== Health Investigation Message (legacy; Healthcenter uses .health-detail) ===== */
1452
1552
  .health-message {
1453
1553
  font-size: 11px;
1454
1554
  color: var(--text-secondary);
@@ -2031,8 +2131,8 @@ a:focus-visible,
2031
2131
  grid-template-columns: 1fr 1fr;
2032
2132
  grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
2033
2133
  grid-template-areas:
2034
- "now monitor"
2035
- "attention checklist";
2134
+ "now attention"
2135
+ "checklist monitor";
2036
2136
  }
2037
2137
 
2038
2138
  #section-overview.active #now-execution-panel,
@@ -2662,10 +2762,10 @@ a:focus-visible,
2662
2762
  font: inherit;
2663
2763
  line-height: 1.4;
2664
2764
  }
2765
+ /* Base hover/focus: keyboard-visible ring; kind rules below set colors (no unset --accent → yellow). */
2665
2766
  .flight-log-card:hover,
2666
2767
  .flight-log-card:focus-visible {
2667
- border-color: var(--accent, var(--yellow));
2668
- outline: 2px solid var(--accent, var(--yellow));
2768
+ outline: 2px solid var(--border);
2669
2769
  outline-offset: 1px;
2670
2770
  }
2671
2771
  .flight-log-card-current {
@@ -2679,6 +2779,11 @@ a:focus-visible,
2679
2779
  .flight-log-card-current.flight-log-kind-residual .flight-log-card-label {
2680
2780
  color: var(--yellow);
2681
2781
  }
2782
+ .flight-log-card-current.flight-log-kind-residual:hover,
2783
+ .flight-log-card-current.flight-log-kind-residual:focus-visible {
2784
+ border-color: var(--yellow);
2785
+ outline-color: var(--yellow);
2786
+ }
2682
2787
  .flight-log-card-current.flight-log-kind-advice {
2683
2788
  border-color: var(--blue);
2684
2789
  background: var(--blue-bg);
@@ -2686,6 +2791,11 @@ a:focus-visible,
2686
2791
  .flight-log-card-current.flight-log-kind-advice .flight-log-card-label {
2687
2792
  color: var(--blue);
2688
2793
  }
2794
+ .flight-log-card-current.flight-log-kind-advice:hover,
2795
+ .flight-log-card-current.flight-log-kind-advice:focus-visible {
2796
+ border-color: var(--blue);
2797
+ outline-color: var(--blue);
2798
+ }
2689
2799
  .flight-log-card-current.flight-log-kind-ok {
2690
2800
  border-color: color-mix(in srgb, var(--green) 55%, var(--border));
2691
2801
  background: color-mix(in srgb, var(--green-bg) 40%, var(--bg-elevated, var(--bg-card)));
@@ -2693,6 +2803,11 @@ a:focus-visible,
2693
2803
  .flight-log-card-current.flight-log-kind-ok .flight-log-card-label {
2694
2804
  color: var(--green);
2695
2805
  }
2806
+ .flight-log-card-current.flight-log-kind-ok:hover,
2807
+ .flight-log-card-current.flight-log-kind-ok:focus-visible {
2808
+ border-color: var(--green);
2809
+ outline-color: var(--green);
2810
+ }
2696
2811
  .flight-log-card-current.flight-log-kind-warning {
2697
2812
  border-color: color-mix(in srgb, var(--orange, var(--yellow)) 70%, var(--border));
2698
2813
  background: color-mix(in srgb, var(--orange-bg, var(--yellow-bg)) 55%, var(--bg-elevated, var(--bg-card)));
@@ -2700,6 +2815,11 @@ a:focus-visible,
2700
2815
  .flight-log-card-current.flight-log-kind-warning .flight-log-card-label {
2701
2816
  color: var(--orange, var(--yellow));
2702
2817
  }
2818
+ .flight-log-card-current.flight-log-kind-warning:hover,
2819
+ .flight-log-card-current.flight-log-kind-warning:focus-visible {
2820
+ border-color: var(--orange, var(--yellow));
2821
+ outline-color: var(--orange, var(--yellow));
2822
+ }
2703
2823
  .flight-log-card-past {
2704
2824
  padding: 8px 10px;
2705
2825
  font-size: 11px;
@@ -2709,15 +2829,35 @@ a:focus-visible,
2709
2829
  .flight-log-card-past.flight-log-kind-residual {
2710
2830
  border-color: color-mix(in srgb, var(--yellow) 35%, var(--border));
2711
2831
  }
2832
+ .flight-log-card-past.flight-log-kind-residual:hover,
2833
+ .flight-log-card-past.flight-log-kind-residual:focus-visible {
2834
+ border-color: color-mix(in srgb, var(--yellow) 55%, var(--border));
2835
+ outline-color: color-mix(in srgb, var(--yellow) 55%, var(--border));
2836
+ }
2712
2837
  .flight-log-card-past.flight-log-kind-advice {
2713
2838
  border-color: color-mix(in srgb, var(--blue) 35%, var(--border));
2714
2839
  }
2840
+ .flight-log-card-past.flight-log-kind-advice:hover,
2841
+ .flight-log-card-past.flight-log-kind-advice:focus-visible {
2842
+ border-color: color-mix(in srgb, var(--blue) 55%, var(--border));
2843
+ outline-color: color-mix(in srgb, var(--blue) 55%, var(--border));
2844
+ }
2715
2845
  .flight-log-card-past.flight-log-kind-ok {
2716
2846
  border-color: color-mix(in srgb, var(--green) 30%, var(--border));
2717
2847
  }
2848
+ .flight-log-card-past.flight-log-kind-ok:hover,
2849
+ .flight-log-card-past.flight-log-kind-ok:focus-visible {
2850
+ border-color: color-mix(in srgb, var(--green) 50%, var(--border));
2851
+ outline-color: color-mix(in srgb, var(--green) 50%, var(--border));
2852
+ }
2718
2853
  .flight-log-card-past.flight-log-kind-warning {
2719
2854
  border-color: color-mix(in srgb, var(--orange, var(--yellow)) 30%, var(--border));
2720
2855
  }
2856
+ .flight-log-card-past.flight-log-kind-warning:hover,
2857
+ .flight-log-card-past.flight-log-kind-warning:focus-visible {
2858
+ border-color: color-mix(in srgb, var(--orange, var(--yellow)) 50%, var(--border));
2859
+ outline-color: color-mix(in srgb, var(--orange, var(--yellow)) 50%, var(--border));
2860
+ }
2721
2861
  .flight-log-card-warning {
2722
2862
  padding: 10px 12px;
2723
2863
  border-color: color-mix(in srgb, var(--orange, var(--yellow)) 70%, var(--border));
@@ -2728,6 +2868,11 @@ a:focus-visible,
2728
2868
  .flight-log-card-warning .flight-log-card-label {
2729
2869
  color: var(--orange, var(--yellow));
2730
2870
  }
2871
+ .flight-log-card-warning:hover,
2872
+ .flight-log-card-warning:focus-visible {
2873
+ border-color: var(--orange, var(--yellow));
2874
+ outline-color: var(--orange, var(--yellow));
2875
+ }
2731
2876
  .flight-log-card-label {
2732
2877
  display: block;
2733
2878
  font-weight: 600;
@@ -2894,7 +3039,7 @@ a:focus-visible,
2894
3039
  </a>
2895
3040
  <div class="nav-more-separator" role="separator"></div>
2896
3041
  <div class="nav-more-group" role="group" aria-labelledby="navSkinsLabel">
2897
- <div class="nav-more-group-label" id="navSkinsLabel">Skins</div>
3042
+ <div class="nav-more-group-label" id="navSkinsLabel" data-icon="skins">Skins</div>
2898
3043
  <button type="button" class="nav-more-item" role="menuitemradio" aria-checked="true" data-dashboard-skin-option="legacy" tabindex="0" onclick="return setDashboardSkin('legacy')">
2899
3044
  Legacy
2900
3045
  </button>
@@ -2914,18 +3059,18 @@ a:focus-visible,
2914
3059
  <div class="top-tabs-row">
2915
3060
  <div class="cockpit-anchors" role="navigation" aria-label="Cockpit sections">
2916
3061
  <a class="top-nav-anchor active" href="#now-execution-panel" data-anchor="now-execution-panel" data-icon="current-mission" onclick="return goCockpitAnchor('now-execution-panel')" aria-current="true">
2917
- <span class="top-nav-anchor-label">Cockpit</span>
3062
+ <span class="top-nav-anchor-label">Current mission</span>
2918
3063
  <span class="top-tab-badge" id="navOverviewBadge">0</span>
2919
3064
  </a>
2920
- <a class="top-nav-anchor" href="#hero-activity" data-anchor="hero-activity" data-icon="monitor" onclick="return goCockpitAnchor('hero-activity')">
2921
- <span class="top-nav-anchor-label">Crew Monitor</span>
2922
- </a>
2923
3065
  <a class="top-nav-anchor" href="#attention-panel" data-anchor="attention-panel" data-icon="field-report" onclick="return goCockpitAnchor('attention-panel')">
2924
3066
  <span class="top-nav-anchor-label">Flight Log</span>
2925
3067
  </a>
2926
3068
  <a class="top-nav-anchor" href="#recent-plans-panel" data-anchor="recent-plans-panel" data-icon="checklist" onclick="return goCockpitAnchor('recent-plans-panel')">
2927
3069
  <span class="top-nav-anchor-label">Checklist</span>
2928
3070
  </a>
3071
+ <a class="top-nav-anchor" href="#hero-activity" data-anchor="hero-activity" data-icon="monitor" onclick="return goCockpitAnchor('hero-activity')">
3072
+ <span class="top-nav-anchor-label">Crew Monitor</span>
3073
+ </a>
2929
3074
  </div>
2930
3075
  </div>
2931
3076
  </nav>
@@ -3295,6 +3440,11 @@ function injectCockpitNavIcons() {
3295
3440
  if (!kind) return;
3296
3441
  el.insertAdjacentHTML('afterbegin', spaceIconSvg(kind, { decorative: true }));
3297
3442
  });
3443
+ // Skins group label: palette glyph (distinct from Skills gear).
3444
+ const skinsLabel = document.getElementById('navSkinsLabel');
3445
+ if (skinsLabel && !skinsLabel.querySelector('.space-icon')) {
3446
+ skinsLabel.insertAdjacentHTML('afterbegin', spaceIconSvg('skins', { decorative: true }));
3447
+ }
3298
3448
  }
3299
3449
 
3300
3450
  // ===== Cockpit nav + more-menu keyboard / outside click =====
@@ -4481,9 +4631,85 @@ function toggleAgentDetails(id) {
4481
4631
  }
4482
4632
  }
4483
4633
 
4484
- // ===== Health check info (Phase 1) =====
4634
+ // ===== Healthcenter: per-check expand + Autofix map (copy-only) =====
4635
+ /** Fixed v1 check set: plans, handoff, agents, commands, memory, git, config. */
4636
+ const HEALTH_CHECK_META = {
4637
+ plans: {
4638
+ okDetail: 'Plans directory is present with at least one plan file.',
4639
+ failDetail: 'No plans under .cursor/plans/, or the plans directory is missing.',
4640
+ autofix: { text: '/start-project', subject: '/start-project', destination: 'chatInput', label: 'Autofix' },
4641
+ },
4642
+ handoff: {
4643
+ okDetail: 'HANDOFF.md is present and parseable (idle Plan is still healthy).',
4644
+ failDetail: 'HANDOFF.md is missing or not parseable.',
4645
+ autofix: { text: '/continue-plan', subject: '/continue-plan', destination: 'chatInput', label: 'Autofix' },
4646
+ },
4647
+ agents: {
4648
+ okDetail: 'At least one agent is cataloged under .cursor/agents/.',
4649
+ failDetail: 'No agents found. Kit install or onboard may be incomplete.',
4650
+ autofix: { text: '/agent-kit-onboard', subject: '/agent-kit-onboard', destination: 'chatInput', label: 'Autofix' },
4651
+ },
4652
+ commands: {
4653
+ okDetail: 'Slash commands are available under .cursor/commands/.',
4654
+ failDetail: 'No commands under .cursor/commands/.',
4655
+ autofix: { text: '.cursor/commands/', subject: 'path .cursor/commands/', destination: 'filePicker', label: 'Copy path' },
4656
+ },
4657
+ memory: {
4658
+ okDetail: 'Memory has recorded errors and/or decisions.',
4659
+ failDetail: 'Memory index has no errors or decisions yet (optional until first WRITE).',
4660
+ autofix: { text: '/agent-kit-onboard', subject: '/agent-kit-onboard', destination: 'chatInput', label: 'Autofix' },
4661
+ },
4662
+ git: {
4663
+ okDetail: 'Git branch signal is available.',
4664
+ failDetail: 'Git repository signal missing (no branch).',
4665
+ autofix: { text: 'git status', subject: 'git status', destination: 'terminal', label: 'Autofix' },
4666
+ },
4667
+ config: {
4668
+ okDetail: 'Workspace config is loaded.',
4669
+ failDetail: 'Workspace config is missing or not loaded.',
4670
+ autofix: { text: 'agent-kit doctor', subject: 'agent-kit doctor', destination: 'terminal', label: 'Autofix' },
4671
+ },
4672
+ };
4673
+
4674
+ function healthCheckSeverity(check, aggregateStatus) {
4675
+ if (check && check.ok) return 'ok';
4676
+ if (aggregateStatus === 'degraded') return 'degraded';
4677
+ if (aggregateStatus === 'error') return 'error';
4678
+ return 'warning';
4679
+ }
4680
+
4681
+ function healthSeverityLabel(sev) {
4682
+ if (sev === 'ok') return 'ok';
4683
+ if (sev === 'warning') return 'warn';
4684
+ if (sev === 'degraded') return 'degraded';
4685
+ if (sev === 'error') return 'error';
4686
+ return sev || 'unknown';
4687
+ }
4688
+
4689
+ function healthDotClass(sev) {
4690
+ if (sev === 'ok') return 'dot-green';
4691
+ if (sev === 'warning') return 'dot-yellow';
4692
+ if (sev === 'degraded') return 'dot-yellow';
4693
+ return 'dot-red';
4694
+ }
4695
+
4696
+ function toggleHealthCheck(checkId) {
4697
+ const card = document.getElementById('health-card-' + checkId);
4698
+ if (!card) return;
4699
+ const open = card.classList.toggle('is-expanded');
4700
+ const btn = card.querySelector('.health-item');
4701
+ if (btn) btn.setAttribute('aria-expanded', open ? 'true' : 'false');
4702
+ }
4703
+
4704
+ function healthCheckKeydown(event, checkId) {
4705
+ if (event.key === 'Enter' || event.key === ' ') {
4706
+ event.preventDefault();
4707
+ toggleHealthCheck(checkId);
4708
+ }
4709
+ }
4710
+
4711
+ /** @deprecated Prefer toggleHealthCheck; kept for any leftover callers. */
4485
4712
  function showHealthInfo(el, ok) {
4486
- // Remove any existing message siblings
4487
4713
  const parent = el.parentElement;
4488
4714
  const existing = parent.querySelector('.health-message');
4489
4715
  if (existing) { existing.remove(); return; }
@@ -5290,9 +5516,9 @@ function nowMetaIconSvg(kind, opts) {
5290
5516
  /**
5291
5517
  * Space-theme Cockpit/nav icon set (inline SVG, no font/fetch/dependency).
5292
5518
  * Shared chrome SVG shell with header refresh / nowMetaIconSvg:
5293
- * viewBox 0 0 16 16, stroke 0.5, round caps/joins, CSS box via --mc-chrome-icon-size.
5519
+ * viewBox 0 0 16 16, stroke 1.5, round caps/joins, CSS box via --mc-chrome-icon-size.
5294
5520
  * Static path markup only; never concatenate untrusted text into the SVG.
5295
- * @param {'current-mission'|'monitor'|'field-report'|'checklist'|'more-sections'|'overview'|'plans'|'activity'|'agents'|'skills'|'commands'|'health'|'git'|'memory'|'terminals'|'processes'|'config'} kind
5521
+ * @param {'current-mission'|'monitor'|'field-report'|'checklist'|'more-sections'|'overview'|'plans'|'activity'|'agents'|'skills'|'skins'|'commands'|'health'|'git'|'memory'|'terminals'|'processes'|'config'} kind
5296
5522
  * @param {{ decorative?: boolean }} [opts] decorative true (default): aria-hidden next to a visible label.
5297
5523
  * decorative false: role=img + aria-label + title for icon-only controls (e.g. more-sections).
5298
5524
  */
@@ -5310,6 +5536,7 @@ function spaceIconSvg(kind, opts) {
5310
5536
  activity: 'Activity',
5311
5537
  agents: 'Agents',
5312
5538
  skills: 'Skills',
5539
+ skins: 'Skins',
5313
5540
  commands: 'Commands',
5314
5541
  health: 'Health',
5315
5542
  git: 'Git',
@@ -5319,7 +5546,7 @@ function spaceIconSvg(kind, opts) {
5319
5546
  config: 'Config',
5320
5547
  };
5321
5548
  const paths = {
5322
- // Rocket (mission) — thin stroke matching helmet aesthetic
5549
+ // Rocket (mission)
5323
5550
  'current-mission':
5324
5551
  '<path d="M8 2c1.4 2 2.5 4 2.5 6a2.5 2.5 0 01-5 0C5.5 6 6.6 4 8 2z"/>' +
5325
5552
  '<circle cx="8" cy="7.5" r=".9"/>' +
@@ -5336,20 +5563,20 @@ function spaceIconSvg(kind, opts) {
5336
5563
  '<rect x="4.5" y="3.5" width="7" height="10" rx="1.2"/>' +
5337
5564
  '<path d="M6.5 3.5V2.5a1.5 1.5 0 013 0v1"/>' +
5338
5565
  '<path d="M6.5 7h3M6.5 9.5h3"/>',
5339
- // Checklist lines — thin
5566
+ // Checklist lines
5340
5567
  checklist:
5341
5568
  '<path d="M3.5 4.5h9M3.5 8h6.5M3.5 11.5h5.5"/>' +
5342
5569
  '<path d="M10.5 10.5l1.2 1.2L14 9.5"/>',
5343
- // Constellation dots (more sections) — outline circles matching thin stroke
5570
+ // Ellipsis (more sections) — radii sized for stroke 1.5
5344
5571
  'more-sections':
5345
- '<circle cx="4" cy="8" r=".8"/>' +
5346
- '<circle cx="8" cy="8" r=".8"/>' +
5347
- '<circle cx="12" cy="8" r=".8"/>',
5348
- // House (overview / home)
5572
+ '<circle cx="4" cy="8" r="1.25"/>' +
5573
+ '<circle cx="8" cy="8" r="1.25"/>' +
5574
+ '<circle cx="12" cy="8" r="1.25"/>',
5575
+ // House (overview / home) — roof + walls + door as one coherent glyph
5349
5576
  overview:
5350
- '<path d="M2.5 8.5L8 3l5.5 5.5"/>' +
5351
- '<path d="M4.5 10v4h3M11.5 10v4h-3"/>' +
5352
- '<path d="M4.5 14h7"/>',
5577
+ '<path d="M2.5 7.5L8 2.75l5.5 4.75"/>' +
5578
+ '<path d="M4.25 7.75v6.5h7.5v-6.5"/>' +
5579
+ '<path d="M6.75 14.25v-3.25h2.5v3.25"/>',
5353
5580
  // Document (plans)
5354
5581
  plans:
5355
5582
  '<path d="M4.5 2.5h5l4 4v8.5a1 1 0 01-1 1h-8a1 1 0 01-1-1v-12a1 1 0 011-1z"/>' +
@@ -5365,6 +5592,10 @@ function spaceIconSvg(kind, opts) {
5365
5592
  skills:
5366
5593
  '<circle cx="8" cy="8" r="2"/>' +
5367
5594
  '<path d="M10.5 8a2.5 2.5 0 000-2.5l.8-.8M5.5 8a2.5 2.5 0 010 2.5l-.8.8M8 5.5l-.5-1M8 10.5l-.5 1M10.5 6.5l1-.5M5.5 9.5l-1 .5M10.5 9.5l1 .5M5.5 6.5l-1-.5"/>',
5595
+ // Overlapping swatches (skins) — distinct from Skills gear
5596
+ skins:
5597
+ '<rect x="2.5" y="3.5" width="7" height="7" rx="1.25"/>' +
5598
+ '<rect x="6.5" y="5.5" width="7" height="7" rx="1.25"/>',
5368
5599
  // Terminal (commands)
5369
5600
  commands:
5370
5601
  '<path d="M2.5 3.5l5 5-5 5"/>' +
@@ -5405,7 +5636,7 @@ function spaceIconSvg(kind, opts) {
5405
5636
  const a11y = decorative
5406
5637
  ? 'aria-hidden="true"'
5407
5638
  : `role="img" aria-label="${label}" title="${label}"`;
5408
- return `<svg class="space-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round" ${a11y}>${body}</svg>`;
5639
+ return `<svg class="space-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" ${a11y}>${body}</svg>`;
5409
5640
  }
5410
5641
 
5411
5642
  /**
@@ -5582,7 +5813,7 @@ function renderFlightLogWarningCard(warning, idx) {
5582
5813
  /**
5583
5814
  * Quiet-state open-triage row (untriaged external review). Prompt chrome;
5584
5815
  * per-row Copy triage + path only. No Review all / Resolve all.
5585
- * Cap mirrors FLIGHT_LOG_QUIET_OPEN_TRIAGES_CAP in semantic-model.mjs.
5816
+ * Kind class from flightLogKindClassName('prompt') (SoT map; not a literal).
5586
5817
  * @param {{ id?: string, label?: string, sourcePath?: string, action?: { target?: string, label?: string, subject?: string } }} item
5587
5818
  * @param {number} idx
5588
5819
  */
@@ -5603,8 +5834,9 @@ function renderFlightLogQuietOpenTriageCard(item, idx) {
5603
5834
  const copyTriageHandler = copyForPasteHandler(triageCmd, 'triage command', 'chatInput');
5604
5835
  const copyPathHandler = copyRepoPathHandler(sourcePath);
5605
5836
  const aria = `Review: ${label}`;
5837
+ const kindClass = flightLogKindClassName('prompt');
5606
5838
  return `
5607
- <div class="flight-log-card flight-log-card-current flight-log-kind-advice" role="listitem" tabindex="0" data-focus-key="flight-log-open-triage-${idx}" data-flight-log-kind="prompt" aria-label="${escapeAttr(aria)}">
5839
+ <div class="flight-log-card flight-log-card-current ${kindClass}" role="listitem" tabindex="0" data-focus-key="flight-log-open-triage-${idx}" data-flight-log-kind="prompt" aria-label="${escapeAttr(aria)}">
5608
5840
  <span class="flight-log-card-label">Review</span>
5609
5841
  <div class="flight-log-card-text">${escapeHtml(label)}</div>
5610
5842
  <div class="flight-log-card-meta">${escapeHtml(sourcePath)}</div>
@@ -5616,6 +5848,43 @@ function renderFlightLogQuietOpenTriageCard(item, idx) {
5616
5848
  `;
5617
5849
  }
5618
5850
 
5851
+ /**
5852
+ * Resolve Live Gaps text for Flight Log render + fingerprint (same fallback chain).
5853
+ * Prefer fl.current; with Plan:none, fall back to now.gaps then system.handoff.gaps.
5854
+ */
5855
+ function resolveFlightLogCurrent(d) {
5856
+ const fl = d?.missionControl?.flightLog ?? d?.flightLog ?? null;
5857
+ if (typeof fl?.current === 'string' && fl.current.trim()) return fl.current.trim();
5858
+ const nowGaps = d?.missionControl?.now?.gaps ?? d?.now?.gaps;
5859
+ if (typeof nowGaps === 'string' && nowGaps.trim()) return nowGaps.trim();
5860
+ const handoffGaps = d?.system?.handoff?.gaps;
5861
+ if (typeof handoffGaps === 'string' && handoffGaps.trim()) return handoffGaps.trim();
5862
+ return null;
5863
+ }
5864
+
5865
+ function flightLogHasPastEntries(fl) {
5866
+ if (!fl || !Array.isArray(fl.past)) return false;
5867
+ return fl.past.some((e) => {
5868
+ if (typeof e === 'string') return Boolean(e.trim());
5869
+ return Boolean(e && typeof e.text === 'string' && e.text.trim());
5870
+ });
5871
+ }
5872
+
5873
+ /** Match renderFlightLogWarningCard: text-less warnings do not count. */
5874
+ function flightLogHasWarningEntries(fl) {
5875
+ if (!fl || !Array.isArray(fl.warnings)) return false;
5876
+ return fl.warnings.some((w) => {
5877
+ if (typeof w === 'string') return Boolean(w.trim());
5878
+ return Boolean(w && typeof w.text === 'string' && w.text.trim());
5879
+ });
5880
+ }
5881
+
5882
+ /** Shared quiet gate for renderAttentionPanel + flightLogFingerprint. */
5883
+ function isFlightLogQuiet(d) {
5884
+ const fl = d?.missionControl?.flightLog ?? d?.flightLog ?? null;
5885
+ return !resolveFlightLogCurrent(d) && !flightLogHasPastEntries(fl) && !flightLogHasWarningEntries(fl);
5886
+ }
5887
+
5619
5888
  /**
5620
5889
  * Flight Log panel: HANDOFF Gaps log (live large + earlier smaller) + Warnings lane.
5621
5890
  * When Gaps + Warnings are empty, may show bounded quiet open-triage rows.
@@ -5626,22 +5895,22 @@ function renderAttentionPanel(d, attentionChanged) {
5626
5895
  const sourcePath =
5627
5896
  (typeof fl?.sourcePath === 'string' && fl.sourcePath.trim()) ||
5628
5897
  '.cursor/HANDOFF.md';
5629
- const current =
5630
- typeof fl?.current === 'string' && fl.current.trim()
5631
- ? fl.current.trim()
5632
- : typeof d.missionControl?.now?.gaps === 'string' && d.missionControl.now.gaps.trim()
5633
- ? d.missionControl.now.gaps.trim()
5634
- : typeof d.system?.handoff?.gaps === 'string' && d.system.handoff.gaps.trim()
5635
- ? d.system.handoff.gaps.trim()
5636
- : null;
5898
+ const current = resolveFlightLogCurrent(d);
5637
5899
  const past = Array.isArray(fl?.past) ? fl.past : [];
5638
5900
  const warnings = Array.isArray(fl?.warnings) ? fl.warnings : [];
5901
+ const quietCap =
5902
+ typeof d.missionControl?.flightLogQuietOpenTriagesCap === 'number' &&
5903
+ d.missionControl.flightLogQuietOpenTriagesCap > 0
5904
+ ? Math.floor(d.missionControl.flightLogQuietOpenTriagesCap)
5905
+ : 0;
5639
5906
  const quietOpenTriages = Array.isArray(fl?.quietOpenTriages)
5640
5907
  ? fl.quietOpenTriages
5641
5908
  : Array.isArray(d.missionControl?.attention)
5642
- ? d.missionControl.attention.filter(
5643
- (i) => i && i.kind === 'report' && typeof i.sourcePath === 'string' && i.sourcePath.trim(),
5644
- ).slice(0, 5)
5909
+ ? d.missionControl.attention
5910
+ .filter(
5911
+ (i) => i && i.kind === 'report' && typeof i.sourcePath === 'string' && i.sourcePath.trim(),
5912
+ )
5913
+ .slice(0, quietCap)
5645
5914
  : [];
5646
5915
  const pastCards = past
5647
5916
  .filter((e) => e && typeof e.text === 'string' && e.text.trim())
@@ -5675,6 +5944,7 @@ function renderAttentionPanel(d, attentionChanged) {
5675
5944
  const hasCurrent = Boolean(current);
5676
5945
  const hasPast = pastCards.length > 0;
5677
5946
  const hasWarnings = warningCards.length > 0;
5947
+ // Keep local flags aligned with isFlightLogQuiet(d) for the quiet branch.
5678
5948
  if (!hasCurrent && !hasPast && !hasWarnings) {
5679
5949
  if (hasOpenTriages) {
5680
5950
  body = `<div class="flight-log-stack" role="list" aria-label="Reviews awaiting triage">${openTriageCards}</div>`;
@@ -5780,18 +6050,48 @@ function previousStageElapsedTitle(now) {
5780
6050
  return `Previous step elapsed ${formatElapsedPlain(row.elapsedMs)}`;
5781
6051
  }
5782
6052
 
5783
- function flightLogFingerprint(fl) {
6053
+ /**
6054
+ * Fingerprint Flight Log for SSE re-render. Accepts full dashboard `d` or a
6055
+ * slim prev snapshot `{ flightLog, now, system }` so quiet-gate matches render.
6056
+ */
6057
+ function flightLogFingerprint(d) {
6058
+ if (!d || typeof d !== 'object') return '';
6059
+ const fl = d.missionControl?.flightLog ?? d.flightLog ?? null;
5784
6060
  if (!fl || typeof fl !== 'object') return '';
5785
- const current = typeof fl.current === 'string' ? fl.current : '';
6061
+ const current = resolveFlightLogCurrent(d) || '';
5786
6062
  const past = Array.isArray(fl.past)
5787
- ? fl.past.map((e) => (e && typeof e.text === 'string' ? e.text : '')).join('\n')
6063
+ ? fl.past
6064
+ .map((e) =>
6065
+ typeof e === 'string' ? e : e && typeof e.text === 'string' ? e.text : '',
6066
+ )
6067
+ .join('\n')
5788
6068
  : '';
5789
6069
  const warnings = Array.isArray(fl.warnings)
5790
- ? fl.warnings.map((w) => (w && (w.id || w.text) ? String(w.id || w.text) : '')).join('\n')
5791
- : '';
5792
- const openTriages = Array.isArray(fl.quietOpenTriages)
5793
- ? fl.quietOpenTriages.map((i) => (i && (i.id || i.sourcePath) ? String(i.id || i.sourcePath) : '')).join('\n')
6070
+ ? fl.warnings
6071
+ .map((w) =>
6072
+ typeof w === 'string'
6073
+ ? w
6074
+ : w && (w.id || w.text)
6075
+ ? String(w.id || w.text)
6076
+ : '',
6077
+ )
6078
+ .join('\n')
5794
6079
  : '';
6080
+ // Quiet-only: include open-triage ids when Gaps + Warnings are empty so a new
6081
+ // monitor does not flash the non-quiet Flight Log card with no visible change.
6082
+ // Must use the same gate as renderAttentionPanel (Plan:none Gaps fallback).
6083
+ const openTriages =
6084
+ isFlightLogQuiet(d) && Array.isArray(fl.quietOpenTriages)
6085
+ ? fl.quietOpenTriages
6086
+ .map((i) =>
6087
+ typeof i === 'string'
6088
+ ? i
6089
+ : i && (i.id || i.sourcePath)
6090
+ ? String(i.id || i.sourcePath)
6091
+ : '',
6092
+ )
6093
+ .join('\n')
6094
+ : '';
5795
6095
  return `${current}\0${past}\0${warnings}\0${openTriages}`;
5796
6096
  }
5797
6097
 
@@ -6085,8 +6385,12 @@ function renderUnsafe() {
6085
6385
  const nowChanged = prevData && nowFingerprint(d.missionControl?.now) !== nowFingerprint(prevData.now);
6086
6386
  const attentionChanged =
6087
6387
  prevData &&
6088
- flightLogFingerprint(d.missionControl?.flightLog) !==
6089
- flightLogFingerprint(prevData.flightLog);
6388
+ flightLogFingerprint(d) !==
6389
+ flightLogFingerprint({
6390
+ flightLog: prevData.flightLog,
6391
+ now: prevData.now,
6392
+ system: prevData.system,
6393
+ });
6090
6394
  prevData = snapshotPrevData(d);
6091
6395
 
6092
6396
  dataLoading = false;
@@ -6193,6 +6497,10 @@ function renderUnsafe() {
6193
6497
  <div class="overview-stack">
6194
6498
  ${renderNowExecutionPanel(d, !!(nowChanged || handoffChanged))}
6195
6499
 
6500
+ ${renderAttentionPanel(d, !!attentionChanged)}
6501
+
6502
+ ${renderRecentPlanCards(d)}
6503
+
6196
6504
  <div class="section-hero" id="hero-activity">
6197
6505
  <div class="live-activity-header">
6198
6506
  <span class="card-title">${spaceIconSvg('monitor')}Crew Monitor</span>
@@ -6225,10 +6533,6 @@ function renderUnsafe() {
6225
6533
  }).join('')}
6226
6534
  </div>
6227
6535
  </div>
6228
-
6229
- ${renderAttentionPanel(d, !!attentionChanged)}
6230
-
6231
- ${renderRecentPlanCards(d)}
6232
6536
  </div>
6233
6537
  </div>
6234
6538
  `);
@@ -6321,30 +6625,108 @@ function renderUnsafe() {
6321
6625
  </div>
6322
6626
  `);
6323
6627
 
6324
- // ===== Health =====
6628
+ // ===== Healthcenter (More → Health) =====
6325
6629
  const checks = health.checks || [];
6630
+ const healthStatus = health.status || 'ok';
6631
+ const passing = checks.filter(c => c.ok).length;
6632
+ const isSnapshotError = healthStatus === 'error' && checks.length === 0;
6633
+ const isOffline = !isSnapshotError && checks.length === 0;
6634
+ const presenceTone = healthStatus === 'ok' ? 'green' : healthStatus === 'warning' ? 'yellow' : 'red';
6635
+ const presencePulse = healthStatus !== 'ok' ? ' dot-pulse' : '';
6636
+ const presenceLabel = isSnapshotError
6637
+ ? 'Snapshot error'
6638
+ : isOffline
6639
+ ? 'Health offline'
6640
+ : healthStatus === 'ok'
6641
+ ? 'Live'
6642
+ : healthStatus === 'warning'
6643
+ ? 'Warning'
6644
+ : healthStatus === 'degraded'
6645
+ ? 'Degraded'
6646
+ : 'Error';
6647
+ const subtitle = isSnapshotError
6648
+ ? 'Snapshot unavailable'
6649
+ : isOffline
6650
+ ? 'No health data'
6651
+ : `${passing}/${checks.length} checks passing`;
6652
+
6653
+ let healthBody = '';
6654
+ if (isSnapshotError) {
6655
+ healthBody = renderEmptyStateCta({
6656
+ headline: 'Health snapshot error',
6657
+ support: (typeof d.error === 'string' && d.error)
6658
+ || 'The dashboard could not load health checks (serve/snapshot failure). This is not a per-check fail.',
6659
+ iconKind: 'health',
6660
+ cta: {
6661
+ text: 'agent-kit doctor',
6662
+ subject: 'agent-kit doctor',
6663
+ destination: 'terminal',
6664
+ label: 'Autofix: copy agent-kit doctor',
6665
+ focusKey: 'cta-health-snapshot-error',
6666
+ },
6667
+ });
6668
+ } else if (isOffline) {
6669
+ healthBody = renderEmptyStateCta({
6670
+ headline: 'Health offline',
6671
+ support: 'No health data available yet. Refresh after the snapshot loads, or run doctor if the panel stays empty.',
6672
+ iconKind: 'health',
6673
+ cta: {
6674
+ text: 'agent-kit doctor',
6675
+ subject: 'agent-kit doctor',
6676
+ destination: 'terminal',
6677
+ label: 'Autofix: copy agent-kit doctor',
6678
+ focusKey: 'cta-health-offline',
6679
+ },
6680
+ });
6681
+ } else {
6682
+ healthBody = `
6683
+ <div class="health-grid" role="list">
6684
+ ${checks.map(c => {
6685
+ const id = c.id || c.label || 'check';
6686
+ const meta = HEALTH_CHECK_META[id] || {
6687
+ okDetail: 'Check is passing.',
6688
+ failDetail: 'Check is failing. Review configuration for this item.',
6689
+ autofix: null,
6690
+ };
6691
+ const sev = healthCheckSeverity(c, healthStatus);
6692
+ const detail = c.ok ? meta.okDetail : meta.failDetail;
6693
+ const autofix = !c.ok && meta.autofix ? meta.autofix : null;
6694
+ const autofixBtn = autofix
6695
+ ? `<button type="button" class="health-autofix-btn" data-focus-key="health-autofix-${escapeAttr(id)}" onclick="event.stopPropagation();${copyForPasteHandler(autofix.text, autofix.subject, autofix.destination)}" title="${escapeAttr(copyActionTitle(autofix.subject, autofix.destination))}">${escapeHtml(autofix.label || 'Autofix')}</button>`
6696
+ : (c.ok
6697
+ ? `<span class="health-detail-copy" style="margin:0">No Autofix needed.</span>`
6698
+ : `<span class="health-detail-copy" style="margin:0">No Autofix mapped for this check.</span>`);
6699
+ return `
6700
+ <div class="health-card" role="listitem" id="health-card-${escapeAttr(id)}" data-check-id="${escapeAttr(id)}" data-severity="${escapeAttr(sev)}">
6701
+ <button type="button" class="health-item" aria-expanded="false" aria-controls="health-detail-${escapeAttr(id)}" aria-label="${escapeAttr(c.label)}: ${escapeAttr(healthSeverityLabel(sev))}" onclick="toggleHealthCheck('${escapeJsString(id)}')" onkeydown="healthCheckKeydown(event, '${escapeJsString(id)}')">
6702
+ <span class="dot ${healthDotClass(sev)}"></span>
6703
+ <span class="health-item-label">${escapeHtml(c.label)}</span>
6704
+ <span class="health-item-sev">${escapeHtml(healthSeverityLabel(sev))}</span>
6705
+ <span class="health-item-chevron" aria-hidden="true">▾</span>
6706
+ </button>
6707
+ <div class="health-detail" id="health-detail-${escapeAttr(id)}">
6708
+ <p class="health-detail-copy">${escapeHtml(detail)}</p>
6709
+ <div class="health-detail-actions">${autofixBtn}</div>
6710
+ </div>
6711
+ </div>`;
6712
+ }).join('')}
6713
+ </div>`;
6714
+ }
6715
+
6326
6716
  parts.push(`
6327
6717
  <div class="content-section" id="section-health">
6328
6718
  <div class="section-title">
6329
- <span class="dot dot-${health.status === 'ok' ? 'green' : health.status === 'warning' ? 'yellow' : 'red'}"></span>
6719
+ <span class="dot dot-${presenceTone}${healthStatus !== 'ok' ? ' dot-pulse' : ''}"></span>
6330
6720
  Health
6331
- <span class="section-subtitle">${checks.filter(c => c.ok).length}/${checks.length} checks passing</span>
6721
+ <span class="section-subtitle">${escapeHtml(subtitle)}</span>
6332
6722
  </div>
6333
- ${checks.length === 0
6334
- ? renderEmptyStateCta({
6335
- headline: 'Health offline',
6336
- support: 'No health data available.',
6337
- })
6338
- : ''}
6339
- <div class="health-grid">
6340
- ${checks.map(c => `
6341
- <div style="position:relative">
6342
- <div class="health-item" role="button" tabindex="0" aria-label="Show info for ${escapeAttr(c.label)}" onclick="showHealthInfo(this, ${c.ok})">
6343
- <span class="dot dot-${c.ok ? 'green' : 'red'}"></span>
6344
- <span>${escapeHtml(c.label)}</span>
6345
- </div>
6346
- </div>
6347
- `).join('')}
6723
+ <div class="healthcenter">
6724
+ <div class="healthcenter-presence" role="status" aria-live="polite">
6725
+ <span class="dot dot-${presenceTone}${presencePulse}"></span>
6726
+ <span>${escapeHtml(presenceLabel)}</span>
6727
+ <span>· same seven checks · copy-only Autofix</span>
6728
+ </div>
6729
+ ${healthBody}
6348
6730
  </div>
6349
6731
  </div>
6350
6732
  `);