neohive 6.0.3 → 6.1.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.
- package/CHANGELOG.md +262 -77
- package/README.md +66 -63
- package/SECURITY.md +8 -6
- package/cli.js +268 -33
- package/dashboard.html +2269 -546
- package/dashboard.js +492 -105
- package/design-system.css +708 -0
- package/design-system.html +264 -0
- package/lib/agents.js +20 -6
- package/lib/audit.js +417 -0
- package/lib/codex-neohive-toml.js +34 -0
- package/lib/github-sync.js +291 -0
- package/lib/hooks.js +173 -0
- package/lib/ide-activity.js +121 -0
- package/logo.svg +1 -0
- package/package.json +11 -2
- package/scripts/check-portable-paths.mjs +74 -0
- package/server.js +1148 -743
- package/tools/channels.js +116 -0
- package/tools/governance.js +471 -0
- package/tools/hooks.js +65 -0
- package/tools/knowledge.js +301 -0
- package/tools/messaging.js +321 -0
- package/tools/safety.js +144 -0
- package/tools/system.js +198 -0
- package/tools/tasks.js +446 -0
- package/tools/workflows.js +286 -0
package/dashboard.html
CHANGED
|
@@ -4,78 +4,116 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Neohive</title>
|
|
7
|
-
<link rel="icon" href="
|
|
7
|
+
<link rel="icon" href="favicon.png" type="image/png" sizes="16x16">
|
|
8
|
+
<link rel="icon" href="logo.svg" type="image/svg+xml">
|
|
8
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
11
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
12
|
+
<link rel="stylesheet" href="/design-system.css">
|
|
13
|
+
<!-- xterm.js dependencies now lazy-loaded on terminal tab activation to prevent dashboard stalling -->
|
|
11
14
|
<style>
|
|
12
15
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
13
16
|
|
|
17
|
+
/*
|
|
18
|
+
* Token fallbacks: specificity 0 via :where() so a loaded design-system.css :root
|
|
19
|
+
* and [data-theme="light"] still win. If /design-system.css is blocked (Safari
|
|
20
|
+
* extensions / networking), these keep cards, padding, and colors defined.
|
|
21
|
+
*/
|
|
22
|
+
:where(:root) {
|
|
23
|
+
--nh-space-1: 4px;
|
|
24
|
+
--nh-space-2: 8px;
|
|
25
|
+
--nh-space-3: 12px;
|
|
26
|
+
--nh-space-4: 16px;
|
|
27
|
+
--nh-space-5: 20px;
|
|
28
|
+
--nh-space-6: 24px;
|
|
29
|
+
--nh-space-8: 32px;
|
|
30
|
+
--nh-radius-sm: 6px;
|
|
31
|
+
--nh-radius-md: 8px;
|
|
32
|
+
--nh-radius-lg: 10px;
|
|
33
|
+
--nh-radius-xl: 12px;
|
|
34
|
+
--nh-bg: #0f0f10;
|
|
35
|
+
--nh-sidebar: #161618;
|
|
36
|
+
--nh-card: #1c1c1f;
|
|
37
|
+
--nh-card-hover: #232326;
|
|
38
|
+
--nh-border: #27272a;
|
|
39
|
+
--nh-text-primary: #f3f4f6;
|
|
40
|
+
--nh-text-secondary: #9ca3af;
|
|
41
|
+
--nh-text-muted: #6b7280;
|
|
42
|
+
--nh-accent: #f59e0b;
|
|
43
|
+
--nh-green: #10b981;
|
|
44
|
+
--nh-red: #ef4444;
|
|
45
|
+
--nh-orange: #f97316;
|
|
46
|
+
--nh-purple: #a78bfa;
|
|
47
|
+
--nh-yellow: #eab308;
|
|
48
|
+
--nh-divider: rgba(255, 255, 255, 0.08);
|
|
49
|
+
--nh-divider-strong: rgba(255, 255, 255, 0.14);
|
|
50
|
+
--nh-accent-5: rgba(245, 158, 11, 0.05);
|
|
51
|
+
--nh-accent-10: rgba(245, 158, 11, 0.1);
|
|
52
|
+
--nh-accent-20: rgba(245, 158, 11, 0.2);
|
|
53
|
+
--nh-green-10: rgba(16, 185, 129, 0.1);
|
|
54
|
+
--nh-red-10: rgba(239, 68, 68, 0.12);
|
|
55
|
+
--nh-orange-10: rgba(249, 115, 22, 0.12);
|
|
56
|
+
--nh-purple-10: rgba(167, 139, 250, 0.12);
|
|
57
|
+
--nh-purple-20: rgba(167, 139, 250, 0.22);
|
|
58
|
+
--nh-shadow-card: 0 4px 24px rgba(0, 0, 0, 0.35);
|
|
59
|
+
--nh-code-inline-fg: #f97316;
|
|
60
|
+
--nh-code-block-bg: #0f0f10;
|
|
61
|
+
--nh-on-dark: #ffffff;
|
|
62
|
+
--nh-on-accent: #3a2201;
|
|
63
|
+
--nh-blue: #3b82f6;
|
|
64
|
+
--nh-green-glow: 0 0 8px rgba(16, 185, 129, 0.5);
|
|
65
|
+
--nh-blue-glow: 0 0 8px rgba(59, 130, 246, 0.45);
|
|
66
|
+
}
|
|
67
|
+
|
|
14
68
|
:root {
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--surface
|
|
18
|
-
--surface-
|
|
19
|
-
--
|
|
20
|
-
--border
|
|
21
|
-
--
|
|
22
|
-
--text
|
|
23
|
-
--text-
|
|
24
|
-
--
|
|
25
|
-
--accent
|
|
26
|
-
--accent-
|
|
27
|
-
--
|
|
28
|
-
--green
|
|
29
|
-
--
|
|
30
|
-
--red
|
|
31
|
-
--
|
|
32
|
-
--orange
|
|
33
|
-
--
|
|
34
|
-
--purple
|
|
35
|
-
--
|
|
69
|
+
/* Bridge legacy dashboard tokens → design-system.css (--nh-*) */
|
|
70
|
+
--bg: var(--nh-bg);
|
|
71
|
+
--surface: var(--nh-sidebar);
|
|
72
|
+
--surface-2: var(--nh-card);
|
|
73
|
+
--surface-3: var(--nh-card-hover);
|
|
74
|
+
--border: var(--nh-divider);
|
|
75
|
+
--border-light: var(--nh-divider-strong);
|
|
76
|
+
--text: var(--nh-text-primary);
|
|
77
|
+
--text-dim: var(--nh-text-secondary);
|
|
78
|
+
--text-muted: var(--nh-text-muted);
|
|
79
|
+
--accent: var(--nh-accent);
|
|
80
|
+
--accent-dim: var(--nh-accent-10);
|
|
81
|
+
--accent-glow: var(--nh-accent-20);
|
|
82
|
+
--green: var(--nh-green);
|
|
83
|
+
--green-dim: var(--nh-green-10);
|
|
84
|
+
--red: var(--nh-red);
|
|
85
|
+
--red-dim: var(--nh-red-10);
|
|
86
|
+
--orange: var(--nh-orange);
|
|
87
|
+
--orange-dim: var(--nh-orange-10);
|
|
88
|
+
--purple: var(--nh-purple);
|
|
89
|
+
--purple-dim: var(--nh-purple-10);
|
|
90
|
+
--yellow: var(--nh-yellow);
|
|
91
|
+
--bg-primary: var(--nh-card);
|
|
92
|
+
--bg-secondary: var(--nh-sidebar);
|
|
93
|
+
--bg-tertiary: var(--nh-card-hover);
|
|
94
|
+
--text-primary: var(--nh-text-primary);
|
|
95
|
+
--text-secondary: var(--nh-text-secondary);
|
|
36
96
|
--text-xs: 12px;
|
|
37
97
|
--text-sm: 13px;
|
|
38
98
|
--text-base: 14px;
|
|
39
99
|
--text-lg: 16px;
|
|
40
100
|
--text-xl: 24px;
|
|
41
|
-
--sidebar-w:
|
|
42
|
-
--header-h:
|
|
43
|
-
--glow: 0 0 24px
|
|
101
|
+
--sidebar-w: 256px;
|
|
102
|
+
--header-h: 56px;
|
|
103
|
+
--glow: 0 0 24px var(--nh-accent-10);
|
|
44
104
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
|
|
45
105
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
46
106
|
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.6);
|
|
47
|
-
--gradient-accent: linear-gradient(135deg,
|
|
48
|
-
--gradient-surface: linear-gradient(180deg, rgba(255,255,255,0.02) 0%, transparent 100%);
|
|
107
|
+
--gradient-accent: linear-gradient(135deg, var(--nh-accent), var(--nh-orange));
|
|
108
|
+
--gradient-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.02) 0%, transparent 100%);
|
|
49
109
|
}
|
|
50
110
|
|
|
51
111
|
[data-theme="light"] {
|
|
52
|
-
--
|
|
53
|
-
--surface: #ffffff;
|
|
54
|
-
--surface-2: #f5f5f4;
|
|
55
|
-
--surface-3: #e7e5e4;
|
|
56
|
-
--border: rgba(0, 0, 0, 0.08);
|
|
57
|
-
--border-light: rgba(0, 0, 0, 0.12);
|
|
58
|
-
--text: #0c0a09;
|
|
59
|
-
--text-dim: #57534e;
|
|
60
|
-
--text-muted: #a8a29e;
|
|
61
|
-
--accent: #d97706;
|
|
62
|
-
--accent-dim: rgba(217, 119, 6, 0.08);
|
|
63
|
-
--accent-glow: rgba(217, 119, 6, 0.12);
|
|
64
|
-
--green: #16a34a;
|
|
65
|
-
--green-dim: rgba(22, 163, 74, 0.08);
|
|
66
|
-
--red: #dc2626;
|
|
67
|
-
--red-dim: rgba(220, 38, 38, 0.08);
|
|
68
|
-
--orange: #ea580c;
|
|
69
|
-
--orange-dim: rgba(234, 88, 12, 0.08);
|
|
70
|
-
--purple: #7c3aed;
|
|
71
|
-
--purple-dim: rgba(124, 58, 237, 0.08);
|
|
72
|
-
--yellow: #ca8a04;
|
|
73
|
-
--glow: 0 0 20px rgba(217, 119, 6, 0.05);
|
|
112
|
+
--glow: 0 0 20px var(--nh-accent-10);
|
|
74
113
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
75
114
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
76
115
|
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
77
|
-
--gradient-
|
|
78
|
-
--gradient-surface: linear-gradient(180deg, rgba(0,0,0,0.01) 0%, transparent 100%);
|
|
116
|
+
--gradient-surface: linear-gradient(180deg, rgba(0, 0, 0, 0.02) 0%, transparent 100%);
|
|
79
117
|
}
|
|
80
118
|
|
|
81
119
|
.theme-toggle {
|
|
@@ -117,10 +155,12 @@
|
|
|
117
155
|
|
|
118
156
|
/* ===== HEADER ===== */
|
|
119
157
|
.header {
|
|
120
|
-
background: var(--bg);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
158
|
+
background: var(--nh-glass-bg);
|
|
159
|
+
backdrop-filter: var(--nh-blur);
|
|
160
|
+
-webkit-backdrop-filter: var(--nh-blur);
|
|
161
|
+
border-bottom: 1px solid var(--nh-glass-border);
|
|
162
|
+
padding: 0 var(--nh-space-6);
|
|
163
|
+
height: var(--header-h);
|
|
124
164
|
display: flex;
|
|
125
165
|
align-items: center;
|
|
126
166
|
justify-content: space-between;
|
|
@@ -129,6 +169,7 @@
|
|
|
129
169
|
left: 0;
|
|
130
170
|
right: 0;
|
|
131
171
|
z-index: 100;
|
|
172
|
+
box-shadow: 0 1px 1px rgba(0,0,0,0.2);
|
|
132
173
|
}
|
|
133
174
|
|
|
134
175
|
[data-theme="light"] .header {
|
|
@@ -142,8 +183,9 @@
|
|
|
142
183
|
}
|
|
143
184
|
|
|
144
185
|
.logo {
|
|
145
|
-
font-size:
|
|
186
|
+
font-size: 20px;
|
|
146
187
|
font-weight: 700;
|
|
188
|
+
letter-spacing: -0.01em;
|
|
147
189
|
color: var(--text);
|
|
148
190
|
white-space: nowrap;
|
|
149
191
|
letter-spacing: -0.3px;
|
|
@@ -164,7 +206,16 @@
|
|
|
164
206
|
.header-actions {
|
|
165
207
|
display: flex;
|
|
166
208
|
align-items: center;
|
|
167
|
-
gap:
|
|
209
|
+
gap: 10px;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.header-conn {
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
215
|
+
gap: 8px;
|
|
216
|
+
font-size: 13px;
|
|
217
|
+
color: var(--text-dim);
|
|
218
|
+
margin-right: 4px;
|
|
168
219
|
}
|
|
169
220
|
|
|
170
221
|
.connection {
|
|
@@ -177,11 +228,11 @@
|
|
|
177
228
|
}
|
|
178
229
|
|
|
179
230
|
.conn-dot {
|
|
180
|
-
width:
|
|
181
|
-
height:
|
|
231
|
+
width: 8px;
|
|
232
|
+
height: 8px;
|
|
182
233
|
border-radius: 50%;
|
|
183
234
|
background: var(--green);
|
|
184
|
-
box-shadow:
|
|
235
|
+
box-shadow: var(--nh-green-glow);
|
|
185
236
|
animation: pulse 2s infinite;
|
|
186
237
|
}
|
|
187
238
|
|
|
@@ -232,13 +283,15 @@
|
|
|
232
283
|
.sidebar {
|
|
233
284
|
width: var(--sidebar-w);
|
|
234
285
|
min-width: var(--sidebar-w);
|
|
235
|
-
background: var(--
|
|
236
|
-
|
|
237
|
-
|
|
286
|
+
background: var(--nh-glass-bg);
|
|
287
|
+
backdrop-filter: var(--nh-blur);
|
|
288
|
+
-webkit-backdrop-filter: var(--nh-blur);
|
|
289
|
+
border-right: 1px solid var(--nh-glass-border);
|
|
238
290
|
display: flex;
|
|
239
291
|
flex-direction: column;
|
|
240
292
|
overflow: hidden;
|
|
241
293
|
transition: width 0.25s ease, min-width 0.25s ease, margin-left 0.25s ease;
|
|
294
|
+
box-shadow: 1px 0 0 rgba(0,0,0,0.1);
|
|
242
295
|
}
|
|
243
296
|
.sidebar.collapsed {
|
|
244
297
|
width: 0;
|
|
@@ -348,24 +401,40 @@
|
|
|
348
401
|
/* ===== AGENT CARDS ===== */
|
|
349
402
|
.agent-card {
|
|
350
403
|
background: var(--surface-2);
|
|
351
|
-
background-image: var(--gradient-surface);
|
|
352
404
|
border: 1px solid var(--border);
|
|
353
405
|
border-radius: 8px;
|
|
354
|
-
padding:
|
|
355
|
-
margin-bottom:
|
|
356
|
-
transition: all 0.
|
|
406
|
+
padding: 8px 12px;
|
|
407
|
+
margin-bottom: 6px;
|
|
408
|
+
transition: all 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
357
409
|
cursor: pointer;
|
|
410
|
+
position: relative;
|
|
411
|
+
overflow: hidden;
|
|
358
412
|
}
|
|
359
413
|
|
|
360
|
-
.agent-card:hover {
|
|
414
|
+
.agent-card:hover {
|
|
415
|
+
transform: translateX(4px);
|
|
416
|
+
border-color: var(--nh-accent-50);
|
|
417
|
+
background: var(--surface-3);
|
|
418
|
+
box-shadow: var(--shadow-sm);
|
|
419
|
+
}
|
|
361
420
|
|
|
362
|
-
.agent-card.
|
|
363
|
-
border-left: 3px solid var(--
|
|
421
|
+
.agent-card.working, .agent-card.active {
|
|
422
|
+
border-left: 3px solid var(--green);
|
|
423
|
+
box-shadow: 0 0 15px var(--green-dim);
|
|
424
|
+
animation: nh-glow-pulse 2s infinite ease-in-out;
|
|
364
425
|
}
|
|
365
426
|
|
|
366
427
|
.agent-card.dead, .agent-card.offline {
|
|
367
428
|
opacity: 0.5;
|
|
368
429
|
}
|
|
430
|
+
.agent-card.unknown {
|
|
431
|
+
opacity: 0.4;
|
|
432
|
+
border-left: 3px solid var(--nh-text-muted);
|
|
433
|
+
}
|
|
434
|
+
.agent-card.stale {
|
|
435
|
+
opacity: 0.65;
|
|
436
|
+
border-left: 3px solid var(--orange);
|
|
437
|
+
}
|
|
369
438
|
|
|
370
439
|
.agent-top {
|
|
371
440
|
display: flex;
|
|
@@ -394,10 +463,22 @@
|
|
|
394
463
|
border-radius: 50%;
|
|
395
464
|
flex-shrink: 0;
|
|
396
465
|
}
|
|
397
|
-
.agent-status-dot.active, .agent-status-dot.working { background:
|
|
398
|
-
.agent-status-dot.listening { background:
|
|
399
|
-
.agent-status-dot.sleeping, .agent-status-dot.idle { background:
|
|
400
|
-
.agent-status-dot.dead, .agent-status-dot.offline { background:
|
|
466
|
+
.agent-status-dot.active, .agent-status-dot.working { background: var(--green); box-shadow: var(--nh-green-glow); }
|
|
467
|
+
.agent-status-dot.listening { background: var(--nh-blue); box-shadow: 0 0 6px var(--nh-blue); animation: pulse 2s infinite; }
|
|
468
|
+
.agent-status-dot.sleeping, .agent-status-dot.idle { background: var(--nh-accent); animation: pulse 2s infinite; }
|
|
469
|
+
.agent-status-dot.dead, .agent-status-dot.offline { background: var(--nh-text-muted); }
|
|
470
|
+
.agent-status-dot.unknown { background: var(--nh-text-muted); opacity: 0.5; }
|
|
471
|
+
.agent-status-dot.stale { background: var(--orange); animation: pulse 2s infinite; }
|
|
472
|
+
.agent-status-dot.unresponsive { background: var(--orange, #f97316); box-shadow: 0 0 6px var(--orange, #f97316); animation: pulse 2s infinite; }
|
|
473
|
+
.agent-status-dot.stuck { background: var(--red, #ef4444); box-shadow: 0 0 8px var(--red, #ef4444); animation: pulse 1.2s infinite; }
|
|
474
|
+
|
|
475
|
+
.agent-state-pill { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 99px; font-size: 10px; font-weight: 600; letter-spacing: 0.02em; line-height: 16px; flex-shrink: 0; }
|
|
476
|
+
.agent-state-pill.listening { background: rgba(96,165,250,0.15); color: #60a5fa; }
|
|
477
|
+
.agent-state-pill.working { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
|
478
|
+
.agent-current-tool { font-size: 10px; color: var(--nh-text-muted, #6b7280); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
|
|
479
|
+
|
|
480
|
+
.agent-card.stuck { border-color: rgba(239, 68, 68, 0.3); }
|
|
481
|
+
.agent-card.unresponsive { border-color: rgba(249, 115, 22, 0.3); }
|
|
401
482
|
|
|
402
483
|
.agent-name {
|
|
403
484
|
font-weight: 600;
|
|
@@ -415,6 +496,147 @@
|
|
|
415
496
|
text-overflow: ellipsis;
|
|
416
497
|
}
|
|
417
498
|
|
|
499
|
+
/* ===== AGENT SKILLS ===== */
|
|
500
|
+
.agent-skills {
|
|
501
|
+
display: flex;
|
|
502
|
+
flex-wrap: wrap;
|
|
503
|
+
gap: 4px;
|
|
504
|
+
margin-top: 6px;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.skill-tag {
|
|
508
|
+
font-size: 9px;
|
|
509
|
+
padding: 1px 5px;
|
|
510
|
+
border-radius: 4px;
|
|
511
|
+
background: var(--surface-3);
|
|
512
|
+
color: var(--text-dim);
|
|
513
|
+
border: 1px solid var(--border);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* ===== NUDGE & SPARKLINE ===== */
|
|
517
|
+
.agent-nudge-btn {
|
|
518
|
+
position: absolute;
|
|
519
|
+
top: 8px;
|
|
520
|
+
right: 8px;
|
|
521
|
+
width: 24px;
|
|
522
|
+
height: 24px;
|
|
523
|
+
border-radius: 6px;
|
|
524
|
+
background: var(--nh-red-10);
|
|
525
|
+
border: 1px solid var(--nh-red);
|
|
526
|
+
color: var(--nh-red);
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
justify-content: center;
|
|
530
|
+
cursor: pointer;
|
|
531
|
+
opacity: 0;
|
|
532
|
+
transition: all 0.2s ease;
|
|
533
|
+
z-index: 10;
|
|
534
|
+
}
|
|
535
|
+
.agent-card:hover .agent-nudge-btn { opacity: 1; }
|
|
536
|
+
.agent-nudge-btn:hover { background: var(--nh-red); color: #fff; transform: scale(1.1); box-shadow: 0 0 10px rgba(239, 68, 68, 0.4); }
|
|
537
|
+
|
|
538
|
+
.listen-sparkline {
|
|
539
|
+
display: flex;
|
|
540
|
+
gap: 3px;
|
|
541
|
+
margin-top: 8px;
|
|
542
|
+
padding-top: 4px;
|
|
543
|
+
border-top: 1px solid var(--nh-divider);
|
|
544
|
+
align-items: center;
|
|
545
|
+
}
|
|
546
|
+
.listen-sparkline-label {
|
|
547
|
+
font-size: 9px;
|
|
548
|
+
color: var(--text-muted);
|
|
549
|
+
text-transform: uppercase;
|
|
550
|
+
font-weight: 700;
|
|
551
|
+
margin-right: 4px;
|
|
552
|
+
letter-spacing: 0.5px;
|
|
553
|
+
}
|
|
554
|
+
.hb-dot {
|
|
555
|
+
width: 6px;
|
|
556
|
+
height: 6px;
|
|
557
|
+
border-radius: 50%;
|
|
558
|
+
background: var(--nh-divider-strong);
|
|
559
|
+
transition: all 0.3s ease;
|
|
560
|
+
}
|
|
561
|
+
.hb-dot.active {
|
|
562
|
+
background: var(--nh-green);
|
|
563
|
+
box-shadow: 0 0 6px var(--nh-green);
|
|
564
|
+
}
|
|
565
|
+
.hb-dot.stale { background: var(--nh-orange); opacity: 0.5; }
|
|
566
|
+
.hb-dot.empty { background: transparent; border: 1px solid var(--nh-divider); }
|
|
567
|
+
|
|
568
|
+
@keyframes stuck-pulse {
|
|
569
|
+
0% { border-color: var(--nh-divider); }
|
|
570
|
+
50% { border-color: var(--nh-red); box-shadow: inset 0 0 10px var(--nh-red-10); }
|
|
571
|
+
100% { border-color: var(--nh-divider); }
|
|
572
|
+
}
|
|
573
|
+
.agent-card.stuck {
|
|
574
|
+
animation: stuck-pulse 1.5s infinite ease-in-out;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/* Platform colors */
|
|
578
|
+
.platform-claude { background: var(--nh-accent-10); color: var(--nh-accent); border-color: var(--nh-accent-20); }
|
|
579
|
+
.platform-gemini { background: rgba(59, 130, 246, 0.1); color: var(--nh-blue); border-color: rgba(59, 130, 246, 0.2); }
|
|
580
|
+
.platform-openai { background: var(--nh-green-10); color: var(--nh-green); border-color: var(--nh-green-20); }
|
|
581
|
+
.platform-deepseek { background: var(--nh-purple-10); color: var(--nh-purple); border-color: var(--nh-purple-20); }
|
|
582
|
+
.platform-llama { background: var(--nh-orange-10); color: var(--nh-orange); border-color: var(--nh-orange-20); }
|
|
583
|
+
.platform-mistral { background: var(--nh-red-10); color: var(--nh-red); border-color: var(--nh-red-20); }
|
|
584
|
+
|
|
585
|
+
/* Skill Editor in Profile */
|
|
586
|
+
.skill-editor {
|
|
587
|
+
display: flex;
|
|
588
|
+
flex-wrap: wrap;
|
|
589
|
+
gap: 6px;
|
|
590
|
+
margin-top: 12px;
|
|
591
|
+
padding: 12px;
|
|
592
|
+
background: var(--surface-2);
|
|
593
|
+
border-radius: 8px;
|
|
594
|
+
border: 1px solid var(--border);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.skill-editor-tag {
|
|
598
|
+
display: flex;
|
|
599
|
+
align-items: center;
|
|
600
|
+
gap: 6px;
|
|
601
|
+
font-size: 11px;
|
|
602
|
+
font-weight: 600;
|
|
603
|
+
padding: 4px 8px;
|
|
604
|
+
background: var(--surface-3);
|
|
605
|
+
border: 1px solid var(--border);
|
|
606
|
+
border-radius: 6px;
|
|
607
|
+
color: var(--text);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.remove-skill-btn {
|
|
611
|
+
cursor: pointer;
|
|
612
|
+
opacity: 0.6;
|
|
613
|
+
transition: opacity 0.2s;
|
|
614
|
+
font-size: 14px;
|
|
615
|
+
line-height: 1;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.remove-skill-btn:hover { opacity: 1; color: var(--red); }
|
|
619
|
+
|
|
620
|
+
.add-skill-box {
|
|
621
|
+
display: flex;
|
|
622
|
+
gap: 6px;
|
|
623
|
+
width: 100%;
|
|
624
|
+
margin-top: 8px;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.add-skill-input {
|
|
628
|
+
flex: 1;
|
|
629
|
+
background: var(--bg);
|
|
630
|
+
border: 1px solid var(--border);
|
|
631
|
+
border-radius: 6px;
|
|
632
|
+
padding: 4px 10px;
|
|
633
|
+
font-size: 12px;
|
|
634
|
+
color: var(--text);
|
|
635
|
+
outline: none;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.add-skill-input:focus { border-color: var(--accent); }
|
|
639
|
+
|
|
418
640
|
.nudge-btn {
|
|
419
641
|
background: var(--orange-dim);
|
|
420
642
|
color: var(--orange);
|
|
@@ -638,37 +860,88 @@
|
|
|
638
860
|
flex-direction: column;
|
|
639
861
|
min-width: 0;
|
|
640
862
|
overflow: hidden;
|
|
863
|
+
background: var(--nh-bg);
|
|
641
864
|
}
|
|
642
865
|
|
|
643
|
-
/* ===== MESSAGES ===== */
|
|
866
|
+
/* ===== MESSAGES (card feed — nh-msg-card from design-system.css) ===== */
|
|
644
867
|
.messages-area {
|
|
645
868
|
flex: 1;
|
|
646
869
|
overflow-y: auto;
|
|
870
|
+
-webkit-overflow-scrolling: touch;
|
|
647
871
|
padding: 16px 20px;
|
|
872
|
+
padding: var(--nh-space-4) var(--nh-space-5);
|
|
873
|
+
display: -webkit-flex;
|
|
648
874
|
display: flex;
|
|
875
|
+
-webkit-flex-direction: column;
|
|
649
876
|
flex-direction: column;
|
|
650
|
-
gap
|
|
877
|
+
/* margin between rows — do not rely on flex gap alone (Safari 14.0 reports @supports(gap) true for grid while flex had no gap) */
|
|
651
878
|
}
|
|
652
879
|
|
|
653
|
-
.
|
|
880
|
+
.messages-area > * + * {
|
|
881
|
+
margin-top: 12px;
|
|
882
|
+
margin-top: var(--nh-space-3, 12px);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/*
|
|
886
|
+
* Card chrome duplicated here (higher specificity than .nh-msg-card alone) with
|
|
887
|
+
* px fallbacks so messages stay “in cards” even if external CSS or vars fail.
|
|
888
|
+
* Horizontal spacing uses margin, not flex gap, for the same Safari reason.
|
|
889
|
+
*/
|
|
890
|
+
.message.nh-msg-card {
|
|
891
|
+
display: -webkit-flex;
|
|
654
892
|
display: flex;
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
893
|
+
-webkit-flex-direction: row;
|
|
894
|
+
flex-direction: row;
|
|
895
|
+
-webkit-align-items: flex-start;
|
|
896
|
+
align-items: flex-start;
|
|
897
|
+
box-sizing: border-box;
|
|
898
|
+
padding: 16px 20px;
|
|
899
|
+
padding: var(--nh-space-4) var(--nh-space-5);
|
|
900
|
+
background-color: #1c1c1f;
|
|
901
|
+
background-color: var(--nh-card, #1c1c1f);
|
|
902
|
+
border: 1px solid #27272a;
|
|
903
|
+
border-color: var(--nh-border, #27272a);
|
|
904
|
+
border-radius: 12px;
|
|
905
|
+
border-radius: var(--nh-radius-xl, 12px);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.message.nh-msg-card > .msg-avatar + .msg-body,
|
|
909
|
+
.message.nh-msg-card > .msg-avatar-img + .msg-body {
|
|
910
|
+
margin-left: 12px;
|
|
911
|
+
margin-left: var(--nh-space-3, 12px);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.message.nh-msg-card:hover {
|
|
915
|
+
border-color: rgba(156, 163, 175, 0.45);
|
|
916
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
|
|
917
|
+
box-shadow: var(--nh-shadow-card, 0 4px 24px rgba(0, 0, 0, 0.35));
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
.message {
|
|
659
921
|
position: relative;
|
|
922
|
+
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
|
923
|
+
overflow: visible;
|
|
660
924
|
}
|
|
661
925
|
|
|
662
|
-
.message
|
|
926
|
+
.message .msg-actions {
|
|
927
|
+
top: -10px;
|
|
928
|
+
right: var(--nh-space-3);
|
|
929
|
+
}
|
|
663
930
|
|
|
664
931
|
/* ===== REACTIONS ===== */
|
|
665
932
|
.msg-actions {
|
|
666
933
|
position: absolute;
|
|
667
|
-
top:
|
|
934
|
+
top: -10px;
|
|
668
935
|
right: 6px;
|
|
669
936
|
display: none;
|
|
670
937
|
gap: 2px;
|
|
671
938
|
align-items: center;
|
|
939
|
+
z-index: 2;
|
|
940
|
+
background: var(--nh-card, #1c1c1f);
|
|
941
|
+
border: 1px solid var(--nh-border, #27272a);
|
|
942
|
+
border-radius: var(--nh-radius-md, 8px);
|
|
943
|
+
padding: 2px 4px;
|
|
944
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
|
672
945
|
}
|
|
673
946
|
|
|
674
947
|
.message:hover .msg-actions { display: flex; }
|
|
@@ -693,11 +966,11 @@
|
|
|
693
966
|
position: absolute;
|
|
694
967
|
top: -4px;
|
|
695
968
|
right: 56px;
|
|
696
|
-
background: var(--
|
|
969
|
+
background: var(--nh-card);
|
|
697
970
|
backdrop-filter: blur(12px);
|
|
698
971
|
-webkit-backdrop-filter: blur(12px);
|
|
699
|
-
border: 1px solid var(--border
|
|
700
|
-
border-radius:
|
|
972
|
+
border: 1px solid var(--nh-border);
|
|
973
|
+
border-radius: var(--nh-radius-lg);
|
|
701
974
|
padding: 4px;
|
|
702
975
|
gap: 2px;
|
|
703
976
|
z-index: 20;
|
|
@@ -717,7 +990,7 @@
|
|
|
717
990
|
line-height: 1;
|
|
718
991
|
}
|
|
719
992
|
|
|
720
|
-
.react-emoji:hover { background: var(--
|
|
993
|
+
.react-emoji:hover { background: var(--nh-card-hover); }
|
|
721
994
|
|
|
722
995
|
.msg-reactions {
|
|
723
996
|
display: flex;
|
|
@@ -730,9 +1003,9 @@
|
|
|
730
1003
|
display: inline-flex;
|
|
731
1004
|
align-items: center;
|
|
732
1005
|
gap: 3px;
|
|
733
|
-
background: var(--
|
|
734
|
-
border: 1px solid var(--border);
|
|
735
|
-
border-radius:
|
|
1006
|
+
background: var(--nh-card-hover);
|
|
1007
|
+
border: 1px solid var(--nh-border);
|
|
1008
|
+
border-radius: var(--nh-radius-lg);
|
|
736
1009
|
padding: 1px 6px;
|
|
737
1010
|
font-size: 12px;
|
|
738
1011
|
cursor: pointer;
|
|
@@ -768,22 +1041,29 @@
|
|
|
768
1041
|
|
|
769
1042
|
.pinned-toggle { font-size: 11px; color: var(--accent); }
|
|
770
1043
|
|
|
771
|
-
.pinned-section .message {
|
|
1044
|
+
.pinned-section .message.nh-msg-card {
|
|
1045
|
+
padding: var(--nh-space-2) var(--nh-space-4);
|
|
1046
|
+
margin-bottom: var(--nh-space-2);
|
|
1047
|
+
}
|
|
1048
|
+
.pinned-section .message.nh-msg-card:last-child { margin-bottom: 0; }
|
|
772
1049
|
.pinned-section .msg-content { font-size: 12px; max-height: 40px; overflow: hidden; }
|
|
773
1050
|
|
|
774
1051
|
.bookmark-btn {
|
|
775
1052
|
position: absolute;
|
|
776
|
-
top:
|
|
777
|
-
right:
|
|
778
|
-
background:
|
|
779
|
-
border:
|
|
1053
|
+
top: -10px;
|
|
1054
|
+
right: 40px;
|
|
1055
|
+
background: var(--nh-card, #1c1c1f);
|
|
1056
|
+
border: 1px solid var(--nh-border, #27272a);
|
|
1057
|
+
border-radius: var(--nh-radius-md, 8px);
|
|
780
1058
|
cursor: pointer;
|
|
781
1059
|
font-size: 14px;
|
|
782
1060
|
opacity: 0;
|
|
783
1061
|
transition: opacity 0.15s;
|
|
784
1062
|
color: var(--text-muted);
|
|
785
|
-
padding: 2px;
|
|
1063
|
+
padding: 2px 4px;
|
|
786
1064
|
line-height: 1;
|
|
1065
|
+
z-index: 2;
|
|
1066
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
|
787
1067
|
}
|
|
788
1068
|
|
|
789
1069
|
.message:hover .bookmark-btn { opacity: 0.6; }
|
|
@@ -791,21 +1071,81 @@
|
|
|
791
1071
|
.bookmark-btn.active { opacity: 1 !important; color: var(--yellow); }
|
|
792
1072
|
|
|
793
1073
|
.msg-avatar {
|
|
794
|
-
width:
|
|
795
|
-
height:
|
|
796
|
-
border-radius:
|
|
1074
|
+
width: 36px;
|
|
1075
|
+
height: 36px;
|
|
1076
|
+
border-radius: 9999px;
|
|
797
1077
|
display: flex;
|
|
798
1078
|
align-items: center;
|
|
799
1079
|
justify-content: center;
|
|
800
1080
|
font-weight: 700;
|
|
801
1081
|
font-size: 13px;
|
|
802
1082
|
flex-shrink: 0;
|
|
803
|
-
color:
|
|
1083
|
+
color: var(--nh-on-dark);
|
|
804
1084
|
margin-top: 2px;
|
|
1085
|
+
border: 1px solid var(--nh-border);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/* min-height:0 helps WebKit flex descendants size correctly; overflow visible avoids clipping receipts/shadows */
|
|
1089
|
+
.msg-body { flex: 1; min-width: 0; min-height: 0; overflow: visible; }
|
|
1090
|
+
|
|
1091
|
+
.msg-card-head {
|
|
1092
|
+
display: flex;
|
|
1093
|
+
align-items: flex-start;
|
|
1094
|
+
justify-content: space-between;
|
|
1095
|
+
gap: var(--nh-space-4);
|
|
1096
|
+
margin-bottom: var(--nh-space-2);
|
|
1097
|
+
width: 100%;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
.msg-card-ident {
|
|
1101
|
+
min-width: 0;
|
|
1102
|
+
flex: 1;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.msg-from {
|
|
1106
|
+
font-weight: 600;
|
|
1107
|
+
font-size: 14px;
|
|
1108
|
+
line-height: 1.3;
|
|
1109
|
+
color: var(--nh-text-primary);
|
|
805
1110
|
}
|
|
806
1111
|
|
|
807
|
-
.msg-
|
|
1112
|
+
.msg-to-line {
|
|
1113
|
+
font-size: 12px;
|
|
1114
|
+
color: var(--nh-text-secondary);
|
|
1115
|
+
margin-top: var(--nh-space-1);
|
|
1116
|
+
line-height: 1.35;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.msg-to-label {
|
|
1120
|
+
color: var(--nh-text-muted);
|
|
1121
|
+
margin-right: var(--nh-space-1);
|
|
1122
|
+
font-weight: 500;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.msg-card-head-right {
|
|
1126
|
+
display: flex;
|
|
1127
|
+
flex-direction: column;
|
|
1128
|
+
align-items: flex-end;
|
|
1129
|
+
gap: var(--nh-space-1);
|
|
1130
|
+
flex-shrink: 0;
|
|
1131
|
+
text-align: right;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
.msg-time {
|
|
1135
|
+
font-size: 12px;
|
|
1136
|
+
color: var(--nh-text-muted);
|
|
1137
|
+
font-variant-numeric: tabular-nums;
|
|
1138
|
+
white-space: nowrap;
|
|
1139
|
+
}
|
|
808
1140
|
|
|
1141
|
+
.msg-badges {
|
|
1142
|
+
display: flex;
|
|
1143
|
+
flex-wrap: wrap;
|
|
1144
|
+
gap: 4px;
|
|
1145
|
+
justify-content: flex-end;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/* Legacy row layout (compact / grouped fallbacks) */
|
|
809
1149
|
.msg-header {
|
|
810
1150
|
display: flex;
|
|
811
1151
|
align-items: baseline;
|
|
@@ -814,10 +1154,8 @@
|
|
|
814
1154
|
flex-wrap: wrap;
|
|
815
1155
|
}
|
|
816
1156
|
|
|
817
|
-
.msg-
|
|
818
|
-
.msg-
|
|
819
|
-
.msg-to { font-size: 13px; color: var(--text-dim); }
|
|
820
|
-
.msg-time { font-size: 11px; color: var(--text-muted); }
|
|
1157
|
+
.msg-arrow { color: var(--nh-text-muted); font-size: 12px; }
|
|
1158
|
+
.msg-to { font-size: 13px; color: var(--nh-text-secondary); }
|
|
821
1159
|
|
|
822
1160
|
.badge-channel {
|
|
823
1161
|
background: var(--purple-dim);
|
|
@@ -884,56 +1222,58 @@
|
|
|
884
1222
|
font-size: 14px;
|
|
885
1223
|
line-height: 1.6;
|
|
886
1224
|
word-break: break-word;
|
|
887
|
-
color: var(--text);
|
|
1225
|
+
color: var(--nh-text-primary);
|
|
888
1226
|
}
|
|
889
1227
|
|
|
890
|
-
.msg-content h1 { font-size: 22px; font-weight: 700; margin: 12px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--border); }
|
|
891
|
-
.msg-content h2 { font-size: 18px; font-weight: 700; margin: 10px 0 5px; padding-bottom: 3px; border-bottom: 1px solid var(--border); }
|
|
1228
|
+
.msg-content h1 { font-size: 22px; font-weight: 700; margin: 12px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--nh-border); }
|
|
1229
|
+
.msg-content h2 { font-size: 18px; font-weight: 700; margin: 10px 0 5px; padding-bottom: 3px; border-bottom: 1px solid var(--nh-border); }
|
|
892
1230
|
.msg-content h3 { font-size: 16px; font-weight: 600; margin: 8px 0 4px; }
|
|
893
1231
|
.msg-content h4 { font-size: 15px; font-weight: 600; margin: 6px 0 3px; }
|
|
894
1232
|
|
|
895
1233
|
.msg-content p { margin: 4px 0; }
|
|
896
1234
|
|
|
897
1235
|
.msg-content strong { font-weight: 700; }
|
|
898
|
-
.msg-content em { font-style: italic; color: var(--text-
|
|
1236
|
+
.msg-content em { font-style: italic; color: var(--nh-text-secondary); }
|
|
899
1237
|
|
|
900
1238
|
.msg-content code {
|
|
901
|
-
background: var(--
|
|
902
|
-
padding:
|
|
903
|
-
border-radius:
|
|
1239
|
+
background: var(--nh-card-hover);
|
|
1240
|
+
padding: 2px 6px;
|
|
1241
|
+
border-radius: var(--nh-radius-sm);
|
|
904
1242
|
font-size: 12px;
|
|
905
1243
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
|
906
|
-
color: var(--
|
|
1244
|
+
color: var(--nh-code-inline-fg);
|
|
1245
|
+
border: 1px solid var(--nh-border);
|
|
907
1246
|
}
|
|
908
1247
|
|
|
909
1248
|
.msg-content pre {
|
|
910
|
-
background: var(--bg);
|
|
911
|
-
border: 1px solid var(--border);
|
|
912
|
-
border-radius:
|
|
913
|
-
padding:
|
|
914
|
-
margin:
|
|
1249
|
+
background: var(--nh-code-block-bg);
|
|
1250
|
+
border: 1px solid var(--nh-border);
|
|
1251
|
+
border-radius: var(--nh-radius-md);
|
|
1252
|
+
padding: var(--nh-space-3) var(--nh-space-4);
|
|
1253
|
+
margin: var(--nh-space-2) 0;
|
|
915
1254
|
overflow-x: auto;
|
|
916
1255
|
font-size: 12px;
|
|
917
1256
|
line-height: 1.6;
|
|
918
1257
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
|
919
1258
|
position: relative;
|
|
920
|
-
box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
|
|
921
1259
|
}
|
|
922
1260
|
|
|
923
1261
|
.msg-content pre code {
|
|
924
1262
|
background: none;
|
|
925
1263
|
padding: 0;
|
|
926
|
-
|
|
1264
|
+
border: none;
|
|
1265
|
+
color: var(--nh-text-primary);
|
|
927
1266
|
font-size: inherit;
|
|
928
1267
|
}
|
|
929
1268
|
|
|
930
1269
|
.msg-content pre .lang-tag {
|
|
931
1270
|
position: absolute;
|
|
932
|
-
top:
|
|
933
|
-
right:
|
|
1271
|
+
top: 6px;
|
|
1272
|
+
right: var(--nh-space-2);
|
|
934
1273
|
font-size: 10px;
|
|
935
|
-
color: var(--text-muted);
|
|
1274
|
+
color: var(--nh-text-muted);
|
|
936
1275
|
text-transform: uppercase;
|
|
1276
|
+
font-weight: 600;
|
|
937
1277
|
}
|
|
938
1278
|
|
|
939
1279
|
.msg-content ul, .msg-content ol {
|
|
@@ -946,15 +1286,15 @@
|
|
|
946
1286
|
}
|
|
947
1287
|
|
|
948
1288
|
.msg-content blockquote {
|
|
949
|
-
border-left: 3px solid var(--border
|
|
1289
|
+
border-left: 3px solid var(--nh-border);
|
|
950
1290
|
padding: 2px 12px;
|
|
951
1291
|
margin: 4px 0;
|
|
952
|
-
color: var(--text-
|
|
1292
|
+
color: var(--nh-text-secondary);
|
|
953
1293
|
}
|
|
954
1294
|
|
|
955
1295
|
.msg-content hr {
|
|
956
1296
|
border: none;
|
|
957
|
-
border-top: 1px solid var(--border);
|
|
1297
|
+
border-top: 1px solid var(--nh-border);
|
|
958
1298
|
margin: 8px 0;
|
|
959
1299
|
}
|
|
960
1300
|
|
|
@@ -966,18 +1306,18 @@
|
|
|
966
1306
|
}
|
|
967
1307
|
|
|
968
1308
|
.msg-content th, .msg-content td {
|
|
969
|
-
border: 1px solid var(--border);
|
|
1309
|
+
border: 1px solid var(--nh-border);
|
|
970
1310
|
padding: 5px 10px;
|
|
971
1311
|
text-align: left;
|
|
972
1312
|
}
|
|
973
1313
|
|
|
974
1314
|
.msg-content th {
|
|
975
|
-
background: var(--
|
|
1315
|
+
background: var(--nh-card-hover);
|
|
976
1316
|
font-weight: 600;
|
|
977
1317
|
}
|
|
978
1318
|
|
|
979
1319
|
.msg-content a {
|
|
980
|
-
color: var(--accent);
|
|
1320
|
+
color: var(--nh-accent);
|
|
981
1321
|
text-decoration: none;
|
|
982
1322
|
}
|
|
983
1323
|
|
|
@@ -988,6 +1328,7 @@
|
|
|
988
1328
|
display: flex;
|
|
989
1329
|
gap: 2px;
|
|
990
1330
|
margin-top: 4px;
|
|
1331
|
+
margin-bottom: 2px;
|
|
991
1332
|
align-items: center;
|
|
992
1333
|
}
|
|
993
1334
|
.read-receipt-dot {
|
|
@@ -1066,6 +1407,7 @@
|
|
|
1066
1407
|
font-size: 12px;
|
|
1067
1408
|
font-family: inherit;
|
|
1068
1409
|
resize: none;
|
|
1410
|
+
width: 100%;
|
|
1069
1411
|
height: 34px;
|
|
1070
1412
|
max-height: 80px;
|
|
1071
1413
|
outline: none;
|
|
@@ -1125,46 +1467,33 @@
|
|
|
1125
1467
|
[data-theme="light"] ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); }
|
|
1126
1468
|
[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.2); }
|
|
1127
1469
|
|
|
1128
|
-
/* ===== SCROLL TO BOTTOM ===== */
|
|
1470
|
+
/* ===== SCROLL TO BOTTOM PILL ===== */
|
|
1129
1471
|
.scroll-bottom {
|
|
1130
1472
|
position: absolute;
|
|
1131
1473
|
bottom: 80px;
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1474
|
+
left: 50%;
|
|
1475
|
+
transform: translateX(-50%);
|
|
1476
|
+
background: var(--surface);
|
|
1477
|
+
background-image: var(--gradient-surface);
|
|
1478
|
+
color: var(--text);
|
|
1479
|
+
border: 1px solid var(--border-light);
|
|
1480
|
+
border-radius: 20px;
|
|
1481
|
+
padding: 8px 16px;
|
|
1139
1482
|
display: none;
|
|
1140
1483
|
align-items: center;
|
|
1141
1484
|
justify-content: center;
|
|
1142
1485
|
cursor: pointer;
|
|
1143
|
-
font-size:
|
|
1144
|
-
|
|
1486
|
+
font-size: 13px;
|
|
1487
|
+
font-weight: 600;
|
|
1488
|
+
box-shadow: var(--shadow-lg), 0 4px 16px rgba(0,0,0,0.1);
|
|
1489
|
+
backdrop-filter: blur(8px);
|
|
1490
|
+
-webkit-backdrop-filter: blur(8px);
|
|
1145
1491
|
z-index: 10;
|
|
1146
1492
|
transition: all 0.25s ease;
|
|
1147
1493
|
}
|
|
1148
1494
|
|
|
1149
|
-
.scroll-bottom.visible { display: flex; }
|
|
1150
|
-
.scroll-bottom:hover { transform: translateY(-2px); box-shadow: 0 6px
|
|
1151
|
-
|
|
1152
|
-
.scroll-bottom .new-count {
|
|
1153
|
-
position: absolute;
|
|
1154
|
-
top: -6px;
|
|
1155
|
-
right: -6px;
|
|
1156
|
-
background: var(--red);
|
|
1157
|
-
color: #fff;
|
|
1158
|
-
font-size: 9px;
|
|
1159
|
-
font-weight: 700;
|
|
1160
|
-
min-width: 16px;
|
|
1161
|
-
height: 16px;
|
|
1162
|
-
border-radius: 8px;
|
|
1163
|
-
display: flex;
|
|
1164
|
-
align-items: center;
|
|
1165
|
-
justify-content: center;
|
|
1166
|
-
padding: 0 4px;
|
|
1167
|
-
}
|
|
1495
|
+
.scroll-bottom.visible { display: flex; transform: translateX(-50%) translateY(0); }
|
|
1496
|
+
.scroll-bottom:hover { transform: translateX(-50%) translateY(-2px); box-shadow: var(--shadow-lg), 0 6px 20px rgba(0,0,0,0.15); color: var(--accent); }
|
|
1168
1497
|
|
|
1169
1498
|
/* ===== SEARCH BAR ===== */
|
|
1170
1499
|
.search-bar {
|
|
@@ -1207,14 +1536,28 @@
|
|
|
1207
1536
|
.compact-toggle.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }
|
|
1208
1537
|
|
|
1209
1538
|
/* Compact mode styles */
|
|
1210
|
-
.messages-area.compact-mode {
|
|
1211
|
-
.messages-area.compact-mode
|
|
1539
|
+
.messages-area.compact-mode { padding: 8px 12px; }
|
|
1540
|
+
.messages-area.compact-mode > * + * {
|
|
1541
|
+
margin-top: 4px;
|
|
1542
|
+
margin-top: var(--nh-space-1, 4px);
|
|
1543
|
+
}
|
|
1544
|
+
.messages-area.compact-mode .message.nh-msg-card { padding: var(--nh-space-2) var(--nh-space-3); }
|
|
1212
1545
|
.messages-area.compact-mode .msg-avatar,
|
|
1213
1546
|
.messages-area.compact-mode .msg-avatar-img { display: none; }
|
|
1214
|
-
.messages-area.compact-mode .msg-
|
|
1215
|
-
|
|
1216
|
-
|
|
1547
|
+
.messages-area.compact-mode .msg-card-head {
|
|
1548
|
+
display: inline-flex;
|
|
1549
|
+
flex-wrap: wrap;
|
|
1550
|
+
align-items: baseline;
|
|
1551
|
+
gap: 6px;
|
|
1552
|
+
margin-bottom: 0;
|
|
1553
|
+
width: auto;
|
|
1554
|
+
}
|
|
1555
|
+
.messages-area.compact-mode .msg-card-ident { display: inline; flex: none; }
|
|
1556
|
+
.messages-area.compact-mode .msg-from { font-size: 12px; display: inline; }
|
|
1557
|
+
.messages-area.compact-mode .msg-to-line { display: inline; margin-top: 0; margin-left: 4px; }
|
|
1558
|
+
.messages-area.compact-mode .msg-card-head-right { flex-direction: row; align-items: baseline; }
|
|
1217
1559
|
.messages-area.compact-mode .msg-time { font-size: 9px; }
|
|
1560
|
+
.messages-area.compact-mode .msg-body { display: block; }
|
|
1218
1561
|
.messages-area.compact-mode .msg-content { display: inline; font-size: 12px; }
|
|
1219
1562
|
.messages-area.compact-mode .msg-content p { display: inline; margin: 0; }
|
|
1220
1563
|
|
|
@@ -1310,18 +1653,26 @@
|
|
|
1310
1653
|
.app-footer a:hover { color: var(--accent); }
|
|
1311
1654
|
|
|
1312
1655
|
/* ===== MESSAGE GROUPING ===== */
|
|
1313
|
-
.message.grouped {
|
|
1314
|
-
padding-top:
|
|
1315
|
-
padding-bottom:
|
|
1656
|
+
.message.grouped.nh-msg-card {
|
|
1657
|
+
padding-top: var(--nh-space-2);
|
|
1658
|
+
padding-bottom: var(--nh-space-2);
|
|
1316
1659
|
}
|
|
1317
1660
|
|
|
1318
|
-
.message.grouped .msg-avatar
|
|
1661
|
+
.message.grouped .msg-avatar,
|
|
1662
|
+
.message.grouped .msg-avatar-img {
|
|
1319
1663
|
visibility: hidden;
|
|
1320
|
-
width:
|
|
1664
|
+
width: 36px;
|
|
1321
1665
|
height: 0;
|
|
1666
|
+
min-height: 0;
|
|
1667
|
+
border: none;
|
|
1668
|
+
margin: 0;
|
|
1322
1669
|
}
|
|
1323
1670
|
|
|
1324
|
-
.message.grouped .msg-
|
|
1671
|
+
.message.grouped .msg-card-head {
|
|
1672
|
+
display: none;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.message.grouped .handoff-banner {
|
|
1325
1676
|
display: none;
|
|
1326
1677
|
}
|
|
1327
1678
|
|
|
@@ -1397,10 +1748,10 @@
|
|
|
1397
1748
|
|
|
1398
1749
|
/* ===== UNIFIED NAV SIDEBAR ===== */
|
|
1399
1750
|
.nav-sidebar {
|
|
1400
|
-
width:
|
|
1401
|
-
min-width:
|
|
1751
|
+
width: var(--sidebar-w);
|
|
1752
|
+
min-width: var(--sidebar-w);
|
|
1402
1753
|
background: var(--surface);
|
|
1403
|
-
border-right: 1px solid var(--border);
|
|
1754
|
+
border-right: 1px solid var(--nh-border);
|
|
1404
1755
|
display: flex;
|
|
1405
1756
|
flex-direction: column;
|
|
1406
1757
|
overflow-y: auto;
|
|
@@ -1416,13 +1767,30 @@
|
|
|
1416
1767
|
display: flex;
|
|
1417
1768
|
align-items: center;
|
|
1418
1769
|
justify-content: space-between;
|
|
1419
|
-
padding:
|
|
1770
|
+
padding: var(--nh-space-4) var(--nh-space-4) var(--nh-space-3);
|
|
1420
1771
|
flex-shrink: 0;
|
|
1772
|
+
border-bottom: 1px solid var(--nh-border);
|
|
1421
1773
|
}
|
|
1422
|
-
.
|
|
1423
|
-
|
|
1774
|
+
.nh-brand-mark {
|
|
1775
|
+
width: 24px;
|
|
1776
|
+
height: 24px;
|
|
1777
|
+
border-radius: 0;
|
|
1778
|
+
background: none;
|
|
1779
|
+
color: var(--nh-on-accent);
|
|
1780
|
+
font-size: 12px;
|
|
1424
1781
|
font-weight: 700;
|
|
1425
|
-
|
|
1782
|
+
display: inline-flex;
|
|
1783
|
+
align-items: center;
|
|
1784
|
+
justify-content: center;
|
|
1785
|
+
flex-shrink: 0;
|
|
1786
|
+
}
|
|
1787
|
+
.nav-sidebar-logo {
|
|
1788
|
+
display: inline-flex;
|
|
1789
|
+
align-items: center;
|
|
1790
|
+
gap: var(--nh-space-2);
|
|
1791
|
+
font-size: 14px;
|
|
1792
|
+
font-weight: 600;
|
|
1793
|
+
color: var(--text);
|
|
1426
1794
|
white-space: nowrap;
|
|
1427
1795
|
overflow: hidden;
|
|
1428
1796
|
letter-spacing: 0.02em;
|
|
@@ -1447,11 +1815,11 @@
|
|
|
1447
1815
|
.nav-section-main { display: flex; flex-direction: column; gap: 1px; }
|
|
1448
1816
|
.nav-sidebar-divider { height: 1px; background: var(--border); margin: 10px 14px; flex-shrink: 0; }
|
|
1449
1817
|
.nav-sidebar-label {
|
|
1450
|
-
font-size:
|
|
1818
|
+
font-size: 12px;
|
|
1451
1819
|
font-weight: 600;
|
|
1452
|
-
color: var(--text-
|
|
1453
|
-
letter-spacing: 0.
|
|
1454
|
-
padding:
|
|
1820
|
+
color: var(--text-dim);
|
|
1821
|
+
letter-spacing: 0.05em;
|
|
1822
|
+
padding: var(--nh-space-2) var(--nh-space-3) var(--nh-space-2);
|
|
1455
1823
|
white-space: nowrap;
|
|
1456
1824
|
overflow: hidden;
|
|
1457
1825
|
text-transform: uppercase;
|
|
@@ -1467,23 +1835,28 @@
|
|
|
1467
1835
|
display: flex;
|
|
1468
1836
|
align-items: center;
|
|
1469
1837
|
gap: 10px;
|
|
1470
|
-
padding:
|
|
1838
|
+
padding: var(--nh-space-2) var(--nh-space-3);
|
|
1471
1839
|
cursor: pointer;
|
|
1472
|
-
border-radius:
|
|
1473
|
-
|
|
1840
|
+
border-radius: var(--nh-radius-sm);
|
|
1841
|
+
border: 1px solid transparent;
|
|
1842
|
+
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
|
1474
1843
|
white-space: nowrap;
|
|
1475
1844
|
overflow: hidden;
|
|
1476
1845
|
margin: 1px 6px;
|
|
1477
1846
|
position: relative;
|
|
1478
1847
|
}
|
|
1479
|
-
.nav-item:hover { background:
|
|
1480
|
-
.nav-item.active {
|
|
1848
|
+
.nav-item:hover { background: var(--nh-card); color: var(--text); }
|
|
1849
|
+
.nav-item.active {
|
|
1850
|
+
background: rgba(245, 158, 11, 0.1);
|
|
1851
|
+
border-color: transparent;
|
|
1852
|
+
box-shadow: inset 3px 0 0 var(--nh-accent);
|
|
1853
|
+
}
|
|
1481
1854
|
.nav-item .nav-icon { line-height: 0; flex-shrink: 0; width: 20px; min-width: 20px; display: flex; align-items: center; justify-content: center; color: var(--text-muted); }
|
|
1482
1855
|
.nav-item .nav-icon svg { flex-shrink: 0; width: 16px; height: 16px; }
|
|
1483
1856
|
.nav-item.active .nav-icon { color: var(--accent); }
|
|
1484
1857
|
.nav-item:hover .nav-icon { color: var(--text); }
|
|
1485
|
-
.nav-item .nav-text { font-size:
|
|
1486
|
-
.nav-item.active .nav-text { color: var(--accent); font-weight:
|
|
1858
|
+
.nav-item .nav-text { font-size: 14px; font-weight: 500; color: var(--text-dim); }
|
|
1859
|
+
.nav-item.active .nav-text { color: var(--accent); font-weight: 500; }
|
|
1487
1860
|
.nav-item:hover .nav-text { color: var(--text); }
|
|
1488
1861
|
.nav-sidebar.collapsed .nav-item { justify-content: center; padding: 10px 4px; margin: 0; }
|
|
1489
1862
|
.nav-sidebar.collapsed .nav-item .nav-text { display: none; }
|
|
@@ -1492,123 +1865,450 @@
|
|
|
1492
1865
|
.agent-bar {
|
|
1493
1866
|
display: flex;
|
|
1494
1867
|
align-items: center;
|
|
1495
|
-
gap: 6px;
|
|
1496
|
-
padding: 6px
|
|
1497
|
-
background: var(--surface);
|
|
1498
|
-
border-bottom: 1px solid var(--border);
|
|
1499
|
-
overflow-x: auto;
|
|
1500
|
-
flex-shrink: 0;
|
|
1501
|
-
min-height: 36px;
|
|
1502
|
-
}
|
|
1503
|
-
.agent-bar::-webkit-scrollbar { height: 0; }
|
|
1504
|
-
.agent-pill {
|
|
1868
|
+
gap: 6px;
|
|
1869
|
+
padding: 6px var(--nh-space-4);
|
|
1870
|
+
background: var(--surface);
|
|
1871
|
+
border-bottom: 1px solid var(--nh-border);
|
|
1872
|
+
overflow-x: auto;
|
|
1873
|
+
flex-shrink: 0;
|
|
1874
|
+
min-height: 36px;
|
|
1875
|
+
}
|
|
1876
|
+
.agent-bar::-webkit-scrollbar { height: 0; }
|
|
1877
|
+
.agent-pill {
|
|
1878
|
+
display: flex;
|
|
1879
|
+
align-items: center;
|
|
1880
|
+
gap: 5px;
|
|
1881
|
+
padding: 3px 10px 3px 6px;
|
|
1882
|
+
border-radius: 20px;
|
|
1883
|
+
background: var(--surface-2);
|
|
1884
|
+
border: 1px solid var(--border);
|
|
1885
|
+
font-size: 13px;
|
|
1886
|
+
white-space: nowrap;
|
|
1887
|
+
cursor: pointer;
|
|
1888
|
+
transition: all 0.15s;
|
|
1889
|
+
flex-shrink: 0;
|
|
1890
|
+
}
|
|
1891
|
+
.agent-pill:hover { border-color: var(--accent); }
|
|
1892
|
+
.agent-pill .status-dot {
|
|
1893
|
+
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
|
|
1894
|
+
}
|
|
1895
|
+
.agent-pill.online .status-dot, .agent-pill.working .status-dot { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
|
1896
|
+
.agent-pill.listening .status-dot { background: var(--nh-blue); animation: pulse 2s infinite; box-shadow: 0 0 6px var(--nh-blue); }
|
|
1897
|
+
.agent-pill.idle .status-dot { background: var(--accent); }
|
|
1898
|
+
.agent-pill.offline .status-dot { background: var(--nh-text-muted); opacity: 0.65; }
|
|
1899
|
+
.agent-pill.unknown .status-dot { background: var(--nh-text-muted); opacity: 0.4; }
|
|
1900
|
+
.agent-pill.stale .status-dot { background: var(--accent); opacity: 0.8; }
|
|
1901
|
+
.agent-pill .agent-name { font-weight: 600; color: var(--text); }
|
|
1902
|
+
.agent-pill .agent-activity { color: var(--text-muted); font-size: 12px; }
|
|
1903
|
+
.agent-pill.offline .agent-name { color: var(--text-muted); opacity: 0.6; }
|
|
1904
|
+
.agent-pill.unknown .agent-name { color: var(--text-muted); opacity: 0.4; }
|
|
1905
|
+
.agent-pill.stale .agent-name { color: var(--text-muted); opacity: 0.7; }
|
|
1906
|
+
|
|
1907
|
+
/* ===== OVERVIEW PAGE ===== */
|
|
1908
|
+
.overview-area {
|
|
1909
|
+
display: none;
|
|
1910
|
+
flex-direction: column;
|
|
1911
|
+
gap: var(--nh-space-6);
|
|
1912
|
+
padding: var(--nh-space-6) var(--nh-space-8);
|
|
1913
|
+
overflow-y: auto;
|
|
1914
|
+
background: var(--nh-bg);
|
|
1915
|
+
}
|
|
1916
|
+
.overview-area.visible { display: flex; }
|
|
1917
|
+
|
|
1918
|
+
.overview-welcome {
|
|
1919
|
+
margin-bottom: 4px;
|
|
1920
|
+
display: flex;
|
|
1921
|
+
flex-wrap: wrap;
|
|
1922
|
+
align-items: flex-start;
|
|
1923
|
+
justify-content: space-between;
|
|
1924
|
+
gap: var(--nh-space-4);
|
|
1925
|
+
}
|
|
1926
|
+
.overview-welcome > div:first-child {
|
|
1927
|
+
flex: 1;
|
|
1928
|
+
min-width: min(100%, 220px);
|
|
1929
|
+
}
|
|
1930
|
+
.overview-welcome h2 {
|
|
1931
|
+
font-size: 20px;
|
|
1932
|
+
font-weight: 600;
|
|
1933
|
+
color: var(--text);
|
|
1934
|
+
margin-bottom: var(--nh-space-1);
|
|
1935
|
+
letter-spacing: -0.02em;
|
|
1936
|
+
}
|
|
1937
|
+
.overview-welcome p {
|
|
1938
|
+
font-size: 14px;
|
|
1939
|
+
color: var(--text-dim);
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
.overview-coordinator {
|
|
1943
|
+
display: flex;
|
|
1944
|
+
align-items: center;
|
|
1945
|
+
gap: var(--nh-space-2);
|
|
1946
|
+
flex-shrink: 0;
|
|
1947
|
+
}
|
|
1948
|
+
.overview-coordinator-label {
|
|
1949
|
+
font-size: 12px;
|
|
1950
|
+
color: var(--text-dim);
|
|
1951
|
+
}
|
|
1952
|
+
.overview-coordinator-toggle {
|
|
1953
|
+
display: flex;
|
|
1954
|
+
border: 1px solid var(--nh-border);
|
|
1955
|
+
border-radius: var(--nh-radius-sm);
|
|
1956
|
+
overflow: hidden;
|
|
1957
|
+
font-size: 12px;
|
|
1958
|
+
}
|
|
1959
|
+
.overview-coordinator-btn {
|
|
1960
|
+
padding: 6px 14px;
|
|
1961
|
+
border: none;
|
|
1962
|
+
cursor: pointer;
|
|
1963
|
+
font-size: 12px;
|
|
1964
|
+
font-family: inherit;
|
|
1965
|
+
font-weight: 400;
|
|
1966
|
+
background: var(--nh-card);
|
|
1967
|
+
color: var(--text-dim);
|
|
1968
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
1969
|
+
}
|
|
1970
|
+
.overview-coordinator-btn + .overview-coordinator-btn {
|
|
1971
|
+
border-left: 1px solid var(--nh-border);
|
|
1972
|
+
}
|
|
1973
|
+
.overview-coordinator-btn:hover {
|
|
1974
|
+
color: var(--text);
|
|
1975
|
+
}
|
|
1976
|
+
/* Stitch: selected “Stay with me” = dark graphite; “Run autonomously” selected = solid amber */
|
|
1977
|
+
.overview-coordinator-btn--mode-responsive {
|
|
1978
|
+
font-weight: 600;
|
|
1979
|
+
background: var(--nh-graphite);
|
|
1980
|
+
color: var(--nh-text-primary);
|
|
1981
|
+
border-color: var(--nh-border);
|
|
1982
|
+
}
|
|
1983
|
+
.overview-coordinator-btn--mode-autonomous {
|
|
1984
|
+
font-weight: 600;
|
|
1985
|
+
background: var(--nh-accent);
|
|
1986
|
+
color: var(--nh-on-accent);
|
|
1987
|
+
border-color: var(--nh-accent);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
.overview-metrics {
|
|
1991
|
+
display: grid;
|
|
1992
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
1993
|
+
gap: var(--nh-space-4);
|
|
1994
|
+
}
|
|
1995
|
+
.metric-card {
|
|
1996
|
+
background: var(--nh-card);
|
|
1997
|
+
border: 1px solid var(--nh-border);
|
|
1998
|
+
border-radius: var(--nh-radius-lg);
|
|
1999
|
+
padding: var(--nh-space-5) var(--nh-space-5);
|
|
2000
|
+
display: flex;
|
|
2001
|
+
flex-direction: column;
|
|
2002
|
+
gap: var(--nh-space-2);
|
|
2003
|
+
position: relative;
|
|
2004
|
+
overflow: hidden;
|
|
2005
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
2006
|
+
}
|
|
2007
|
+
.metric-card:hover {
|
|
2008
|
+
border-color: rgba(156, 163, 175, 0.45);
|
|
2009
|
+
box-shadow: var(--nh-shadow-card);
|
|
2010
|
+
}
|
|
2011
|
+
.metric-card .metric-icon {
|
|
2012
|
+
width: 40px;
|
|
2013
|
+
height: 40px;
|
|
2014
|
+
border-radius: var(--nh-radius-md);
|
|
2015
|
+
display: flex;
|
|
2016
|
+
align-items: center;
|
|
2017
|
+
justify-content: center;
|
|
2018
|
+
margin-bottom: 2px;
|
|
2019
|
+
flex-shrink: 0;
|
|
2020
|
+
}
|
|
2021
|
+
.metric-card .metric-label {
|
|
2022
|
+
font-size: 12px;
|
|
2023
|
+
color: var(--text-dim);
|
|
2024
|
+
font-weight: 500;
|
|
2025
|
+
letter-spacing: 0.02em;
|
|
2026
|
+
}
|
|
2027
|
+
.metric-card .metric-value {
|
|
2028
|
+
font-size: 28px;
|
|
2029
|
+
font-weight: 700;
|
|
2030
|
+
color: var(--text);
|
|
2031
|
+
line-height: 1.15;
|
|
2032
|
+
letter-spacing: -0.03em;
|
|
2033
|
+
}
|
|
2034
|
+
.metric-card .metric-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
|
|
2035
|
+
|
|
2036
|
+
.metric-card__progress {
|
|
2037
|
+
height: 6px;
|
|
2038
|
+
background: var(--nh-card-hover);
|
|
2039
|
+
border-radius: 3px;
|
|
2040
|
+
overflow: hidden;
|
|
2041
|
+
margin-top: var(--nh-space-2);
|
|
2042
|
+
}
|
|
2043
|
+
.metric-card__progress-fill {
|
|
2044
|
+
height: 100%;
|
|
2045
|
+
border-radius: 3px;
|
|
2046
|
+
background: var(--nh-accent);
|
|
2047
|
+
transition: width 0.35s ease;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
.overview-stitch-rows {
|
|
2051
|
+
display: flex;
|
|
2052
|
+
flex-direction: column;
|
|
2053
|
+
gap: var(--nh-space-4);
|
|
2054
|
+
width: 100%;
|
|
2055
|
+
}
|
|
2056
|
+
.overview-stitch-rows > .overview-panel {
|
|
2057
|
+
width: 100%;
|
|
2058
|
+
box-sizing: border-box;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
.overview-grid {
|
|
2062
|
+
display: grid;
|
|
2063
|
+
grid-template-columns: 1fr 1fr;
|
|
2064
|
+
gap: var(--nh-space-4);
|
|
2065
|
+
}
|
|
2066
|
+
.overview-grid--pair {
|
|
2067
|
+
align-items: stretch;
|
|
2068
|
+
}
|
|
2069
|
+
@media (max-width: 900px) {
|
|
2070
|
+
.overview-grid--pair {
|
|
2071
|
+
grid-template-columns: 1fr;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
.overview-panel {
|
|
2075
|
+
background: var(--nh-sidebar);
|
|
2076
|
+
border: 1px solid var(--nh-border);
|
|
2077
|
+
border-radius: var(--nh-radius-xl);
|
|
2078
|
+
padding: var(--nh-space-5);
|
|
2079
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
2080
|
+
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04) inset;
|
|
2081
|
+
}
|
|
2082
|
+
[data-theme="light"] .overview-panel {
|
|
2083
|
+
box-shadow: none;
|
|
2084
|
+
}
|
|
2085
|
+
.overview-panel:hover {
|
|
2086
|
+
border-color: rgba(156, 163, 175, 0.45);
|
|
2087
|
+
box-shadow: var(--nh-shadow-card);
|
|
2088
|
+
}
|
|
2089
|
+
.overview-panel h3 {
|
|
2090
|
+
font-size: 14px;
|
|
2091
|
+
font-weight: 600;
|
|
2092
|
+
color: var(--text);
|
|
2093
|
+
margin-bottom: var(--nh-space-3);
|
|
2094
|
+
display: flex;
|
|
2095
|
+
align-items: center;
|
|
2096
|
+
justify-content: space-between;
|
|
2097
|
+
}
|
|
2098
|
+
.overview-panel h3 a {
|
|
2099
|
+
font-size: 12px;
|
|
2100
|
+
font-weight: 600;
|
|
2101
|
+
color: var(--nh-accent);
|
|
2102
|
+
cursor: pointer;
|
|
2103
|
+
text-decoration: none;
|
|
2104
|
+
transition: opacity 0.15s;
|
|
2105
|
+
}
|
|
2106
|
+
.overview-panel h3 a:hover { opacity: 0.85; }
|
|
2107
|
+
.overview-panel-empty {
|
|
2108
|
+
font-size: 13px;
|
|
2109
|
+
color: var(--text-dim);
|
|
2110
|
+
margin: 0;
|
|
2111
|
+
}
|
|
2112
|
+
.overview-task-stitch {
|
|
2113
|
+
display: flex;
|
|
2114
|
+
flex-direction: column;
|
|
2115
|
+
gap: var(--nh-space-2);
|
|
2116
|
+
font-size: 14px;
|
|
2117
|
+
}
|
|
2118
|
+
.overview-task-stitch-done { color: var(--text); font-weight: 600; }
|
|
2119
|
+
.overview-task-stitch-rem { color: var(--text-dim); }
|
|
2120
|
+
.overview-agent-row {
|
|
2121
|
+
display: flex;
|
|
2122
|
+
align-items: center;
|
|
2123
|
+
gap: var(--nh-space-2);
|
|
2124
|
+
flex-wrap: wrap;
|
|
2125
|
+
padding: var(--nh-space-2) 0;
|
|
2126
|
+
border-bottom: 1px solid var(--nh-border);
|
|
2127
|
+
}
|
|
2128
|
+
.overview-agent-row:last-child { border-bottom: none; }
|
|
2129
|
+
.overview-agent-name { font-size: 13px; font-weight: 600; color: var(--text); }
|
|
2130
|
+
.overview-agent-status { font-size: 12px; color: var(--text-muted); }
|
|
2131
|
+
.overview-activity-row {
|
|
2132
|
+
display: flex;
|
|
2133
|
+
gap: var(--nh-space-3);
|
|
2134
|
+
padding: var(--nh-space-2) 0;
|
|
2135
|
+
border-bottom: 1px solid var(--nh-border);
|
|
2136
|
+
font-size: 13px;
|
|
2137
|
+
align-items: flex-start;
|
|
2138
|
+
}
|
|
2139
|
+
.overview-activity-row:last-child { border-bottom: none; }
|
|
2140
|
+
.overview-activity-time {
|
|
2141
|
+
color: var(--text-dim);
|
|
2142
|
+
font-size: 12px;
|
|
2143
|
+
flex-shrink: 0;
|
|
2144
|
+
min-width: 100px;
|
|
2145
|
+
font-variant-numeric: tabular-nums;
|
|
2146
|
+
}
|
|
2147
|
+
.overview-activity-body {
|
|
2148
|
+
flex: 1;
|
|
2149
|
+
min-width: 0;
|
|
2150
|
+
color: var(--text-dim);
|
|
2151
|
+
line-height: 1.45;
|
|
2152
|
+
}
|
|
2153
|
+
.overview-msg-from--agent { font-weight: 600; color: var(--nh-accent); }
|
|
2154
|
+
.overview-msg-from--coord { font-weight: 600; color: var(--nh-yellow); }
|
|
2155
|
+
.overview-msg-from--system { font-weight: 600; color: var(--nh-text-muted); }
|
|
2156
|
+
.overview-activity-preview {
|
|
2157
|
+
color: var(--text-dim);
|
|
2158
|
+
word-break: break-word;
|
|
2159
|
+
}
|
|
2160
|
+
.overview-full-width { grid-column: 1 / -1; }
|
|
2161
|
+
.overview-task-summary { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
2162
|
+
.overview-task-badge {
|
|
2163
|
+
display: inline-flex;
|
|
2164
|
+
align-items: center;
|
|
2165
|
+
padding: 4px 10px;
|
|
2166
|
+
border-radius: 9999px;
|
|
2167
|
+
font-size: 12px;
|
|
2168
|
+
font-weight: 500;
|
|
2169
|
+
line-height: 1.25;
|
|
2170
|
+
border: 1px solid transparent;
|
|
2171
|
+
}
|
|
2172
|
+
.overview-task-badge.pending {
|
|
2173
|
+
background: var(--nh-yellow-10);
|
|
2174
|
+
color: var(--yellow);
|
|
2175
|
+
border-color: var(--nh-yellow-20);
|
|
2176
|
+
}
|
|
2177
|
+
.overview-task-badge.active {
|
|
2178
|
+
background: var(--nh-accent-10);
|
|
2179
|
+
color: var(--accent);
|
|
2180
|
+
border-color: var(--nh-accent-20);
|
|
2181
|
+
}
|
|
2182
|
+
.overview-task-badge.done {
|
|
2183
|
+
background: var(--nh-green-10);
|
|
2184
|
+
color: var(--green);
|
|
2185
|
+
border-color: var(--nh-green-20);
|
|
2186
|
+
}
|
|
2187
|
+
.overview-empty-state {
|
|
2188
|
+
display: flex;
|
|
2189
|
+
flex-direction: column;
|
|
2190
|
+
align-items: center;
|
|
2191
|
+
justify-content: center;
|
|
2192
|
+
padding: 60px var(--nh-space-5);
|
|
2193
|
+
text-align: center;
|
|
2194
|
+
background: var(--nh-sidebar);
|
|
2195
|
+
border: 1px solid var(--nh-border);
|
|
2196
|
+
border-radius: var(--nh-radius-xl);
|
|
2197
|
+
box-shadow: var(--nh-shadow-card);
|
|
2198
|
+
}
|
|
2199
|
+
.overview-empty-state-icon {
|
|
2200
|
+
width: 64px;
|
|
2201
|
+
height: 64px;
|
|
2202
|
+
border-radius: var(--nh-radius-xl);
|
|
2203
|
+
background: var(--nh-accent-10);
|
|
2204
|
+
border: 1px solid var(--nh-accent-20);
|
|
1505
2205
|
display: flex;
|
|
1506
2206
|
align-items: center;
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
border-radius: 20px;
|
|
1510
|
-
background: var(--surface-2);
|
|
1511
|
-
border: 1px solid var(--border);
|
|
1512
|
-
font-size: 13px;
|
|
1513
|
-
white-space: nowrap;
|
|
1514
|
-
cursor: pointer;
|
|
1515
|
-
transition: all 0.15s;
|
|
1516
|
-
flex-shrink: 0;
|
|
1517
|
-
}
|
|
1518
|
-
.agent-pill:hover { border-color: var(--accent); }
|
|
1519
|
-
.agent-pill .status-dot {
|
|
1520
|
-
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
|
|
1521
|
-
}
|
|
1522
|
-
.agent-pill.online .status-dot, .agent-pill.working .status-dot { background: #22c55e; }
|
|
1523
|
-
.agent-pill.listening .status-dot { background: #3b82f6; animation: pulse 2s infinite; }
|
|
1524
|
-
.agent-pill.idle .status-dot { background: #f59e0b; }
|
|
1525
|
-
.agent-pill.offline .status-dot { background: #6b7280; opacity: 0.6; }
|
|
1526
|
-
.agent-pill .agent-name { font-weight: 600; color: var(--text); }
|
|
1527
|
-
.agent-pill .agent-activity { color: var(--text-muted); font-size: 12px; }
|
|
1528
|
-
.agent-pill.offline .agent-name { color: var(--text-muted); opacity: 0.6; }
|
|
1529
|
-
|
|
1530
|
-
/* ===== OVERVIEW PAGE ===== */
|
|
1531
|
-
.overview-area { display: none; flex-direction: column; gap: 24px; padding: 28px 32px; overflow-y: auto; }
|
|
1532
|
-
.overview-area.visible { display: flex; }
|
|
1533
|
-
|
|
1534
|
-
.overview-welcome {
|
|
1535
|
-
margin-bottom: 4px;
|
|
2207
|
+
justify-content: center;
|
|
2208
|
+
margin-bottom: var(--nh-space-5);
|
|
1536
2209
|
}
|
|
1537
|
-
.overview-
|
|
1538
|
-
font-size:
|
|
1539
|
-
font-weight:
|
|
2210
|
+
.overview-empty-state-title {
|
|
2211
|
+
font-size: 18px;
|
|
2212
|
+
font-weight: 600;
|
|
1540
2213
|
color: var(--text);
|
|
1541
|
-
margin-bottom:
|
|
1542
|
-
letter-spacing: -0.3px;
|
|
2214
|
+
margin-bottom: var(--nh-space-2);
|
|
1543
2215
|
}
|
|
1544
|
-
.overview-
|
|
1545
|
-
font-size:
|
|
1546
|
-
color: var(--text-
|
|
2216
|
+
.overview-empty-state-desc {
|
|
2217
|
+
font-size: 14px;
|
|
2218
|
+
color: var(--text-dim);
|
|
2219
|
+
max-width: 360px;
|
|
2220
|
+
line-height: 1.6;
|
|
2221
|
+
margin-bottom: var(--nh-space-1);
|
|
1547
2222
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
2223
|
+
.overview-launch-btn {
|
|
2224
|
+
display: inline-flex;
|
|
2225
|
+
align-items: center;
|
|
2226
|
+
gap: var(--nh-space-2);
|
|
2227
|
+
padding: 10px var(--nh-space-5);
|
|
2228
|
+
background: var(--nh-accent);
|
|
2229
|
+
color: var(--nh-on-accent);
|
|
2230
|
+
border: none;
|
|
2231
|
+
border-radius: var(--nh-radius-sm);
|
|
2232
|
+
font-size: 14px;
|
|
2233
|
+
font-weight: 600;
|
|
2234
|
+
cursor: pointer;
|
|
2235
|
+
font-family: inherit;
|
|
2236
|
+
margin-top: var(--nh-space-3);
|
|
2237
|
+
transition: background-color 0.15s ease, box-shadow 0.2s ease, transform 0.15s ease;
|
|
2238
|
+
box-shadow: 0 2px 12px var(--nh-accent-20);
|
|
1551
2239
|
}
|
|
1552
|
-
.
|
|
1553
|
-
background: var(--
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
transition: border-color 0.2s, box-shadow 0.2s;
|
|
2240
|
+
.overview-launch-btn:hover {
|
|
2241
|
+
background: var(--nh-accent-90);
|
|
2242
|
+
transform: translateY(-1px);
|
|
2243
|
+
box-shadow: 0 4px 20px var(--nh-accent-20);
|
|
1557
2244
|
}
|
|
1558
|
-
.
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
2245
|
+
.overview-notif-row-stitch {
|
|
2246
|
+
display: flex;
|
|
2247
|
+
align-items: flex-start;
|
|
2248
|
+
gap: var(--nh-space-3);
|
|
2249
|
+
padding: var(--nh-space-2) 0;
|
|
2250
|
+
border-bottom: 1px solid var(--nh-border);
|
|
2251
|
+
font-size: 13px;
|
|
1563
2252
|
}
|
|
1564
|
-
.
|
|
1565
|
-
.
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
display:
|
|
2253
|
+
.overview-notif-row-stitch:last-child { border-bottom: none; }
|
|
2254
|
+
.overview-notif-icon-stitch {
|
|
2255
|
+
width: 24px;
|
|
2256
|
+
height: 24px;
|
|
2257
|
+
border-radius: var(--nh-radius-sm);
|
|
2258
|
+
display: flex;
|
|
2259
|
+
align-items: center;
|
|
2260
|
+
justify-content: center;
|
|
2261
|
+
font-size: 12px;
|
|
2262
|
+
font-weight: 700;
|
|
2263
|
+
flex-shrink: 0;
|
|
1570
2264
|
}
|
|
1571
|
-
.overview-
|
|
1572
|
-
background: var(--
|
|
1573
|
-
|
|
2265
|
+
.overview-notif--success .overview-notif-icon-stitch {
|
|
2266
|
+
background: var(--nh-green-10);
|
|
2267
|
+
color: var(--nh-green);
|
|
1574
2268
|
}
|
|
1575
|
-
.overview-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
2269
|
+
.overview-notif--info .overview-notif-icon-stitch {
|
|
2270
|
+
background: rgba(59, 130, 246, 0.12);
|
|
2271
|
+
color: var(--nh-blue);
|
|
1579
2272
|
}
|
|
1580
|
-
.overview-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
display: flex; align-items: center; gap: 10px; padding: 8px 0;
|
|
1584
|
-
border-bottom: 1px solid var(--border);
|
|
2273
|
+
.overview-notif--neutral .overview-notif-icon-stitch {
|
|
2274
|
+
background: var(--nh-card-hover);
|
|
2275
|
+
color: var(--text-dim);
|
|
1585
2276
|
}
|
|
1586
|
-
.overview-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
.overview-agent-status { font-size: 12px; color: var(--text-muted); }
|
|
1590
|
-
.overview-msg-row {
|
|
1591
|
-
display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; align-items: baseline;
|
|
2277
|
+
.overview-notif--warn .overview-notif-icon-stitch {
|
|
2278
|
+
background: var(--nh-orange-10);
|
|
2279
|
+
color: var(--nh-orange);
|
|
1592
2280
|
}
|
|
1593
|
-
.overview-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
.overview-full-width { grid-column: 1 / -1; }
|
|
1598
|
-
.overview-task-summary { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
1599
|
-
.overview-task-badge {
|
|
1600
|
-
padding: 6px 14px; border-radius: 8px; font-size: 13px; font-weight: 600;
|
|
2281
|
+
.overview-notif-tag {
|
|
2282
|
+
font-size: 11px;
|
|
2283
|
+
font-weight: 700;
|
|
2284
|
+
margin-right: 6px;
|
|
1601
2285
|
}
|
|
1602
|
-
.overview-
|
|
1603
|
-
.overview-
|
|
1604
|
-
.overview-
|
|
1605
|
-
.overview-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
font-size:
|
|
1609
|
-
|
|
2286
|
+
.overview-notif--success .overview-notif-tag { color: var(--nh-green); }
|
|
2287
|
+
.overview-notif--info .overview-notif-tag { color: var(--nh-blue); }
|
|
2288
|
+
.overview-notif--neutral .overview-notif-tag { color: var(--text-dim); }
|
|
2289
|
+
.overview-notif--warn .overview-notif-tag { color: var(--nh-orange); }
|
|
2290
|
+
.overview-notif-msg { color: var(--text-dim); line-height: 1.4; }
|
|
2291
|
+
.overview-notif-time-stitch {
|
|
2292
|
+
font-size: 11px;
|
|
2293
|
+
color: var(--text-dim);
|
|
2294
|
+
flex-shrink: 0;
|
|
2295
|
+
margin-left: auto;
|
|
2296
|
+
padding-left: var(--nh-space-2);
|
|
2297
|
+
}
|
|
2298
|
+
.overview-role-pill {
|
|
2299
|
+
display: inline-block;
|
|
2300
|
+
font-size: 10px;
|
|
2301
|
+
font-weight: 600;
|
|
2302
|
+
text-transform: uppercase;
|
|
2303
|
+
letter-spacing: 0.04em;
|
|
2304
|
+
padding: 2px 6px;
|
|
2305
|
+
border-radius: 4px;
|
|
2306
|
+
background: var(--nh-accent-10);
|
|
2307
|
+
color: var(--nh-accent);
|
|
2308
|
+
border: 1px solid var(--nh-accent-20);
|
|
2309
|
+
margin-left: var(--nh-space-2);
|
|
2310
|
+
vertical-align: middle;
|
|
1610
2311
|
}
|
|
1611
|
-
.overview-launch-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 16px var(--accent-glow); }
|
|
1612
2312
|
|
|
1613
2313
|
/* ===== TOAST NOTIFICATIONS ===== */
|
|
1614
2314
|
.toast-container {
|
|
@@ -1631,7 +2331,11 @@
|
|
|
1631
2331
|
@keyframes toast-out { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(12px); } }
|
|
1632
2332
|
|
|
1633
2333
|
/* ===== USER MESSAGES ===== */
|
|
1634
|
-
.message.user-msg
|
|
2334
|
+
.message.user-msg.nh-msg-card {
|
|
2335
|
+
background: var(--nh-accent-5);
|
|
2336
|
+
border-left: 3px solid var(--nh-accent);
|
|
2337
|
+
padding-left: calc(var(--nh-space-5) - 2px);
|
|
2338
|
+
}
|
|
1635
2339
|
|
|
1636
2340
|
/* Hide old view-tabs (replaced by nav sidebar) */
|
|
1637
2341
|
.view-tabs { display: none !important; }
|
|
@@ -1878,6 +2582,7 @@
|
|
|
1878
2582
|
.kanban-title.in_review { background: rgba(168,85,247,0.15); color: var(--purple,#a855f7); }
|
|
1879
2583
|
.kanban-title.done { background: var(--green-dim); color: var(--green); }
|
|
1880
2584
|
.kanban-title.blocked { background: var(--red-dim); color: var(--red); }
|
|
2585
|
+
.kanban-title.blocked_permanent { background: var(--red); color: #fff; }
|
|
1881
2586
|
|
|
1882
2587
|
.kanban-count {
|
|
1883
2588
|
font-size: 10px;
|
|
@@ -1911,8 +2616,10 @@
|
|
|
1911
2616
|
color: var(--text-dim);
|
|
1912
2617
|
margin-bottom: 6px;
|
|
1913
2618
|
display: -webkit-box;
|
|
1914
|
-
-webkit-line-clamp:
|
|
2619
|
+
-webkit-line-clamp: 3;
|
|
2620
|
+
line-clamp: 3;
|
|
1915
2621
|
-webkit-box-orient: vertical;
|
|
2622
|
+
box-orient: vertical;
|
|
1916
2623
|
overflow: hidden;
|
|
1917
2624
|
}
|
|
1918
2625
|
|
|
@@ -2182,10 +2889,11 @@
|
|
|
2182
2889
|
}
|
|
2183
2890
|
|
|
2184
2891
|
/* ===== HANDOFF MESSAGE ===== */
|
|
2185
|
-
.message.handoff-msg {
|
|
2186
|
-
background: var(--purple-
|
|
2187
|
-
border:
|
|
2188
|
-
border-left: 3px solid var(--purple);
|
|
2892
|
+
.message.handoff-msg.nh-msg-card {
|
|
2893
|
+
background: var(--nh-purple-10);
|
|
2894
|
+
border-color: var(--nh-purple-20);
|
|
2895
|
+
border-left: 3px solid var(--nh-purple);
|
|
2896
|
+
padding-left: calc(var(--nh-space-5) - 2px);
|
|
2189
2897
|
}
|
|
2190
2898
|
|
|
2191
2899
|
.handoff-banner {
|
|
@@ -2194,10 +2902,10 @@
|
|
|
2194
2902
|
gap: 6px;
|
|
2195
2903
|
font-size: 11px;
|
|
2196
2904
|
font-weight: 600;
|
|
2197
|
-
color: var(--purple);
|
|
2905
|
+
color: var(--nh-purple);
|
|
2198
2906
|
text-transform: uppercase;
|
|
2199
2907
|
letter-spacing: 0.5px;
|
|
2200
|
-
margin-bottom:
|
|
2908
|
+
margin-bottom: var(--nh-space-2);
|
|
2201
2909
|
}
|
|
2202
2910
|
|
|
2203
2911
|
/* ===== FILE SHARE MESSAGE ===== */
|
|
@@ -2205,37 +2913,119 @@
|
|
|
2205
2913
|
display: inline-flex;
|
|
2206
2914
|
align-items: center;
|
|
2207
2915
|
gap: 6px;
|
|
2208
|
-
background: var(--
|
|
2209
|
-
border: 1px solid var(--border);
|
|
2210
|
-
border-radius:
|
|
2916
|
+
background: var(--nh-card-hover);
|
|
2917
|
+
border: 1px solid var(--nh-border);
|
|
2918
|
+
border-radius: var(--nh-radius-sm);
|
|
2211
2919
|
padding: 4px 10px;
|
|
2212
2920
|
font-size: 11px;
|
|
2213
|
-
color: var(--text-
|
|
2214
|
-
margin-bottom:
|
|
2921
|
+
color: var(--nh-text-secondary);
|
|
2922
|
+
margin-bottom: var(--nh-space-2);
|
|
2215
2923
|
}
|
|
2216
2924
|
|
|
2217
2925
|
.file-icon { font-size: 14px; }
|
|
2218
2926
|
|
|
2219
2927
|
.file-size {
|
|
2220
|
-
color: var(--text-muted);
|
|
2928
|
+
color: var(--nh-text-muted);
|
|
2221
2929
|
font-size: 10px;
|
|
2222
2930
|
}
|
|
2223
2931
|
|
|
2224
|
-
/* ===== SYSTEM
|
|
2225
|
-
.message.system-
|
|
2226
|
-
|
|
2227
|
-
|
|
2932
|
+
/* ===== SYSTEM EVENT BANNER (all system messages) ===== */
|
|
2933
|
+
.message.system-event-banner {
|
|
2934
|
+
background: none;
|
|
2935
|
+
border: none;
|
|
2936
|
+
border-radius: 6px;
|
|
2937
|
+
box-shadow: none;
|
|
2938
|
+
padding: 10px 16px;
|
|
2939
|
+
margin: 0 0 6px 0;
|
|
2940
|
+
min-height: auto;
|
|
2941
|
+
display: flex;
|
|
2942
|
+
align-items: flex-start;
|
|
2943
|
+
gap: 8px;
|
|
2944
|
+
border-left: 3px solid var(--nh-border, var(--border));
|
|
2945
|
+
}
|
|
2946
|
+
.system-event-banner .event-icon {
|
|
2947
|
+
flex-shrink: 0;
|
|
2948
|
+
width: 16px;
|
|
2949
|
+
height: 16px;
|
|
2950
|
+
margin-top: 1px;
|
|
2951
|
+
opacity: 0.8;
|
|
2952
|
+
}
|
|
2953
|
+
.system-event-banner .event-icon svg {
|
|
2954
|
+
width: 14px;
|
|
2955
|
+
height: 14px;
|
|
2956
|
+
display: block;
|
|
2957
|
+
}
|
|
2958
|
+
.system-event-banner .event-body {
|
|
2959
|
+
flex: 1;
|
|
2960
|
+
min-width: 0;
|
|
2961
|
+
}
|
|
2962
|
+
.system-event-banner .event-text {
|
|
2963
|
+
font-size: 12px;
|
|
2964
|
+
color: var(--nh-text-secondary, var(--text-muted));
|
|
2965
|
+
line-height: 1.4;
|
|
2966
|
+
word-wrap: break-word;
|
|
2967
|
+
}
|
|
2968
|
+
.system-event-banner .event-text p { margin: 0; }
|
|
2969
|
+
.system-event-banner .event-time {
|
|
2970
|
+
font-size: 10px;
|
|
2971
|
+
color: var(--nh-text-secondary, var(--text-muted));
|
|
2972
|
+
opacity: 0.5;
|
|
2973
|
+
flex-shrink: 0;
|
|
2974
|
+
margin-top: 2px;
|
|
2975
|
+
}
|
|
2976
|
+
/* Type colors — left border + icon color */
|
|
2977
|
+
.system-event-banner.evt-critical { border-left-color: #ef4444; }
|
|
2978
|
+
.system-event-banner.evt-critical .event-icon { color: #ef4444; }
|
|
2979
|
+
.system-event-banner.evt-critical .event-text { color: #ef4444; font-weight: 500; }
|
|
2980
|
+
.system-event-banner.evt-warning { border-left-color: #f59e0b; }
|
|
2981
|
+
.system-event-banner.evt-warning .event-icon { color: #f59e0b; }
|
|
2982
|
+
.system-event-banner.evt-warning .event-text { color: #f59e0b; }
|
|
2983
|
+
.system-event-banner.evt-event { border-left-color: #22c55e; }
|
|
2984
|
+
.system-event-banner.evt-event .event-icon { color: #22c55e; }
|
|
2985
|
+
.system-event-banner.evt-review { border-left-color: #3b82f6; }
|
|
2986
|
+
.system-event-banner.evt-review .event-icon { color: #3b82f6; }
|
|
2987
|
+
.system-event-banner.evt-status { border-left-color: #6b7280; }
|
|
2988
|
+
.system-event-banner.evt-status .event-icon { color: #6b7280; }
|
|
2989
|
+
.system-event-banner.evt-role { border-left-color: #8b5cf6; }
|
|
2990
|
+
.system-event-banner.evt-role .event-icon { color: #8b5cf6; }
|
|
2991
|
+
.system-event-banner.evt-violation { border-left-color: #ef4444; background: rgba(239,68,68,0.05); }
|
|
2992
|
+
.system-event-banner.evt-violation .event-icon { color: #ef4444; }
|
|
2993
|
+
.system-event-banner.evt-violation .event-text { color: var(--text); font-weight: 500; }
|
|
2994
|
+
|
|
2995
|
+
/* ===== VIOLATION MESSAGES ===== */
|
|
2996
|
+
.message.violation-msg.nh-msg-card {
|
|
2997
|
+
border: 1px solid var(--nh-amber, #f59e0b);
|
|
2998
|
+
background: rgba(245, 158, 11, 0.05);
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
.violation-content {
|
|
3002
|
+
color: var(--text);
|
|
3003
|
+
font-weight: 500;
|
|
3004
|
+
font-style: normal;
|
|
2228
3005
|
}
|
|
2229
3006
|
|
|
2230
|
-
.
|
|
2231
|
-
|
|
2232
|
-
|
|
3007
|
+
.violation-icon {
|
|
3008
|
+
color: var(--nh-amber, #f59e0b);
|
|
3009
|
+
margin-right: 6px;
|
|
3010
|
+
vertical-align: middle;
|
|
3011
|
+
display: inline-flex;
|
|
3012
|
+
align-items: center;
|
|
2233
3013
|
}
|
|
2234
3014
|
|
|
2235
|
-
.
|
|
2236
|
-
color: var(--
|
|
2237
|
-
font-
|
|
2238
|
-
|
|
3015
|
+
.violation-label {
|
|
3016
|
+
color: var(--nh-amber, #f59e0b);
|
|
3017
|
+
font-weight: 700;
|
|
3018
|
+
margin-right: 4px;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
/* ===== STEP SHIELD ICON ===== */
|
|
3022
|
+
.step-shield {
|
|
3023
|
+
display: inline-flex;
|
|
3024
|
+
align-items: center;
|
|
3025
|
+
color: var(--nh-amber, #f59e0b);
|
|
3026
|
+
margin-right: 4px;
|
|
3027
|
+
vertical-align: middle;
|
|
3028
|
+
opacity: 0.85;
|
|
2239
3029
|
}
|
|
2240
3030
|
|
|
2241
3031
|
/* ===== RESPONSIVE ===== */
|
|
@@ -2261,6 +3051,24 @@
|
|
|
2261
3051
|
|
|
2262
3052
|
.sidebar.open { transform: translateX(0); }
|
|
2263
3053
|
|
|
3054
|
+
.nav-sidebar {
|
|
3055
|
+
position: fixed;
|
|
3056
|
+
top: var(--header-h);
|
|
3057
|
+
left: 0;
|
|
3058
|
+
bottom: 0;
|
|
3059
|
+
z-index: 90;
|
|
3060
|
+
transform: translateX(-100%);
|
|
3061
|
+
box-shadow: 4px 0 20px rgba(0,0,0,0.3);
|
|
3062
|
+
transition: transform 0.25s ease;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
.nav-sidebar.open {
|
|
3066
|
+
display: flex !important;
|
|
3067
|
+
width: var(--sidebar-w) !important;
|
|
3068
|
+
min-width: var(--sidebar-w) !important;
|
|
3069
|
+
transform: translateX(0);
|
|
3070
|
+
}
|
|
3071
|
+
|
|
2264
3072
|
.sidebar-overlay {
|
|
2265
3073
|
display: none;
|
|
2266
3074
|
position: fixed;
|
|
@@ -2305,9 +3113,14 @@
|
|
|
2305
3113
|
.mobile-project-select:focus { border-color: var(--orange); }
|
|
2306
3114
|
|
|
2307
3115
|
/* Messages: better mobile spacing */
|
|
2308
|
-
.messages-area { padding: 8px;
|
|
2309
|
-
.
|
|
3116
|
+
.messages-area { padding: 8px; }
|
|
3117
|
+
.messages-area > * + * {
|
|
3118
|
+
margin-top: 8px;
|
|
3119
|
+
margin-top: var(--nh-space-2, 8px);
|
|
3120
|
+
}
|
|
3121
|
+
.message.nh-msg-card { padding: var(--nh-space-3); }
|
|
2310
3122
|
.msg-avatar { width: 28px; height: 28px; font-size: 11px; }
|
|
3123
|
+
.msg-avatar-img { width: 28px; height: 28px; }
|
|
2311
3124
|
.msg-from { font-size: 12px; }
|
|
2312
3125
|
.msg-content { font-size: 13px; line-height: 1.5; }
|
|
2313
3126
|
.msg-time { font-size: 9px; }
|
|
@@ -2319,6 +3132,7 @@
|
|
|
2319
3132
|
/* Input bar: stack vertically */
|
|
2320
3133
|
.msg-input-bar {
|
|
2321
3134
|
flex-direction: column;
|
|
3135
|
+
align-items: stretch;
|
|
2322
3136
|
padding: 8px 10px;
|
|
2323
3137
|
gap: 6px;
|
|
2324
3138
|
}
|
|
@@ -2371,7 +3185,7 @@
|
|
|
2371
3185
|
}
|
|
2372
3186
|
|
|
2373
3187
|
.header { padding: 0 8px; }
|
|
2374
|
-
.logo { font-size:
|
|
3188
|
+
.logo { font-size: 17px; }
|
|
2375
3189
|
.header-left { gap: 6px; }
|
|
2376
3190
|
|
|
2377
3191
|
/* Compact header buttons */
|
|
@@ -2384,8 +3198,12 @@
|
|
|
2384
3198
|
|
|
2385
3199
|
/* Messages: tighter for small screens */
|
|
2386
3200
|
.messages-area { padding: 6px; }
|
|
2387
|
-
.
|
|
2388
|
-
|
|
3201
|
+
.messages-area > * + * {
|
|
3202
|
+
margin-top: 6px;
|
|
3203
|
+
margin-top: var(--nh-space-2, 6px);
|
|
3204
|
+
}
|
|
3205
|
+
.message.nh-msg-card { padding: var(--nh-space-2) var(--nh-space-3); }
|
|
3206
|
+
.msg-avatar { width: 28px; height: 28px; font-size: 10px; }
|
|
2389
3207
|
.msg-header { gap: 4px; }
|
|
2390
3208
|
.msg-from { font-size: 11px; }
|
|
2391
3209
|
.msg-to { font-size: 11px; }
|
|
@@ -2418,7 +3236,7 @@
|
|
|
2418
3236
|
|
|
2419
3237
|
/* ===== MOBILE: VERY SMALL PHONE (360px) ===== */
|
|
2420
3238
|
@media (max-width: 360px) {
|
|
2421
|
-
.logo { font-size:
|
|
3239
|
+
.logo { font-size: 15px; }
|
|
2422
3240
|
.mobile-project-select { max-width: 80px; font-size: 9px; }
|
|
2423
3241
|
.msg-avatar { display: none; }
|
|
2424
3242
|
.message { gap: 0; }
|
|
@@ -2434,12 +3252,13 @@
|
|
|
2434
3252
|
}
|
|
2435
3253
|
|
|
2436
3254
|
.msg-avatar-img {
|
|
2437
|
-
width:
|
|
2438
|
-
height:
|
|
3255
|
+
width: 36px;
|
|
3256
|
+
height: 36px;
|
|
2439
3257
|
border-radius: 50%;
|
|
2440
3258
|
object-fit: cover;
|
|
2441
3259
|
flex-shrink: 0;
|
|
2442
3260
|
margin-top: 2px;
|
|
3261
|
+
border: 1px solid var(--nh-border);
|
|
2443
3262
|
}
|
|
2444
3263
|
|
|
2445
3264
|
.role-badge {
|
|
@@ -2452,7 +3271,7 @@
|
|
|
2452
3271
|
margin-left: 4px;
|
|
2453
3272
|
}
|
|
2454
3273
|
|
|
2455
|
-
/* ===== v3.0: PROFILE POPUP ===== */
|
|
3274
|
+
/* ===== v3.0: PROFILE POPUP (3-TAB) ===== */
|
|
2456
3275
|
.profile-popup {
|
|
2457
3276
|
display: none;
|
|
2458
3277
|
position: fixed;
|
|
@@ -2461,11 +3280,16 @@
|
|
|
2461
3280
|
background-image: var(--gradient-surface);
|
|
2462
3281
|
border: 1px solid var(--border-light);
|
|
2463
3282
|
border-radius: 14px;
|
|
2464
|
-
padding:
|
|
2465
|
-
width:
|
|
3283
|
+
padding: 0;
|
|
3284
|
+
width: 310px;
|
|
3285
|
+
box-sizing: border-box;
|
|
3286
|
+
max-height: 80vh;
|
|
3287
|
+
overflow-x: hidden;
|
|
3288
|
+
overflow-y: hidden;
|
|
3289
|
+
word-break: break-word;
|
|
2466
3290
|
box-shadow: var(--shadow-lg), 0 0 40px rgba(0,0,0,0.2);
|
|
2467
|
-
backdrop-filter: blur(
|
|
2468
|
-
-webkit-backdrop-filter: blur(
|
|
3291
|
+
backdrop-filter: blur(12px);
|
|
3292
|
+
-webkit-backdrop-filter: blur(12px);
|
|
2469
3293
|
}
|
|
2470
3294
|
|
|
2471
3295
|
.profile-popup.open { display: block; }
|
|
@@ -2474,19 +3298,21 @@
|
|
|
2474
3298
|
display: flex;
|
|
2475
3299
|
align-items: center;
|
|
2476
3300
|
gap: 12px;
|
|
2477
|
-
|
|
3301
|
+
padding: 16px 18px 12px;
|
|
2478
3302
|
}
|
|
2479
3303
|
|
|
2480
3304
|
.profile-popup-avatar {
|
|
2481
|
-
width:
|
|
2482
|
-
height:
|
|
3305
|
+
width: 44px;
|
|
3306
|
+
height: 44px;
|
|
2483
3307
|
border-radius: 50%;
|
|
2484
3308
|
object-fit: cover;
|
|
3309
|
+
flex-shrink: 0;
|
|
2485
3310
|
}
|
|
2486
3311
|
|
|
2487
3312
|
.profile-popup-name {
|
|
2488
3313
|
font-weight: 700;
|
|
2489
3314
|
font-size: 15px;
|
|
3315
|
+
line-height: 1.2;
|
|
2490
3316
|
}
|
|
2491
3317
|
|
|
2492
3318
|
.profile-popup-id {
|
|
@@ -2494,6 +3320,73 @@
|
|
|
2494
3320
|
color: var(--text-muted);
|
|
2495
3321
|
}
|
|
2496
3322
|
|
|
3323
|
+
.pp-status-badge {
|
|
3324
|
+
display: inline-flex;
|
|
3325
|
+
align-items: center;
|
|
3326
|
+
gap: 4px;
|
|
3327
|
+
font-size: 10px;
|
|
3328
|
+
font-weight: 600;
|
|
3329
|
+
text-transform: uppercase;
|
|
3330
|
+
letter-spacing: 0.3px;
|
|
3331
|
+
padding: 2px 8px;
|
|
3332
|
+
border-radius: 10px;
|
|
3333
|
+
margin-left: 6px;
|
|
3334
|
+
}
|
|
3335
|
+
.pp-status-badge.online { background: rgba(52,211,153,0.15); color: #34d399; }
|
|
3336
|
+
.pp-status-badge.offline { background: rgba(156,163,175,0.15); color: #9ca3af; }
|
|
3337
|
+
.pp-status-badge.listening { background: rgba(96,165,250,0.15); color: #60a5fa; }
|
|
3338
|
+
.pp-status-badge.unresponsive { background: rgba(249,115,22,0.15); color: #f97316; }
|
|
3339
|
+
.pp-status-badge.stuck { background: rgba(239,68,68,0.15); color: #ef4444; }
|
|
3340
|
+
|
|
3341
|
+
/* Tab bar */
|
|
3342
|
+
.pp-tabs {
|
|
3343
|
+
display: flex;
|
|
3344
|
+
border-bottom: 1px solid var(--border);
|
|
3345
|
+
padding: 0 18px;
|
|
3346
|
+
gap: 0;
|
|
3347
|
+
}
|
|
3348
|
+
.pp-tab {
|
|
3349
|
+
flex: 1;
|
|
3350
|
+
padding: 8px 0;
|
|
3351
|
+
font-size: 11px;
|
|
3352
|
+
font-weight: 600;
|
|
3353
|
+
text-transform: uppercase;
|
|
3354
|
+
letter-spacing: 0.4px;
|
|
3355
|
+
color: var(--text-muted);
|
|
3356
|
+
background: none;
|
|
3357
|
+
border: none;
|
|
3358
|
+
border-bottom: 2px solid transparent;
|
|
3359
|
+
cursor: pointer;
|
|
3360
|
+
text-align: center;
|
|
3361
|
+
transition: color 0.15s, border-color 0.15s;
|
|
3362
|
+
}
|
|
3363
|
+
.pp-tab:hover { color: var(--text); }
|
|
3364
|
+
.pp-tab.active { color: var(--nh-accent); border-bottom-color: var(--nh-accent); }
|
|
3365
|
+
|
|
3366
|
+
/* Tab content */
|
|
3367
|
+
.pp-tab-content {
|
|
3368
|
+
display: none;
|
|
3369
|
+
padding: 14px 18px 16px;
|
|
3370
|
+
max-height: calc(80vh - 140px);
|
|
3371
|
+
overflow-y: auto;
|
|
3372
|
+
overflow-x: hidden;
|
|
3373
|
+
}
|
|
3374
|
+
.pp-tab-content.active { display: block; }
|
|
3375
|
+
.terminal-wrapper {
|
|
3376
|
+
background: #000;
|
|
3377
|
+
border-radius: var(--nh-radius-md);
|
|
3378
|
+
padding: 10px;
|
|
3379
|
+
margin-top: 8px;
|
|
3380
|
+
height: 320px;
|
|
3381
|
+
position: relative;
|
|
3382
|
+
overflow: hidden;
|
|
3383
|
+
border: 1px solid var(--nh-border);
|
|
3384
|
+
}
|
|
3385
|
+
#pp-terminal-container {
|
|
3386
|
+
width: 100%;
|
|
3387
|
+
height: 100%;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
2497
3390
|
.profile-popup-bio {
|
|
2498
3391
|
font-size: 12px;
|
|
2499
3392
|
color: var(--text-dim);
|
|
@@ -2504,19 +3397,49 @@
|
|
|
2504
3397
|
.profile-popup-stats {
|
|
2505
3398
|
display: grid;
|
|
2506
3399
|
grid-template-columns: 1fr 1fr;
|
|
2507
|
-
gap:
|
|
3400
|
+
gap: 6px;
|
|
2508
3401
|
margin-top: 8px;
|
|
2509
3402
|
}
|
|
2510
3403
|
|
|
2511
3404
|
.profile-popup-stat {
|
|
2512
3405
|
font-size: 11px;
|
|
2513
3406
|
color: var(--text-dim);
|
|
3407
|
+
background: var(--surface-2);
|
|
3408
|
+
border-radius: 8px;
|
|
3409
|
+
padding: 8px 10px;
|
|
2514
3410
|
}
|
|
2515
3411
|
|
|
2516
3412
|
.profile-popup-stat span {
|
|
2517
3413
|
font-weight: 600;
|
|
2518
3414
|
color: var(--text);
|
|
3415
|
+
display: block;
|
|
3416
|
+
font-size: 16px;
|
|
3417
|
+
margin-bottom: 2px;
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
/* Actions tab */
|
|
3421
|
+
.pp-actions-grid {
|
|
3422
|
+
display: grid;
|
|
3423
|
+
grid-template-columns: 1fr 1fr;
|
|
3424
|
+
gap: 8px;
|
|
3425
|
+
}
|
|
3426
|
+
.pp-action-btn {
|
|
3427
|
+
display: flex;
|
|
3428
|
+
flex-direction: column;
|
|
3429
|
+
align-items: center;
|
|
3430
|
+
gap: 6px;
|
|
3431
|
+
padding: 14px 8px;
|
|
3432
|
+
background: var(--surface-2);
|
|
3433
|
+
border: 1px solid var(--border);
|
|
3434
|
+
border-radius: 10px;
|
|
3435
|
+
cursor: pointer;
|
|
3436
|
+
color: var(--text);
|
|
3437
|
+
font-size: 11px;
|
|
3438
|
+
font-weight: 500;
|
|
3439
|
+
transition: background 0.15s, border-color 0.15s;
|
|
2519
3440
|
}
|
|
3441
|
+
.pp-action-btn:hover { background: var(--surface-3); border-color: var(--nh-accent-dim); }
|
|
3442
|
+
.pp-action-btn svg { width: 20px; height: 20px; stroke: var(--nh-accent); fill: none; stroke-width: 1.5; }
|
|
2520
3443
|
|
|
2521
3444
|
/* ===== v3.0: WORKSPACES TAB ===== */
|
|
2522
3445
|
.workspaces-area {
|
|
@@ -2721,6 +3644,7 @@
|
|
|
2721
3644
|
margin-top: 4px;
|
|
2722
3645
|
display: -webkit-box;
|
|
2723
3646
|
-webkit-line-clamp: 2;
|
|
3647
|
+
line-clamp: 2;
|
|
2724
3648
|
-webkit-box-orient: vertical;
|
|
2725
3649
|
overflow: hidden;
|
|
2726
3650
|
}
|
|
@@ -3242,7 +4166,7 @@
|
|
|
3242
4166
|
}
|
|
3243
4167
|
.launch-status-item .ls-dot.pending { background: var(--text-muted); }
|
|
3244
4168
|
.launch-status-item .ls-dot.launching { background: var(--yellow); animation: pulse-dot 1s infinite; }
|
|
3245
|
-
.launch-status-item .ls-dot.launched { background: var(--
|
|
4169
|
+
.launch-status-item .ls-dot.launched { background: var(--nh-accent); }
|
|
3246
4170
|
.launch-status-item .ls-dot.registered { background: var(--green); }
|
|
3247
4171
|
.launch-status-item .ls-dot.failed { background: var(--red); }
|
|
3248
4172
|
@keyframes pulse-dot { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
@@ -3523,8 +4447,8 @@
|
|
|
3523
4447
|
<div class="header">
|
|
3524
4448
|
<div class="header-left">
|
|
3525
4449
|
<button class="mobile-toggle" onclick="toggleSidebar()" aria-label="Menu">☰</button>
|
|
3526
|
-
<
|
|
3527
|
-
<div class="logo">
|
|
4450
|
+
<img src="logo.svg" width="40" height="40" alt="" aria-hidden="true" style="flex-shrink:0">
|
|
4451
|
+
<div class="logo">NeoHive</div>
|
|
3528
4452
|
<!-- Hidden stat elements for JS compatibility -->
|
|
3529
4453
|
<span id="stat-messages" style="display:none">0</span>
|
|
3530
4454
|
<span id="stat-agents" style="display:none">0</span>
|
|
@@ -3534,7 +4458,10 @@
|
|
|
3534
4458
|
<div id="managed-badge" style="display:none"><span id="managed-phase"></span><span id="managed-floor-info"></span></div>
|
|
3535
4459
|
</div>
|
|
3536
4460
|
<div class="header-actions">
|
|
3537
|
-
<
|
|
4461
|
+
<div class="header-conn" title="Live updates connected">
|
|
4462
|
+
<span class="conn-dot" aria-hidden="true"></span>
|
|
4463
|
+
<span>Connected</span>
|
|
4464
|
+
</div>
|
|
3538
4465
|
<!-- Settings dropdown -->
|
|
3539
4466
|
<div style="position:relative;display:inline-block">
|
|
3540
4467
|
<button class="header-settings-btn" onclick="toggleSettingsMenu()" title="Settings">
|
|
@@ -3588,9 +4515,10 @@
|
|
|
3588
4515
|
<!-- APP LAYOUT -->
|
|
3589
4516
|
<div class="app">
|
|
3590
4517
|
<!-- UNIFIED NAV SIDEBAR -->
|
|
4518
|
+
<div class="sidebar-overlay" id="sidebar-overlay" onclick="closeSidebar()"></div>
|
|
3591
4519
|
<nav class="nav-sidebar expanded" id="nav-sidebar">
|
|
3592
4520
|
<div class="nav-sidebar-header">
|
|
3593
|
-
<span class="nav-sidebar-logo"
|
|
4521
|
+
<span class="nav-sidebar-logo">Workspace</span>
|
|
3594
4522
|
<button class="nav-sidebar-toggle" onclick="toggleNavSidebar()" title="Collapse sidebar">◀</button>
|
|
3595
4523
|
</div>
|
|
3596
4524
|
<!-- Navigation -->
|
|
@@ -3646,8 +4574,16 @@
|
|
|
3646
4574
|
<div class="nav-item" data-view="plan" onclick="switchView('plan')">
|
|
3647
4575
|
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 2l10 6-10 6z"/></svg></span><span class="nav-text">Plan</span>
|
|
3648
4576
|
</div>
|
|
4577
|
+
</div>
|
|
4578
|
+
<div class="nav-sidebar-divider"></div>
|
|
4579
|
+
<!-- Governance -->
|
|
4580
|
+
<div class="nav-sidebar-section nav-sidebar-governance">
|
|
4581
|
+
<div class="nav-sidebar-label">GOVERNANCE</div>
|
|
3649
4582
|
<div class="nav-item" data-view="rules" onclick="switchView('rules')">
|
|
3650
|
-
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 1L2 4v4c0 3.5 2.5 6.5 6 7.5 3.5-1 6-4 6-7.5V4z"/></svg></span><span class="nav-text">
|
|
4583
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 1L2 4v4c0 3.5 2.5 6.5 6 7.5 3.5-1 6-4 6-7.5V4z"/></svg></span><span class="nav-text">Policies</span>
|
|
4584
|
+
</div>
|
|
4585
|
+
<div class="nav-item" data-view="audit-log" onclick="switchView('audit-log')">
|
|
4586
|
+
<span class="nav-icon"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 4v8h10V4H3zm2 2h6v1H5V6zm0 2h6v1H5V8z"/></svg></span><span class="nav-text">Audit Log</span>
|
|
3651
4587
|
</div>
|
|
3652
4588
|
</div>
|
|
3653
4589
|
</nav>
|
|
@@ -3676,7 +4612,8 @@
|
|
|
3676
4612
|
<div class="view-tab" id="tab-workflows" onclick="switchView('workflows')">Workflows</div>
|
|
3677
4613
|
<div class="view-tab" id="tab-plan" onclick="switchView('plan')">Plan</div>
|
|
3678
4614
|
<div class="view-tab" id="tab-launch" onclick="switchView('launch')">Launch</div>
|
|
3679
|
-
<div class="view-tab" id="tab-rules" onclick="switchView('rules')">
|
|
4615
|
+
<div class="view-tab" id="tab-rules" onclick="switchView('rules')">Policies</div>
|
|
4616
|
+
<div class="view-tab" id="tab-audit-log" onclick="switchView('audit-log')">Audit Log</div>
|
|
3680
4617
|
<div class="view-tab" id="tab-stats" onclick="switchView('stats')">Stats</div>
|
|
3681
4618
|
<div class="view-tab" id="tab-docs" onclick="switchView('docs')">Docs</div>
|
|
3682
4619
|
</div>
|
|
@@ -3705,9 +4642,13 @@
|
|
|
3705
4642
|
<div class="plan-area" id="monitor-panel" style="display:none"></div>
|
|
3706
4643
|
<div class="launch-area" id="launch-area"></div>
|
|
3707
4644
|
<div class="rules-area" id="rules-area"></div>
|
|
4645
|
+
<div class="audit-log-area" id="audit-log-area" style="display:none; padding:16px 20px; overflow-y:auto; flex:1"></div>
|
|
3708
4646
|
<div class="stats-area" id="stats-area"></div>
|
|
3709
4647
|
<div class="docs-area" id="docs-area"><div id="docs-content"></div></div>
|
|
3710
|
-
<button class="scroll-bottom" id="scroll-bottom" onclick="scrollToBottom()"
|
|
4648
|
+
<button class="scroll-bottom" id="scroll-bottom" type="button" onclick="scrollToBottom()" aria-label="Jump to latest messages.">
|
|
4649
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px;"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>
|
|
4650
|
+
<span id="new-msg-count" style="margin-right:4px">0</span> <span id="new-msg-label">new messages</span>
|
|
4651
|
+
</button>
|
|
3711
4652
|
<div class="typing-bar" id="typing-bar"></div>
|
|
3712
4653
|
|
|
3713
4654
|
<!-- MESSAGE INPUT -->
|
|
@@ -3733,16 +4674,48 @@
|
|
|
3733
4674
|
<div class="profile-popup" id="profile-popup" onclick="event.stopPropagation()">
|
|
3734
4675
|
<div class="profile-popup-header">
|
|
3735
4676
|
<img class="profile-popup-avatar" id="pp-avatar" src="data:," style="display:none">
|
|
3736
|
-
<div>
|
|
3737
|
-
<div
|
|
4677
|
+
<div style="flex:1;min-width:0">
|
|
4678
|
+
<div style="display:flex;align-items:center;gap:4px;flex-wrap:wrap">
|
|
4679
|
+
<div class="profile-popup-name" id="pp-name"></div>
|
|
4680
|
+
<span id="pp-status-badge" class="pp-status-badge"></span>
|
|
4681
|
+
</div>
|
|
3738
4682
|
<div class="profile-popup-id" id="pp-id"></div>
|
|
3739
4683
|
</div>
|
|
3740
4684
|
</div>
|
|
3741
|
-
<div
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
4685
|
+
<div class="pp-tabs">
|
|
4686
|
+
<button class="pp-tab active" data-tab="stats" onclick="switchPpTab('stats')">Stats</button>
|
|
4687
|
+
<button class="pp-tab" data-tab="actions" onclick="switchPpTab('actions')">Actions</button>
|
|
4688
|
+
<button class="pp-tab" data-tab="profile" onclick="switchPpTab('profile')">Profile</button>
|
|
4689
|
+
<button class="pp-tab" data-tab="terminal" onclick="switchPpTab('terminal', true)" style="display:none">Terminal</button>
|
|
4690
|
+
</div>
|
|
4691
|
+
<!-- Stats Tab -->
|
|
4692
|
+
<div class="pp-tab-content active" id="pp-tab-stats">
|
|
4693
|
+
<div id="pp-badges" style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px"></div>
|
|
4694
|
+
<div class="profile-popup-stats" id="pp-stats"></div>
|
|
4695
|
+
<div id="pp-skills-readonly" style="margin-top:12px"></div>
|
|
4696
|
+
</div>
|
|
4697
|
+
<!-- Actions Tab -->
|
|
4698
|
+
<div class="pp-tab-content" id="pp-tab-actions">
|
|
4699
|
+
<div class="pp-actions-grid" id="pp-actions"></div>
|
|
4700
|
+
</div>
|
|
4701
|
+
<!-- Profile Tab -->
|
|
4702
|
+
<div class="pp-tab-content" id="pp-tab-profile">
|
|
4703
|
+
<div class="profile-popup-bio" id="pp-bio"></div>
|
|
4704
|
+
<div id="pp-skills-container" style="margin-top:10px;padding-top:10px;border-top:1px solid var(--border)"></div>
|
|
4705
|
+
<button class="btn btn-primary" style="width:100%;margin-top:12px" id="pp-edit-btn" onclick="openProfileEditor()">Edit Profile</button>
|
|
4706
|
+
<button class="btn btn-danger" style="width:100%;margin-top:6px" id="pp-delete-btn" onclick="confirmDeleteAgent()">Delete Agent</button>
|
|
4707
|
+
</div>
|
|
4708
|
+
<!-- Terminal Tab -->
|
|
4709
|
+
<div class="pp-tab-content" id="pp-tab-terminal" style="display:none">
|
|
4710
|
+
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.5px;color:var(--text-muted);margin-bottom:6px">Live Terminal Bridge</div>
|
|
4711
|
+
<div class="terminal-wrapper">
|
|
4712
|
+
<div id="pp-terminal-container"></div>
|
|
4713
|
+
</div>
|
|
4714
|
+
<div style="font-size:10px;color:var(--text-muted);margin-top:8px;display:flex;justify-content:space-between">
|
|
4715
|
+
<span>Real-time agent stream</span>
|
|
4716
|
+
<span id="terminal-status">Ready</span>
|
|
4717
|
+
</div>
|
|
4718
|
+
</div>
|
|
3746
4719
|
</div>
|
|
3747
4720
|
|
|
3748
4721
|
<!-- Character Designer Panel -->
|
|
@@ -3790,6 +4763,12 @@
|
|
|
3790
4763
|
<button class="cd-tab" onclick="cdSwitchTab('face', this)">Face</button>
|
|
3791
4764
|
<button class="cd-tab" onclick="cdSwitchTab('outfit', this)">Outfit</button>
|
|
3792
4765
|
<button class="cd-tab" onclick="cdSwitchTab('accessories', this)">Acc.</button>
|
|
4766
|
+
<button class="cd-tab" onclick="cdSwitchTab('stats', this)">Stats</button>
|
|
4767
|
+
</div>
|
|
4768
|
+
|
|
4769
|
+
<!-- Tab: Stats -->
|
|
4770
|
+
<div class="cd-tab-content" id="cd-tab-stats">
|
|
4771
|
+
<div id="pe-stats-container" style="display:grid;grid-template-columns:1fr 1fr;gap:8px;font-size:12px;color:var(--text)"></div>
|
|
3793
4772
|
</div>
|
|
3794
4773
|
|
|
3795
4774
|
<!-- Tab: Body -->
|
|
@@ -4181,6 +5160,28 @@ function formatTime(ts) {
|
|
|
4181
5160
|
return new Date(ts).toLocaleTimeString([], { hour:'2-digit', minute:'2-digit', second:'2-digit' });
|
|
4182
5161
|
}
|
|
4183
5162
|
|
|
5163
|
+
function formatShortTime(ts) {
|
|
5164
|
+
return new Date(ts).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
5165
|
+
}
|
|
5166
|
+
|
|
5167
|
+
/** Message card header: name, To line, clock time, badges (Stitch-style). */
|
|
5168
|
+
function buildMsgCardHead(fromDisplay, toDisplay, color, ts, badgesHtml, opt) {
|
|
5169
|
+
opt = opt || {};
|
|
5170
|
+
var showTo = opt.showTo !== false;
|
|
5171
|
+
var timeStr = formatShortTime(ts);
|
|
5172
|
+
var badges = badgesHtml || '';
|
|
5173
|
+
return '<div class="msg-card-head">' +
|
|
5174
|
+
'<div class="msg-card-ident">' +
|
|
5175
|
+
'<span class="msg-from" style="color:' + color + '">' + escapeHtml(fromDisplay) + '</span>' +
|
|
5176
|
+
(showTo ? '<div class="msg-to-line"><span class="msg-to-label">To</span> ' + escapeHtml(toDisplay) + '</div>' : '') +
|
|
5177
|
+
'</div>' +
|
|
5178
|
+
'<div class="msg-card-head-right">' +
|
|
5179
|
+
'<span class="msg-time" title="' + formatTime(ts) + '">' + timeStr + '</span>' +
|
|
5180
|
+
(badges ? '<div class="msg-badges">' + badges + '</div>' : '') +
|
|
5181
|
+
'</div>' +
|
|
5182
|
+
'</div>';
|
|
5183
|
+
}
|
|
5184
|
+
|
|
4184
5185
|
function timeAgo(ts) {
|
|
4185
5186
|
var secs = Math.floor((Date.now() - new Date(ts).getTime()) / 1000);
|
|
4186
5187
|
if (secs < 5) return 'just now';
|
|
@@ -4372,12 +5373,13 @@ function renderTable(rows) {
|
|
|
4372
5373
|
|
|
4373
5374
|
// ==================== AGENT MONITORING ====================
|
|
4374
5375
|
|
|
4375
|
-
|
|
5376
|
+
/* Provider label colors: use design-system tokens (theme-aware). */
|
|
5377
|
+
var PROVIDER_COLOR_VARS = { claude: 'var(--nh-accent)', anthropic: 'var(--nh-accent)', openai: 'var(--nh-green)', gemini: 'var(--nh-blue)', google: 'var(--nh-blue)', cursor: 'var(--nh-purple)' };
|
|
4376
5378
|
|
|
4377
5379
|
function getProviderBadge(provider) {
|
|
4378
5380
|
if (!provider) return '';
|
|
4379
5381
|
var key = provider.toLowerCase();
|
|
4380
|
-
var c =
|
|
5382
|
+
var c = PROVIDER_COLOR_VARS[key] || 'var(--text-muted)';
|
|
4381
5383
|
return '<span style="color:' + c + ';font-size:9px;font-weight:600;margin-right:4px">' + escapeHtml(provider) + '</span>';
|
|
4382
5384
|
}
|
|
4383
5385
|
|
|
@@ -4423,7 +5425,7 @@ function renderAgents(agents) {
|
|
|
4423
5425
|
}
|
|
4424
5426
|
|
|
4425
5427
|
var collapsedSig = Object.keys(collapsedRoleGroups).filter(function(k) { return collapsedRoleGroups[k]; }).sort().join(',');
|
|
4426
|
-
var structuralPart = keys.map(function(k) { var a = agents[k]; return k + ':' + (a.alive ? '1' : '0') + ':' + (a.is_listening ? 'L' : 'W') + ':' + (a.role || '') + ':' + (a.status || ''); }).join('|');
|
|
5428
|
+
var structuralPart = keys.map(function(k) { var a = agents[k]; return k + ':' + (a.alive ? '1' : '0') + ':' + (a.is_listening ? 'L' : 'W') + ':' + (a.role || '') + ':' + (a.status || '') + ':' + (a.skills || []).join(','); }).join('|');
|
|
4427
5429
|
var useGrouping = filtered.length > 6;
|
|
4428
5430
|
var layoutHash = structuralPart + '|F:' + agentFilterSearch + ':' + agentFilterRole + ':' + agentFilterStatus + '|C:' + collapsedSig + '|G:' + (useGrouping ? '1' : '0');
|
|
4429
5431
|
|
|
@@ -4432,14 +5434,14 @@ function renderAgents(agents) {
|
|
|
4432
5434
|
_lastAgentsHash = layoutHash;
|
|
4433
5435
|
var alertEl0 = document.getElementById('alert-count');
|
|
4434
5436
|
if (alertEl0) alertEl0.style.display = 'none';
|
|
4435
|
-
updateInjectTargets(
|
|
5437
|
+
updateInjectTargets(agents);
|
|
4436
5438
|
return;
|
|
4437
5439
|
}
|
|
4438
5440
|
|
|
4439
5441
|
// Group by role
|
|
4440
5442
|
var ROLE_ORDER = ['lead', 'backend', 'frontend', 'quality', 'monitor', 'advisor', ''];
|
|
4441
5443
|
var ROLE_LABELS = { lead: 'Lead', backend: 'Backend', frontend: 'Frontend', quality: 'Quality', monitor: 'Monitor', advisor: 'Advisor', '': 'Unassigned' };
|
|
4442
|
-
var ROLE_COLORS = { lead: 'var(--accent)', backend: '
|
|
5444
|
+
var ROLE_COLORS = { lead: 'var(--nh-accent)', backend: 'var(--nh-amber)', frontend: 'var(--nh-purple)', quality: 'var(--nh-green)', monitor: 'var(--nh-red)', advisor: 'var(--nh-purple)', '': 'var(--nh-text-muted)' };
|
|
4443
5445
|
var roleGroups = {};
|
|
4444
5446
|
for (var g = 0; g < filtered.length; g++) {
|
|
4445
5447
|
var gname = filtered[g];
|
|
@@ -4457,13 +5459,24 @@ function renderAgents(agents) {
|
|
|
4457
5459
|
var name = agentNames[i];
|
|
4458
5460
|
var info = agents[name];
|
|
4459
5461
|
var color = getColor(name);
|
|
4460
|
-
var state = !info.alive ? 'offline' : info.is_listening ? 'listening' :
|
|
4461
|
-
if (state === 'idle') sleepCount++;
|
|
5462
|
+
var state = info.status || (!info.alive ? 'offline' : info.is_listening ? 'listening' : 'working');
|
|
5463
|
+
if (state === 'idle' || state === 'stale') sleepCount++;
|
|
5464
|
+
var currentToolHtml = (state === 'working' && info.current_tool)
|
|
5465
|
+
? '<div class="agent-current-tool">\u2699 ' + escapeHtml(info.current_tool) + '</div>'
|
|
5466
|
+
: '';
|
|
4462
5467
|
|
|
4463
5468
|
// Compact activity text
|
|
4464
5469
|
var activityText = '';
|
|
4465
|
-
if (state === '
|
|
5470
|
+
if (state === 'unknown') {
|
|
5471
|
+
activityText = 'never started';
|
|
5472
|
+
} else if (state === 'stale') {
|
|
5473
|
+
activityText = 'stale';
|
|
5474
|
+
} else if (state === 'offline' || state === 'dead') {
|
|
4466
5475
|
activityText = 'offline';
|
|
5476
|
+
} else if (state === 'stuck') {
|
|
5477
|
+
activityText = '⚠ no listen()';
|
|
5478
|
+
} else if (state === 'unresponsive') {
|
|
5479
|
+
activityText = 'unresponsive';
|
|
4467
5480
|
} else if (info.idle_seconds != null) {
|
|
4468
5481
|
if (info.idle_seconds < 5) activityText = 'just now';
|
|
4469
5482
|
else if (info.idle_seconds < 60) activityText = info.idle_seconds + 's ago';
|
|
@@ -4484,20 +5497,97 @@ function renderAgents(agents) {
|
|
|
4484
5497
|
if (activityText) subtitleParts.push(activityText);
|
|
4485
5498
|
var subtitleText = subtitleParts.join(' \u00B7 ');
|
|
4486
5499
|
|
|
5500
|
+
// Skills: limited to 3 for sidebar cards
|
|
5501
|
+
var skillsHtml = '';
|
|
5502
|
+
if (info.skills && info.skills.length > 0) {
|
|
5503
|
+
var platformClass = 'platform-' + (info.provider || 'unknown').toLowerCase();
|
|
5504
|
+
var platformSkills = info.platform_skills || [];
|
|
5505
|
+
skillsHtml = '<div class="agent-skills">';
|
|
5506
|
+
for (var s = 0; s < Math.min(info.skills.length, 3); s++) {
|
|
5507
|
+
var skill = info.skills[s];
|
|
5508
|
+
var isPlatform = platformSkills.indexOf(skill) !== -1;
|
|
5509
|
+
skillsHtml += '<span class="skill-tag' + (isPlatform ? ' ' + platformClass : '') + '">' + escapeHtml(skill) + '</span>';
|
|
5510
|
+
}
|
|
5511
|
+
if (info.skills.length > 3) {
|
|
5512
|
+
skillsHtml += '<span class="skill-tag" style="opacity:0.5">+' + (info.skills.length - 3) + '</span>';
|
|
5513
|
+
}
|
|
5514
|
+
skillsHtml += '</div>';
|
|
5515
|
+
}
|
|
5516
|
+
|
|
5517
|
+
// Nudge button (Bell icon)
|
|
5518
|
+
var nudgeBtnHtml = '';
|
|
5519
|
+
if (state !== 'offline' && state !== 'dead' && state !== 'unknown') {
|
|
5520
|
+
nudgeBtnHtml = '<div class="agent-nudge-btn" title="Nudge agent to listen()" onclick="sendNudge(\'' + escapeHtml(name) + '\', event)">' +
|
|
5521
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>' +
|
|
5522
|
+
'</div>';
|
|
5523
|
+
}
|
|
5524
|
+
|
|
5525
|
+
// Liveness Sparkline (Heartbeat dots)
|
|
5526
|
+
var sparklineHtml = '';
|
|
5527
|
+
if (state !== 'offline' && state !== 'dead' && state !== 'unknown') {
|
|
5528
|
+
var dots = '';
|
|
5529
|
+
var history = info.listen_history || [];
|
|
5530
|
+
// Fallback: synthesize from last_listen_call if listen_history is missing
|
|
5531
|
+
if (history.length === 0) {
|
|
5532
|
+
var llc = info.last_listen_call || info.last_listened_at;
|
|
5533
|
+
if (llc) {
|
|
5534
|
+
var llcTs = typeof llc === 'string' ? new Date(llc).getTime() : llc;
|
|
5535
|
+
if (llcTs > 0) history = [llcTs];
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
for (var d = 0; d < 10; d++) {
|
|
5539
|
+
var dotClass = 'hb-dot';
|
|
5540
|
+
if (d < history.length) {
|
|
5541
|
+
var age = (Date.now() - history[d]) / 1000;
|
|
5542
|
+
if (age < 30) dotClass += ' active';
|
|
5543
|
+
else if (age < 120) dotClass += ' stale';
|
|
5544
|
+
} else {
|
|
5545
|
+
dotClass += ' empty';
|
|
5546
|
+
}
|
|
5547
|
+
dots += '<div class="' + dotClass + '"></div>';
|
|
5548
|
+
}
|
|
5549
|
+
sparklineHtml = '<div class="listen-sparkline"><span class="listen-sparkline-label">Liveness</span>' + dots + '</div>';
|
|
5550
|
+
}
|
|
5551
|
+
|
|
5552
|
+
var indicatorHtml = (state === 'listening' || state === 'working')
|
|
5553
|
+
? '<span class="agent-state-pill ' + state + '">' + state + '</span>'
|
|
5554
|
+
: '<span class="agent-status-dot ' + state + '" title="' + state + '"></span>';
|
|
4487
5555
|
cardHtml += '<div class="' + cardClass + '" data-agent="' + escapeHtml(name) + '" onclick="showProfilePopup(\'' + escapeHtml(name) + '\', event)">' +
|
|
5556
|
+
nudgeBtnHtml +
|
|
4488
5557
|
'<div class="agent-top">' +
|
|
4489
5558
|
avatarHtml +
|
|
4490
5559
|
'<div class="agent-info">' +
|
|
4491
5560
|
'<div class="agent-name" style="color:' + color + '">' + escapeHtml(displayName) + '</div>' +
|
|
4492
5561
|
'<div class="agent-subtitle">' + escapeHtml(subtitleText) + '</div>' +
|
|
5562
|
+
currentToolHtml +
|
|
5563
|
+
skillsHtml +
|
|
5564
|
+
sparklineHtml +
|
|
4493
5565
|
'</div>' +
|
|
4494
|
-
|
|
5566
|
+
indicatorHtml +
|
|
4495
5567
|
'</div>' +
|
|
4496
5568
|
'</div>';
|
|
4497
5569
|
}
|
|
4498
5570
|
return cardHtml;
|
|
4499
5571
|
}
|
|
4500
5572
|
|
|
5573
|
+
function sendNudge(name, event) {
|
|
5574
|
+
if (event) event.stopPropagation();
|
|
5575
|
+
fetch('/api/inject', {
|
|
5576
|
+
method: 'POST',
|
|
5577
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5578
|
+
body: JSON.stringify({
|
|
5579
|
+
to: name,
|
|
5580
|
+
content: '[NUDGE] Your neohive session appears stuck. Please call listen() now to process messages.',
|
|
5581
|
+
priority: 'critical'
|
|
5582
|
+
})
|
|
5583
|
+
})
|
|
5584
|
+
.then(r => r.json())
|
|
5585
|
+
.then(d => {
|
|
5586
|
+
if (d.success) showToast('Nudge sent to ' + name, 'success');
|
|
5587
|
+
else showToast('Failed to nudge: ' + (d.error || 'unknown error'), 'error');
|
|
5588
|
+
});
|
|
5589
|
+
}
|
|
5590
|
+
|
|
4501
5591
|
if (useGrouping) {
|
|
4502
5592
|
for (var r = 0; r < ROLE_ORDER.length; r++) {
|
|
4503
5593
|
var role = ROLE_ORDER[r];
|
|
@@ -4531,9 +5621,13 @@ function renderAgents(agents) {
|
|
|
4531
5621
|
var pCard = el.querySelector('[data-agent="' + pName + '"]');
|
|
4532
5622
|
if (!pCard) { didPatch = false; break; }
|
|
4533
5623
|
var pInfo = agents[pName];
|
|
4534
|
-
var pState = !pInfo.alive ? 'offline' : pInfo.is_listening ? 'listening' :
|
|
5624
|
+
var pState = pInfo.status || (!pInfo.alive ? 'offline' : pInfo.is_listening ? 'listening' : 'working');
|
|
4535
5625
|
var dot = pCard.querySelector('.agent-status-dot');
|
|
4536
5626
|
if (dot) { dot.className = 'agent-status-dot ' + pState; dot.title = pState; }
|
|
5627
|
+
var pill = pCard.querySelector('.agent-state-pill');
|
|
5628
|
+
if (pill) { pill.className = 'agent-state-pill ' + pState; pill.textContent = pState; }
|
|
5629
|
+
var ctEl = pCard.querySelector('.agent-current-tool');
|
|
5630
|
+
if (ctEl) { ctEl.textContent = (pState === 'working' && pInfo.current_tool) ? '\u2699 ' + pInfo.current_tool : ''; }
|
|
4537
5631
|
var sub = pCard.querySelector('.agent-subtitle');
|
|
4538
5632
|
if (sub) {
|
|
4539
5633
|
var parts = [];
|
|
@@ -4558,6 +5652,9 @@ function renderAgents(agents) {
|
|
|
4558
5652
|
_lastAgentsHash = layoutHash;
|
|
4559
5653
|
}
|
|
4560
5654
|
|
|
5655
|
+
// Update inject target dropdown — must run BEFORE the alertEl guard which may early-return
|
|
5656
|
+
updateInjectTargets(agents);
|
|
5657
|
+
|
|
4561
5658
|
// Update alert badge
|
|
4562
5659
|
var alertEl = document.getElementById('alert-count');
|
|
4563
5660
|
if (!alertEl) return;
|
|
@@ -4567,9 +5664,6 @@ function renderAgents(agents) {
|
|
|
4567
5664
|
} else {
|
|
4568
5665
|
alertEl.style.display = 'none';
|
|
4569
5666
|
}
|
|
4570
|
-
|
|
4571
|
-
// Update inject target dropdown
|
|
4572
|
-
updateInjectTargets(keys);
|
|
4573
5667
|
}
|
|
4574
5668
|
|
|
4575
5669
|
function sendNudge(agentName) {
|
|
@@ -4679,18 +5773,23 @@ function copyRespawnPrompt() {
|
|
|
4679
5773
|
|
|
4680
5774
|
var lastAgentKeys = '';
|
|
4681
5775
|
|
|
4682
|
-
function updateInjectTargets(
|
|
5776
|
+
function updateInjectTargets(agents) {
|
|
5777
|
+
// Build sorted list of real agents (exclude system/internal names)
|
|
5778
|
+
var keys = Object.keys(agents).filter(function(k) {
|
|
5779
|
+
return k !== '__system__' && k !== '__user__' && k !== 'Dashboard';
|
|
5780
|
+
}).sort();
|
|
4683
5781
|
var joined = keys.join(',');
|
|
4684
5782
|
if (joined === lastAgentKeys) return;
|
|
4685
5783
|
lastAgentKeys = joined;
|
|
4686
5784
|
|
|
4687
5785
|
var sel = document.getElementById('inject-target');
|
|
5786
|
+
if (!sel) return;
|
|
4688
5787
|
var current = sel.value;
|
|
4689
5788
|
sel.innerHTML = '<option value="">Select agent...</option>';
|
|
4690
5789
|
for (var i = 0; i < keys.length; i++) {
|
|
4691
5790
|
var opt = document.createElement('option');
|
|
4692
5791
|
opt.value = keys[i];
|
|
4693
|
-
opt.textContent = keys[i];
|
|
5792
|
+
opt.textContent = (agents[keys[i]] && agents[keys[i]].display_name) || keys[i];
|
|
4694
5793
|
sel.appendChild(opt);
|
|
4695
5794
|
}
|
|
4696
5795
|
// Broadcast option
|
|
@@ -4782,6 +5881,33 @@ function renderThreads(messages) {
|
|
|
4782
5881
|
|
|
4783
5882
|
// ==================== MESSAGES ====================
|
|
4784
5883
|
|
|
5884
|
+
/** Before replacing #messages innerHTML, remember which row was at the top of the viewport (stable vs raw scrollTop). */
|
|
5885
|
+
function captureMessageScrollAnchor(container) {
|
|
5886
|
+
if (!container) return null;
|
|
5887
|
+
var st = container.scrollTop;
|
|
5888
|
+
var nodes = container.querySelectorAll('.nh-msg-card[data-msg-id]');
|
|
5889
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
5890
|
+
var n = nodes[i];
|
|
5891
|
+
var mid = n.getAttribute('data-msg-id');
|
|
5892
|
+
if (!mid) continue;
|
|
5893
|
+
if (n.offsetTop + n.offsetHeight > st + 0.5) {
|
|
5894
|
+
return { id: mid, delta: n.offsetTop - st };
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
return null;
|
|
5898
|
+
}
|
|
5899
|
+
|
|
5900
|
+
function restoreMessageScrollAnchor(container, anchor) {
|
|
5901
|
+
if (!anchor || !container) return;
|
|
5902
|
+
var nodes = container.querySelectorAll('.nh-msg-card[data-msg-id]');
|
|
5903
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
5904
|
+
if (nodes[i].getAttribute('data-msg-id') === anchor.id) {
|
|
5905
|
+
container.scrollTop = Math.max(0, nodes[i].offsetTop - anchor.delta);
|
|
5906
|
+
return;
|
|
5907
|
+
}
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
|
|
4785
5911
|
function renderMessages(messages) {
|
|
4786
5912
|
var el = document.getElementById('messages');
|
|
4787
5913
|
|
|
@@ -4854,9 +5980,7 @@ function renderMessages(messages) {
|
|
|
4854
5980
|
// Track new messages while scrolled up
|
|
4855
5981
|
if (isNew && !autoScroll) {
|
|
4856
5982
|
newWhileScrolled += (filtered.length - lastMessageCount);
|
|
4857
|
-
|
|
4858
|
-
countEl.textContent = newWhileScrolled;
|
|
4859
|
-
countEl.style.display = '';
|
|
5983
|
+
updateNewMsgPill(newWhileScrolled);
|
|
4860
5984
|
document.getElementById('scroll-bottom').classList.add('visible');
|
|
4861
5985
|
}
|
|
4862
5986
|
// Play sound if new messages arrived
|
|
@@ -4915,33 +6039,56 @@ function renderMessages(messages) {
|
|
|
4915
6039
|
}
|
|
4916
6040
|
|
|
4917
6041
|
var groupClass = isGrouped ? ' grouped' : '';
|
|
6042
|
+
var msgIdAttr = m.id ? ' data-msg-id="' + escapeHtml(m.id) + '"' : '';
|
|
4918
6043
|
|
|
4919
6044
|
var badges = '';
|
|
4920
6045
|
if (m.channel && m.channel !== 'general') badges += '<span class="badge-channel" onclick="filterChannel(\'' + escapeHtml(m.channel) + '\')" title="Channel: #' + escapeHtml(m.channel) + '">#' + escapeHtml(m.channel) + '</span>';
|
|
4921
6046
|
if (m.acked) badges += '<span class="badge badge-ack">ACK</span>';
|
|
4922
6047
|
if (m.thread_id) badges += '<span class="badge badge-thread">Thread</span>';
|
|
4923
|
-
if (m.edited) badges += '<span class="
|
|
6048
|
+
if (m.edited) badges += '<span class="nh-pill nh-pill--review nh-pill--sm" title="Edited ' + (m.edited_at ? new Date(m.edited_at).toLocaleString() : '') + '">edited</span>';
|
|
4924
6049
|
|
|
4925
6050
|
var msgAvatarHtml = getMsgAvatar(m.from, color);
|
|
4926
6051
|
|
|
4927
6052
|
if (isSystem) {
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
'
|
|
6053
|
+
var evtType = 'status';
|
|
6054
|
+
var evtIcon = '<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="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>';
|
|
6055
|
+
if (/\[CRITICAL\]/.test(m.content)) {
|
|
6056
|
+
evtType = 'critical';
|
|
6057
|
+
evtIcon = '<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 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
|
|
6058
|
+
} else if (/\[WARNING\]/.test(m.content)) {
|
|
6059
|
+
evtType = 'warning';
|
|
6060
|
+
evtIcon = '<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 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
|
|
6061
|
+
} else if (/\[VIOLATION\]/i.test(m.content)) {
|
|
6062
|
+
evtType = 'violation';
|
|
6063
|
+
evtIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>';
|
|
6064
|
+
} else if (/\[EVENT\]/.test(m.content)) {
|
|
6065
|
+
evtType = 'event';
|
|
6066
|
+
evtIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
6067
|
+
} else if (/\[REVIEW/.test(m.content)) {
|
|
6068
|
+
evtType = 'review';
|
|
6069
|
+
evtIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
|
|
6070
|
+
} else if (/\[ROLE/.test(m.content)) {
|
|
6071
|
+
evtType = 'role';
|
|
6072
|
+
evtIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>';
|
|
6073
|
+
} else if (/\[STATUS\]/.test(m.content) || /\[QUALITY/.test(m.content)) {
|
|
6074
|
+
evtType = 'status';
|
|
6075
|
+
}
|
|
6076
|
+
var evtTime = new Date(m.timestamp).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
|
|
6077
|
+
var evtText = m.content.replace(/^\[[\w\s]+\]\s*/, '');
|
|
6078
|
+
html += '<div class="message system-event-banner evt-' + evtType + newClass + '"' + msgIdAttr + '>' +
|
|
6079
|
+
'<span class="event-icon">' + evtIcon + '</span>' +
|
|
6080
|
+
'<div class="event-body"><span class="event-text">' + renderMarkdown(evtText) + '</span></div>' +
|
|
6081
|
+
'<span class="event-time">' + evtTime + '</span>' +
|
|
6082
|
+
'</div>';
|
|
4932
6083
|
lastFrom = '';
|
|
4933
6084
|
lastTo = '';
|
|
4934
6085
|
} else if (m.type === 'handoff') {
|
|
4935
|
-
|
|
6086
|
+
var handoffBadges = '<span class="nh-pill nh-pill--review nh-pill--sm">Handoff</span>' + badges;
|
|
6087
|
+
html += '<div class="message nh-msg-card handoff-msg' + newClass + '"' + msgIdAttr + '>' +
|
|
4936
6088
|
buildMsgActions(m.id) +
|
|
4937
6089
|
msgAvatarHtml +
|
|
4938
6090
|
'<div class="msg-body">' +
|
|
4939
|
-
|
|
4940
|
-
'<div class="msg-header">' +
|
|
4941
|
-
'<span class="msg-from" style="color:' + color + '" title="@' + escapeHtml(m.from) + '">' + escapeHtml(fromDisplay) + '</span>' +
|
|
4942
|
-
'<span class="msg-time" title="' + formatTime(m.timestamp) + '">' + relativeTime(m.timestamp) + '</span>' +
|
|
4943
|
-
'<div class="msg-badges">' + badges + '</div>' +
|
|
4944
|
-
'</div>' +
|
|
6091
|
+
buildMsgCardHead(fromDisplay, toDisplay, color, m.timestamp, handoffBadges) +
|
|
4945
6092
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
4946
6093
|
buildReactionsHtml(m.id) +
|
|
4947
6094
|
buildReadReceipts(m.id) +
|
|
@@ -4952,17 +6099,11 @@ function renderMessages(messages) {
|
|
|
4952
6099
|
var fileMeta = m.file || {};
|
|
4953
6100
|
var fileName = fileMeta.name || 'file';
|
|
4954
6101
|
var fileSize = fileMeta.size ? (fileMeta.size > 1024 ? Math.round(fileMeta.size / 1024) + ' KB' : fileMeta.size + ' B') : '';
|
|
4955
|
-
html += '<div class="message' + newClass + '">' +
|
|
6102
|
+
html += '<div class="message nh-msg-card' + newClass + '"' + msgIdAttr + '>' +
|
|
4956
6103
|
buildMsgActions(m.id) +
|
|
4957
6104
|
msgAvatarHtml +
|
|
4958
6105
|
'<div class="msg-body">' +
|
|
4959
|
-
|
|
4960
|
-
'<span class="msg-from" style="color:' + color + '" title="@' + escapeHtml(m.from) + '">' + escapeHtml(fromDisplay) + '</span>' +
|
|
4961
|
-
'<span class="msg-arrow">→</span>' +
|
|
4962
|
-
'<span class="msg-to">' + escapeHtml(toDisplay) + '</span>' +
|
|
4963
|
-
'<span class="msg-time" title="' + formatTime(m.timestamp) + '">' + relativeTime(m.timestamp) + '</span>' +
|
|
4964
|
-
'<div class="msg-badges">' + badges + '</div>' +
|
|
4965
|
-
'</div>' +
|
|
6106
|
+
buildMsgCardHead(fromDisplay, toDisplay, color, m.timestamp, badges) +
|
|
4966
6107
|
'<div class="file-meta"><span class="file-icon" style="opacity:0.5">[ ]</span>' + escapeHtml(fileName) + '<span class="file-size">' + fileSize + '</span></div>' +
|
|
4967
6108
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
4968
6109
|
buildReactionsHtml(m.id) +
|
|
@@ -4972,18 +6113,12 @@ function renderMessages(messages) {
|
|
|
4972
6113
|
lastTo = m.to;
|
|
4973
6114
|
} else {
|
|
4974
6115
|
var userMsgClass = isUserMsg ? ' user-msg' : '';
|
|
4975
|
-
var replyBadge = isReplyToUser ? '<span class="
|
|
4976
|
-
html += '<div class="message' + newClass + groupClass + userMsgClass + '">' +
|
|
6116
|
+
var replyBadge = isReplyToUser ? '<span class="nh-pill nh-pill--review nh-pill--sm">Reply to you</span>' : '';
|
|
6117
|
+
html += '<div class="message nh-msg-card' + newClass + groupClass + userMsgClass + '"' + msgIdAttr + '>' +
|
|
4977
6118
|
buildMsgActions(m.id) +
|
|
4978
6119
|
msgAvatarHtml +
|
|
4979
6120
|
'<div class="msg-body">' +
|
|
4980
|
-
|
|
4981
|
-
'<span class="msg-from" style="color:' + color + '" title="@' + escapeHtml(m.from) + '">' + escapeHtml(fromDisplay) + '</span>' +
|
|
4982
|
-
'<span class="msg-arrow">→</span>' +
|
|
4983
|
-
'<span class="msg-to">' + escapeHtml(toDisplay) + '</span>' +
|
|
4984
|
-
'<span class="msg-time" title="' + formatTime(m.timestamp) + '">' + relativeTime(m.timestamp) + '</span>' +
|
|
4985
|
-
'<div class="msg-badges">' + replyBadge + badges + '</div>' +
|
|
4986
|
-
'</div>' +
|
|
6121
|
+
buildMsgCardHead(fromDisplay, toDisplay, color, m.timestamp, replyBadge + badges) +
|
|
4987
6122
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
4988
6123
|
buildReactionsHtml(m.id) +
|
|
4989
6124
|
buildReadReceipts(m.id) +
|
|
@@ -5029,20 +6164,33 @@ function renderMessages(messages) {
|
|
|
5029
6164
|
var prevTo = lastRenderedIds.length > 0 ? filtered[appendStart - 1].to : '';
|
|
5030
6165
|
for (var ni = appendStart; ni < filtered.length; ni++) {
|
|
5031
6166
|
var nm = filtered[ni];
|
|
5032
|
-
var nColor = getColor(nm.from);
|
|
5033
|
-
var
|
|
5034
|
-
var
|
|
5035
|
-
// Simplified: render each new message with full formatting
|
|
5036
|
-
var nDisplayName = nm.from;
|
|
6167
|
+
var nColor = nm.from === '__user__' ? 'var(--accent)' : getColor(nm.from);
|
|
6168
|
+
var nFromDisp = nm.from === '__user__' ? 'You' : (cachedAgents[nm.from] && cachedAgents[nm.from].display_name) || nm.from;
|
|
6169
|
+
var nToDisp = nm.to === '__user__' ? 'You' : (cachedAgents[nm.to] && cachedAgents[nm.to].display_name) || nm.to;
|
|
5037
6170
|
var nContent = renderMarkdown(nm.content);
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
'<
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
'
|
|
6171
|
+
if (nm.system === true) {
|
|
6172
|
+
var neType = 'status';
|
|
6173
|
+
var neIcon = '<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="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>';
|
|
6174
|
+
if (/\[CRITICAL\]/.test(nm.content)) { neType = 'critical'; neIcon = '<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 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'; }
|
|
6175
|
+
else if (/\[WARNING\]/.test(nm.content)) { neType = 'warning'; neIcon = '<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 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'; }
|
|
6176
|
+
else if (/\[VIOLATION\]/i.test(nm.content)) { neType = 'violation'; neIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>'; }
|
|
6177
|
+
else if (/\[EVENT\]/.test(nm.content)) { neType = 'event'; neIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>'; }
|
|
6178
|
+
else if (/\[REVIEW/.test(nm.content)) { neType = 'review'; neIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>'; }
|
|
6179
|
+
else if (/\[ROLE/.test(nm.content)) { neType = 'role'; neIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>'; }
|
|
6180
|
+
var neTime = new Date(nm.timestamp).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
|
|
6181
|
+
var neText = nm.content.replace(/^\[[\w\s]+\]\s*/, '');
|
|
6182
|
+
newMsgHtml += '<div class="message system-event-banner evt-' + neType + ' message-new" data-msg-id="' + escapeHtml(nm.id) + '">' +
|
|
6183
|
+
'<span class="event-icon">' + neIcon + '</span>' +
|
|
6184
|
+
'<div class="event-body"><span class="event-text">' + renderMarkdown(neText) + '</span></div>' +
|
|
6185
|
+
'<span class="event-time">' + neTime + '</span>' +
|
|
6186
|
+
'</div>';
|
|
6187
|
+
continue;
|
|
6188
|
+
}
|
|
6189
|
+
var nAvatar = getMsgAvatar(nm.from, nColor);
|
|
6190
|
+
newMsgHtml += '<div class="message nh-msg-card message-new" data-msg-id="' + escapeHtml(nm.id) + '">' +
|
|
6191
|
+
nAvatar +
|
|
6192
|
+
'<div class="msg-body">' +
|
|
6193
|
+
buildMsgCardHead(nFromDisp, nToDisp, nColor, nm.timestamp, '') +
|
|
5046
6194
|
'<div class="msg-content">' + nContent + '</div>' +
|
|
5047
6195
|
'</div></div>';
|
|
5048
6196
|
}
|
|
@@ -5052,8 +6200,10 @@ function renderMessages(messages) {
|
|
|
5052
6200
|
while (tempDiv.firstChild) el.appendChild(tempDiv.firstChild);
|
|
5053
6201
|
}
|
|
5054
6202
|
} else {
|
|
5055
|
-
// Full re-render
|
|
6203
|
+
// Full re-render — innerHTML clears the DOM; restore view via message anchor, not raw scrollTop
|
|
6204
|
+
var scrollAnchor = !autoScroll ? captureMessageScrollAnchor(el) : null;
|
|
5056
6205
|
el.innerHTML = loadMoreHtml + html;
|
|
6206
|
+
if (scrollAnchor) restoreMessageScrollAnchor(el, scrollAnchor);
|
|
5057
6207
|
}
|
|
5058
6208
|
|
|
5059
6209
|
lastRenderedIds = currentIds;
|
|
@@ -5153,18 +6303,55 @@ function renderChannelBar(messages) {
|
|
|
5153
6303
|
|
|
5154
6304
|
function toggleSidebar() {
|
|
5155
6305
|
var sb = document.getElementById('sidebar') || document.getElementById('nav-sidebar');
|
|
5156
|
-
if (sb)
|
|
6306
|
+
if (!sb) return;
|
|
6307
|
+
var isOpen = sb.classList.toggle('open');
|
|
6308
|
+
if (isOpen) {
|
|
6309
|
+
sb.style.display = 'flex';
|
|
6310
|
+
sb.style.width = 'var(--sidebar-w)';
|
|
6311
|
+
sb.style.minWidth = 'var(--sidebar-w)';
|
|
6312
|
+
sb.style.transform = 'translateX(0)';
|
|
6313
|
+
} else {
|
|
6314
|
+
sb.style.display = '';
|
|
6315
|
+
sb.style.width = '';
|
|
6316
|
+
sb.style.minWidth = '';
|
|
6317
|
+
sb.style.transform = '';
|
|
6318
|
+
}
|
|
5157
6319
|
var ov = document.getElementById('sidebar-overlay');
|
|
5158
6320
|
if (ov) ov.classList.toggle('open');
|
|
5159
6321
|
}
|
|
5160
6322
|
|
|
5161
6323
|
function closeSidebar() {
|
|
5162
6324
|
var sb = document.getElementById('sidebar') || document.getElementById('nav-sidebar');
|
|
5163
|
-
if (sb)
|
|
6325
|
+
if (sb) {
|
|
6326
|
+
sb.classList.remove('open');
|
|
6327
|
+
sb.style.display = '';
|
|
6328
|
+
sb.style.width = '';
|
|
6329
|
+
sb.style.minWidth = '';
|
|
6330
|
+
sb.style.transform = '';
|
|
6331
|
+
}
|
|
5164
6332
|
var ov = document.getElementById('sidebar-overlay');
|
|
5165
6333
|
if (ov) ov.classList.remove('open');
|
|
5166
6334
|
}
|
|
5167
6335
|
|
|
6336
|
+
function updateNewMsgPill(count) {
|
|
6337
|
+
count = Math.max(0, Number(count) || 0);
|
|
6338
|
+
|
|
6339
|
+
var scrollBtn = document.getElementById('scroll-bottom');
|
|
6340
|
+
var countEl = document.getElementById('new-msg-count');
|
|
6341
|
+
var labelEl = document.getElementById('new-msg-label');
|
|
6342
|
+
if (!scrollBtn || !countEl) return;
|
|
6343
|
+
|
|
6344
|
+
countEl.textContent = count;
|
|
6345
|
+
if (labelEl) labelEl.textContent = count === 1 ? 'new message' : 'new messages';
|
|
6346
|
+
|
|
6347
|
+
if (count > 0) {
|
|
6348
|
+
var label = count === 1 ? 'new message' : 'new messages';
|
|
6349
|
+
scrollBtn.setAttribute('aria-label', 'Jump to latest messages. ' + count + ' ' + label + '.');
|
|
6350
|
+
} else {
|
|
6351
|
+
scrollBtn.setAttribute('aria-label', 'Jump to latest messages.');
|
|
6352
|
+
}
|
|
6353
|
+
}
|
|
6354
|
+
|
|
5168
6355
|
var newWhileScrolled = 0;
|
|
5169
6356
|
|
|
5170
6357
|
document.getElementById('messages').addEventListener('scroll', function() {
|
|
@@ -5174,9 +6361,9 @@ document.getElementById('messages').addEventListener('scroll', function() {
|
|
|
5174
6361
|
if (atBottom) {
|
|
5175
6362
|
scrollBtn.classList.remove('visible');
|
|
5176
6363
|
newWhileScrolled = 0;
|
|
5177
|
-
|
|
6364
|
+
updateNewMsgPill(0);
|
|
5178
6365
|
} else {
|
|
5179
|
-
scrollBtn.classList.add('visible');
|
|
6366
|
+
if (newWhileScrolled > 0) scrollBtn.classList.add('visible');
|
|
5180
6367
|
}
|
|
5181
6368
|
});
|
|
5182
6369
|
|
|
@@ -5185,8 +6372,8 @@ function scrollToBottom() {
|
|
|
5185
6372
|
el.scrollTop = el.scrollHeight;
|
|
5186
6373
|
autoScroll = true;
|
|
5187
6374
|
newWhileScrolled = 0;
|
|
6375
|
+
updateNewMsgPill(0);
|
|
5188
6376
|
document.getElementById('scroll-bottom').classList.remove('visible');
|
|
5189
|
-
document.getElementById('new-msg-count').style.display = 'none';
|
|
5190
6377
|
}
|
|
5191
6378
|
|
|
5192
6379
|
// ==================== COMPACT MODE ====================
|
|
@@ -5418,10 +6605,12 @@ function renderPinnedMessages() {
|
|
|
5418
6605
|
var m = cachedHistory[i];
|
|
5419
6606
|
if (!pins[m.id]) continue;
|
|
5420
6607
|
var color = getColor(m.from);
|
|
5421
|
-
|
|
5422
|
-
|
|
6608
|
+
var pFrom = (cachedAgents[m.from] && cachedAgents[m.from].display_name) || m.from;
|
|
6609
|
+
var pTo = (cachedAgents[m.to] && cachedAgents[m.to].display_name) || m.to;
|
|
6610
|
+
html += '<div class="message nh-msg-card">' +
|
|
6611
|
+
'<div class="msg-avatar" style="background:' + color + ';width:24px;height:24px;font-size:9px">' + initial(m.from) + '</div>' +
|
|
5423
6612
|
'<div class="msg-body">' +
|
|
5424
|
-
|
|
6613
|
+
buildMsgCardHead(pFrom, pTo, color, m.timestamp, '') +
|
|
5425
6614
|
'<div class="msg-content">' + escapeHtml(m.content.substring(0, 120)) + (m.content.length > 120 ? '...' : '') + '</div>' +
|
|
5426
6615
|
'</div></div>';
|
|
5427
6616
|
}
|
|
@@ -5897,7 +7086,11 @@ function exportJSON() {
|
|
|
5897
7086
|
|
|
5898
7087
|
// ==================== VIEW SWITCHING ====================
|
|
5899
7088
|
|
|
5900
|
-
var activeView =
|
|
7089
|
+
var activeView = 'overview';
|
|
7090
|
+
try {
|
|
7091
|
+
var _savedView = localStorage.getItem('neohive_activeView');
|
|
7092
|
+
if (_savedView) activeView = _savedView;
|
|
7093
|
+
} catch (e) { /* Safari / private mode: storage can throw; keep default */ }
|
|
5901
7094
|
|
|
5902
7095
|
function switchView(view) {
|
|
5903
7096
|
activeView = view;
|
|
@@ -5905,6 +7098,7 @@ function switchView(view) {
|
|
|
5905
7098
|
// Reset render hashes so switching tabs forces a fresh render
|
|
5906
7099
|
_lastTasksHash = '';
|
|
5907
7100
|
_lastAgentsHash = '';
|
|
7101
|
+
if (view === 'messages') lastAgentKeys = '';
|
|
5908
7102
|
if (renderOverview._lastHash) renderOverview._lastHash = '';
|
|
5909
7103
|
// Update nav sidebar active state
|
|
5910
7104
|
var navItems = document.querySelectorAll('.nav-item');
|
|
@@ -5912,7 +7106,7 @@ function switchView(view) {
|
|
|
5912
7106
|
navItems[i].classList.toggle('active', navItems[i].getAttribute('data-view') === view);
|
|
5913
7107
|
}
|
|
5914
7108
|
// Update old tab active states (kept for compatibility)
|
|
5915
|
-
var tabIds = ['messages','tasks','workspaces','workflows','plan','launch','rules','stats','docs'];
|
|
7109
|
+
var tabIds = ['messages','tasks','workspaces','workflows','plan','launch','rules','audit-log','stats','docs'];
|
|
5916
7110
|
for (var t = 0; t < tabIds.length; t++) {
|
|
5917
7111
|
var el = document.getElementById('tab-' + tabIds[t]);
|
|
5918
7112
|
if (el) el.classList.toggle('active', view === tabIds[t]);
|
|
@@ -5927,6 +7121,10 @@ function switchView(view) {
|
|
|
5927
7121
|
document.getElementById('monitor-panel').style.display = view === 'plan' ? 'block' : 'none';
|
|
5928
7122
|
document.getElementById('launch-area').classList.toggle('visible', view === 'launch');
|
|
5929
7123
|
document.getElementById('rules-area').classList.toggle('visible', view === 'rules');
|
|
7124
|
+
if (document.getElementById('audit-log-area')) {
|
|
7125
|
+
document.getElementById('audit-log-area').classList.toggle('visible', view === 'audit-log');
|
|
7126
|
+
document.getElementById('audit-log-area').style.display = view === 'audit-log' ? 'block' : 'none';
|
|
7127
|
+
}
|
|
5930
7128
|
document.getElementById('stats-area').classList.toggle('visible', view === 'stats');
|
|
5931
7129
|
document.getElementById('docs-area').classList.toggle('visible', view === 'docs');
|
|
5932
7130
|
document.getElementById('search-bar').style.display = view === 'messages' ? 'flex' : 'none';
|
|
@@ -5942,6 +7140,7 @@ function switchView(view) {
|
|
|
5942
7140
|
if (view === 'docs') renderDocs();
|
|
5943
7141
|
if (view === 'launch') renderLaunchPanel();
|
|
5944
7142
|
if (view === 'rules') fetchRules();
|
|
7143
|
+
if (view === 'audit-log') fetchAuditLog();
|
|
5945
7144
|
if (view === 'stats') fetchStats();
|
|
5946
7145
|
if (isMobile) closeSidebar();
|
|
5947
7146
|
}
|
|
@@ -5999,7 +7198,7 @@ function renderTasks() {
|
|
|
5999
7198
|
if (tasksHash === _lastTasksHash) return;
|
|
6000
7199
|
_lastTasksHash = tasksHash;
|
|
6001
7200
|
|
|
6002
|
-
var groups = { pending: [], in_progress: [], in_review: [], done: [], blocked: [] };
|
|
7201
|
+
var groups = { pending: [], in_progress: [], in_review: [], done: [], blocked: [], blocked_permanent: [] };
|
|
6003
7202
|
for (var i = 0; i < cachedTasks.length; i++) {
|
|
6004
7203
|
var t = cachedTasks[i];
|
|
6005
7204
|
var status = t.status || 'pending';
|
|
@@ -6012,7 +7211,8 @@ function renderTasks() {
|
|
|
6012
7211
|
{ key: 'in_progress', label: 'In Progress' },
|
|
6013
7212
|
{ key: 'in_review', label: 'In Review' },
|
|
6014
7213
|
{ key: 'done', label: 'Done' },
|
|
6015
|
-
{ key: 'blocked', label: 'Blocked' }
|
|
7214
|
+
{ key: 'blocked', label: 'Blocked' },
|
|
7215
|
+
{ key: 'blocked_permanent', label: '⛔ Needs Intervention' }
|
|
6016
7216
|
];
|
|
6017
7217
|
|
|
6018
7218
|
var html = '<div class="kanban">';
|
|
@@ -6051,6 +7251,7 @@ function buildTaskCard(t) {
|
|
|
6051
7251
|
|
|
6052
7252
|
var badgesHtml = '';
|
|
6053
7253
|
if (t.escalated_at) badgesHtml += '<span style="font-size:9px;padding:1px 5px;border-radius:6px;font-weight:600;background:var(--red-dim);color:var(--red)">ESCALATED</span>';
|
|
7254
|
+
if (t.retry_count > 0) badgesHtml += '<span style="font-size:9px;padding:1px 5px;border-radius:6px;font-weight:600;background:var(--yellow)20;color:var(--yellow)" title="Reclaimed ' + t.retry_count + ' time(s) by watchdog">↺' + t.retry_count + '</span>';
|
|
6054
7255
|
if (t.channel) badgesHtml += '<span class="badge-channel" onclick="filterChannel(\'' + escapeHtml(t.channel) + '\')">#' + escapeHtml(t.channel) + '</span>';
|
|
6055
7256
|
|
|
6056
7257
|
var notesHtml = '';
|
|
@@ -6059,9 +7260,14 @@ function buildTaskCard(t) {
|
|
|
6059
7260
|
notesHtml = '<div style="font-size:10px;color:var(--text-muted);margin-top:4px;font-style:italic">' + escapeHtml(lastNote.by) + ': ' + escapeHtml((lastNote.text || '').substring(0, 80)) + '</div>';
|
|
6060
7261
|
}
|
|
6061
7262
|
|
|
7263
|
+
var blockedReasonHtml = (t.status === 'blocked_permanent' && t.blocked_reason)
|
|
7264
|
+
? '<div style="font-size:10px;color:var(--red);margin-top:4px;font-style:italic">' + escapeHtml(t.blocked_reason) + '</div>'
|
|
7265
|
+
: '';
|
|
7266
|
+
|
|
6062
7267
|
return '<div class="task-card" draggable="true" data-task-id="' + t.id + '" ondragstart="onTaskDragStart(event)" ondragend="onTaskDragEnd(event)" onclick="showTaskDetail(\'' + t.id + '\')">' +
|
|
6063
7268
|
'<div class="task-title">' + escapeHtml(t.title || 'Untitled') + (badgesHtml ? ' ' + badgesHtml : '') + '</div>' +
|
|
6064
7269
|
(t.description ? '<div class="task-desc">' + escapeHtml(t.description) + '</div>' : '') +
|
|
7270
|
+
blockedReasonHtml +
|
|
6065
7271
|
notesHtml +
|
|
6066
7272
|
'<div class="task-footer">' +
|
|
6067
7273
|
assigneeHtml +
|
|
@@ -6088,7 +7294,7 @@ function showTaskDetail(taskId) {
|
|
|
6088
7294
|
if (!task) return;
|
|
6089
7295
|
var el = document.getElementById('task-detail-content');
|
|
6090
7296
|
if (!el) return;
|
|
6091
|
-
var statusColors = { pending: 'var(--yellow)', in_progress: 'var(--accent)', in_review: 'var(--purple)', done: 'var(--green)', blocked: 'var(--red)' };
|
|
7297
|
+
var statusColors = { pending: 'var(--yellow)', in_progress: 'var(--accent)', in_review: 'var(--purple)', done: 'var(--green)', blocked: 'var(--red)', blocked_permanent: 'var(--red)' };
|
|
6092
7298
|
var statusColor = statusColors[task.status] || 'var(--text-muted)';
|
|
6093
7299
|
|
|
6094
7300
|
var html = '';
|
|
@@ -6271,7 +7477,7 @@ function onKanbanDrop(e) {
|
|
|
6271
7477
|
|
|
6272
7478
|
// ==================== STATS VIEW ====================
|
|
6273
7479
|
|
|
6274
|
-
var AGENT_COLORS = ['
|
|
7480
|
+
var AGENT_COLORS = ['var(--nh-accent)', 'var(--nh-orange)', 'var(--nh-green)', 'var(--nh-blue)', 'var(--nh-purple)', 'var(--nh-red)', 'var(--nh-amber)', 'var(--nh-yellow)'];
|
|
6275
7481
|
|
|
6276
7482
|
function fetchStats() {
|
|
6277
7483
|
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
@@ -6313,7 +7519,7 @@ function renderStats(data, tasks, workflows) {
|
|
|
6313
7519
|
var ts = tasks[ti].status;
|
|
6314
7520
|
if (ts === 'done') tasksDone++;
|
|
6315
7521
|
else if (ts === 'in_progress' || ts === 'in_review') tasksActive++;
|
|
6316
|
-
else if (ts === 'blocked') tasksBlocked++;
|
|
7522
|
+
else if (ts === 'blocked' || ts === 'blocked_permanent') tasksBlocked++;
|
|
6317
7523
|
else tasksPending++;
|
|
6318
7524
|
}
|
|
6319
7525
|
|
|
@@ -6464,6 +7670,7 @@ function renderInsights(decisions, history) {
|
|
|
6464
7670
|
// ==================== RULES TAB ====================
|
|
6465
7671
|
|
|
6466
7672
|
var cachedRules = [];
|
|
7673
|
+
var cachedAuditLog = [];
|
|
6467
7674
|
|
|
6468
7675
|
function fetchRules() {
|
|
6469
7676
|
var pq = projectParam();
|
|
@@ -6473,47 +7680,101 @@ function fetchRules() {
|
|
|
6473
7680
|
}).catch(function() { cachedRules = []; renderRules(); });
|
|
6474
7681
|
}
|
|
6475
7682
|
|
|
7683
|
+
function fetchAuditLog() {
|
|
7684
|
+
var pq = projectParam();
|
|
7685
|
+
lttFetch('/api/audit-log' + pq).then(function(r) { return r.json(); }).then(function(log) {
|
|
7686
|
+
cachedAuditLog = Array.isArray(log) ? log : [];
|
|
7687
|
+
renderAuditLog();
|
|
7688
|
+
}).catch(function() { cachedAuditLog = []; renderAuditLog(); });
|
|
7689
|
+
}
|
|
7690
|
+
|
|
6476
7691
|
function renderRules() {
|
|
6477
7692
|
var el = document.getElementById('rules-area');
|
|
6478
|
-
var html = '<h3 style="font-size:16px;font-weight:700
|
|
6479
|
-
|
|
6480
|
-
//
|
|
6481
|
-
html += '<div class="rule-add-form">' +
|
|
6482
|
-
'<
|
|
6483
|
-
'<div
|
|
6484
|
-
'<
|
|
7693
|
+
var html = '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px"><h3 style="font-size:16px;font-weight:700">Governance Policies</h3></div>';
|
|
7694
|
+
|
|
7695
|
+
// No-code Rule Builder Form
|
|
7696
|
+
html += '<div class="rule-add-form" style="background:var(--nh-glass-bg);backdrop-filter:blur(var(--nh-blur));border:1px solid var(--nh-glass-border);padding:16px;border-radius:12px;margin-bottom:20px">' +
|
|
7697
|
+
'<h4 style="font-size:13px;font-weight:700;color:var(--text);margin-bottom:12px">CREATE NEW POLICY</h4>' +
|
|
7698
|
+
'<div style="display:flex;gap:12px;margin-bottom:12px">' +
|
|
7699
|
+
'<div style="flex:1">' +
|
|
7700
|
+
'<label style="font-size:10px;color:var(--text-muted);display:block;margin-bottom:4px;font-weight:600;letter-spacing:0.5px">WHEN (TRIGGER)</label>' +
|
|
7701
|
+
'<select id="builder-trigger" style="width:100%;background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:8px;border-radius:6px">' +
|
|
7702
|
+
'<option value="Anytime">Anytime</option>' +
|
|
7703
|
+
'<option value="Before executing a workflow step">Before workflow step</option>' +
|
|
7704
|
+
'<option value="After executing a workflow step">After workflow step</option>' +
|
|
7705
|
+
'<option value="When writing a file">When writing a file</option>' +
|
|
7706
|
+
'<option value="When sending a message">When sending a message</option>' +
|
|
7707
|
+
'</select>' +
|
|
7708
|
+
'</div>' +
|
|
7709
|
+
'<div style="flex:2">' +
|
|
7710
|
+
'<label style="font-size:10px;color:var(--text-muted);display:block;margin-bottom:4px;font-weight:600;letter-spacing:0.5px">IF (CONDITION)</label>' +
|
|
7711
|
+
'<input id="builder-condition" type="text" placeholder="e.g. the file ends with .js (optional)" style="width:100%;background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:8px;border-radius:6px">' +
|
|
7712
|
+
'</div>' +
|
|
7713
|
+
'</div>' +
|
|
7714
|
+
'<div style="margin-bottom:16px">' +
|
|
7715
|
+
'<label style="font-size:10px;color:var(--text-muted);display:block;margin-bottom:4px;font-weight:600;letter-spacing:0.5px">THEN (ACTION)</label>' +
|
|
7716
|
+
'<textarea id="builder-action" placeholder="What must the agent do? e.g. Ensure strict type checking is added." style="width:100%;min-height:50px;background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:8px;border-radius:6px"></textarea>' +
|
|
7717
|
+
'</div>' +
|
|
7718
|
+
'<div style="display:flex;gap:12px;margin-bottom:16px;background:var(--surface-3);padding:10px;border-radius:8px;border:1px dashed var(--border)">' +
|
|
7719
|
+
'<div style="flex:1">' +
|
|
7720
|
+
'<label style="font-size:10px;color:var(--text-muted);display:block;margin-bottom:4px;font-weight:600;letter-spacing:0.5px">SCOPE: ROLE</label>' +
|
|
7721
|
+
'<input id="builder-scope-role" type="text" placeholder="e.g. quality" style="width:100%;background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:6px;border-radius:6px;font-size:12px">' +
|
|
7722
|
+
'</div>' +
|
|
7723
|
+
'<div style="flex:1">' +
|
|
7724
|
+
'<label style="font-size:10px;color:var(--text-muted);display:block;margin-bottom:4px;font-weight:600;letter-spacing:0.5px">SCOPE: PROVIDER</label>' +
|
|
7725
|
+
'<input id="builder-scope-provider" type="text" placeholder="e.g. claude" style="width:100%;background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:6px;border-radius:6px;font-size:12px">' +
|
|
7726
|
+
'</div>' +
|
|
7727
|
+
'<div style="flex:1">' +
|
|
7728
|
+
'<label style="font-size:10px;color:var(--text-muted);display:block;margin-bottom:4px;font-weight:600;letter-spacing:0.5px">SCOPE: AGENT</label>' +
|
|
7729
|
+
'<input id="builder-scope-agent" type="text" placeholder="e.g. Victor" style="width:100%;background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:6px;border-radius:6px;font-size:12px">' +
|
|
7730
|
+
'</div>' +
|
|
7731
|
+
'</div>' +
|
|
7732
|
+
'<div class="rule-form-row" style="display:flex;gap:10px;align-items:center">' +
|
|
7733
|
+
'<select id="new-rule-category" style="background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:6px;border-radius:6px">' +
|
|
6485
7734
|
'<option value="general">General</option>' +
|
|
6486
7735
|
'<option value="safety">Safety</option>' +
|
|
6487
7736
|
'<option value="workflow">Workflow</option>' +
|
|
6488
7737
|
'<option value="coding">Coding Standards</option>' +
|
|
6489
7738
|
'<option value="forbidden">Forbidden Actions</option>' +
|
|
6490
7739
|
'</select>' +
|
|
6491
|
-
'<select id="new-rule-priority">' +
|
|
7740
|
+
'<select id="new-rule-priority" style="background:var(--surface-2);color:var(--text);border:1px solid var(--border);padding:6px;border-radius:6px">' +
|
|
6492
7741
|
'<option value="normal">Normal</option>' +
|
|
6493
7742
|
'<option value="important">Important</option>' +
|
|
6494
7743
|
'<option value="critical">Critical</option>' +
|
|
6495
7744
|
'</select>' +
|
|
6496
|
-
'<button class="btn btn-primary" onclick="addRule()" style="margin-left:auto">
|
|
7745
|
+
'<button class="btn btn-primary" onclick="addRule()" style="margin-left:auto;background:var(--gradient-accent);color:#fff;border:none;padding:6px 16px;border-radius:6px;cursor:pointer;font-weight:600">Save Policy</button>' +
|
|
6497
7746
|
'</div>' +
|
|
6498
7747
|
'</div>';
|
|
6499
7748
|
|
|
6500
7749
|
if (!cachedRules.length) {
|
|
6501
|
-
html += '<div style="color:var(--text-muted);font-size:13px;text-align:center;padding:40px 0">No
|
|
7750
|
+
html += '<div style="color:var(--text-muted);font-size:13px;text-align:center;padding:40px 0;background:var(--nh-glass-bg);backdrop-filter:blur(var(--nh-blur));border:1px solid var(--nh-glass-border);border-radius:12px">No policies defined yet.</div>';
|
|
6502
7751
|
} else {
|
|
6503
7752
|
for (var i = 0; i < cachedRules.length; i++) {
|
|
6504
7753
|
var r = cachedRules[i];
|
|
6505
7754
|
var cardClass = 'rule-card' + (r.active === false ? ' inactive' : '') + (r.priority === 'critical' ? ' critical' : r.priority === 'important' ? ' important' : '');
|
|
6506
|
-
|
|
7755
|
+
var scopeObj = {};
|
|
7756
|
+
if (r.scope_role) scopeObj.Role = r.scope_role;
|
|
7757
|
+
if (r.scope_provider) scopeObj.Provider = r.scope_provider;
|
|
7758
|
+
if (r.scope_agent) scopeObj.Agent = r.scope_agent;
|
|
7759
|
+
var scopeKeys = Object.keys(scopeObj);
|
|
7760
|
+
var scopeStr = scopeKeys.length > 0
|
|
7761
|
+
? scopeKeys.map(function(k) { return k + ':' + scopeObj[k]; }).join(', ')
|
|
7762
|
+
: escapeHtml(r.scope || 'global');
|
|
7763
|
+
var hasScope = scopeKeys.length > 0 || (r.scope && r.scope !== 'global');
|
|
7764
|
+
var scopeBadge = hasScope ? '<span style="font-size:9px;padding:2px 6px;border-radius:4px;background:var(--surface-3);color:var(--text-dim);border:1px solid var(--border);margin-right:6px;text-transform:uppercase" title="Scope">' + escapeHtml(scopeStr) + '</span>' : '';
|
|
7765
|
+
|
|
7766
|
+
html += '<div class="' + cardClass + '" style="background:var(--nh-glass-bg);backdrop-filter:blur(var(--nh-blur));border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:12px;display:flex;align-items:center">' +
|
|
6507
7767
|
'<div style="flex:1">' +
|
|
6508
|
-
'<div class="rule-text">' + escapeHtml(r.text) + '</div>' +
|
|
7768
|
+
'<div class="rule-text" style="font-size:14px;color:var(--text);margin-bottom:6px">' + escapeHtml(r.text) + '</div>' +
|
|
6509
7769
|
'<div style="display:flex;gap:6px;align-items:center;margin-top:4px">' +
|
|
6510
|
-
|
|
7770
|
+
scopeBadge +
|
|
7771
|
+
'<span class="rule-category" style="font-size:10px;padding:2px 6px;border-radius:4px;background:var(--purple-dim);color:var(--purple)">' + escapeHtml(r.category || 'general') + '</span>' +
|
|
6511
7772
|
'<span style="font-size:9px;color:var(--text-muted)">by ' + escapeHtml(r.created_by || '?') + ' · ' + timeAgo(r.created_at) + '</span>' +
|
|
6512
7773
|
'</div>' +
|
|
6513
7774
|
'</div>' +
|
|
6514
|
-
'<div class="rule-actions">' +
|
|
6515
|
-
'<button onclick="toggleRule(\'' + r.id + '\',' + (r.active === false ? 'true' : 'false') + ')">' + (r.active === false ? 'Enable' : 'Disable') + '</button>' +
|
|
6516
|
-
'<button onclick="deleteRule(\'' + r.id + '\')"
|
|
7775
|
+
'<div class="rule-actions" style="display:flex;gap:8px;align-items:center">' +
|
|
7776
|
+
'<button class="btn" style="padding:4px 8px;font-size:11px;background:var(--surface-2);color:var(--text);border:1px solid var(--border);border-radius:4px;cursor:pointer" onclick="toggleRule(\'' + r.id + '\',' + (r.active === false ? 'true' : 'false') + ')">' + (r.active === false ? 'Enable' : 'Disable') + '</button>' +
|
|
7777
|
+
'<button class="btn" style="padding:4px 8px;font-size:11px;color:var(--red,#ef4444);background:transparent;border:none;cursor:pointer" onclick="deleteRule(\'' + r.id + '\')">Delete</button>' +
|
|
6517
7778
|
'</div>' +
|
|
6518
7779
|
'</div>';
|
|
6519
7780
|
}
|
|
@@ -6522,19 +7783,74 @@ function renderRules() {
|
|
|
6522
7783
|
el.innerHTML = html;
|
|
6523
7784
|
}
|
|
6524
7785
|
|
|
7786
|
+
function renderAuditLog() {
|
|
7787
|
+
var el = document.getElementById('audit-log-area');
|
|
7788
|
+
if (!el) return;
|
|
7789
|
+
var html = '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px"><h3 style="font-size:16px;font-weight:700">Governance Audit Log</h3></div>';
|
|
7790
|
+
|
|
7791
|
+
if (!cachedAuditLog.length) {
|
|
7792
|
+
html += '<div style="color:var(--text-muted);font-size:13px;text-align:center;padding:40px 0;background:var(--nh-glass-bg);backdrop-filter:blur(var(--nh-blur));border:1px solid var(--nh-glass-border);border-radius:12px">No enforcement events recorded yet.</div>';
|
|
7793
|
+
} else {
|
|
7794
|
+
html += '<div style="background:var(--nh-glass-bg);backdrop-filter:blur(var(--nh-blur));border:1px solid var(--border);border-radius:12px;overflow:hidden">';
|
|
7795
|
+
for (var i = 0; i < cachedAuditLog.length; i++) {
|
|
7796
|
+
var log = cachedAuditLog[i];
|
|
7797
|
+
var isViolation = log.type === 'violation' || log.event === 'violation' || log.status === 'violation';
|
|
7798
|
+
var icon = isViolation ? '<span style="color:var(--red)">⚠</span>' : '<span style="color:var(--green)">✓</span>';
|
|
7799
|
+
var borderLeft = isViolation ? 'border-left:3px solid var(--red)' : 'border-left:3px solid transparent';
|
|
7800
|
+
|
|
7801
|
+
html += '<div style="padding:12px 16px;border-bottom:1px solid var(--border);display:flex;gap:12px;align-items:flex-start;' + borderLeft + '">' +
|
|
7802
|
+
'<div style="font-size:16px;margin-top:2px">' + icon + '</div>' +
|
|
7803
|
+
'<div style="flex:1">' +
|
|
7804
|
+
'<div style="font-size:13px;color:var(--text);margin-bottom:4px"><strong>' + escapeHtml(log.agent || 'System') + '</strong>: ' + escapeHtml(log.description || log.message || '') + '</div>' +
|
|
7805
|
+
'<div style="font-size:11px;color:var(--text-muted)">' + timeAgo(log.timestamp) + (log.rule_id ? ' · Rule ' + escapeHtml(log.rule_id) : '') + '</div>' +
|
|
7806
|
+
'</div>' +
|
|
7807
|
+
'</div>';
|
|
7808
|
+
}
|
|
7809
|
+
html += '</div>';
|
|
7810
|
+
}
|
|
7811
|
+
el.innerHTML = html;
|
|
7812
|
+
}
|
|
7813
|
+
|
|
6525
7814
|
function addRule() {
|
|
6526
|
-
var
|
|
6527
|
-
|
|
7815
|
+
var trigger = document.getElementById('builder-trigger').value;
|
|
7816
|
+
var condition = document.getElementById('builder-condition').value.trim();
|
|
7817
|
+
var action = document.getElementById('builder-action').value.trim();
|
|
7818
|
+
|
|
7819
|
+
if (!action) {
|
|
7820
|
+
alert("Action field is required to create a rule.");
|
|
7821
|
+
return;
|
|
7822
|
+
}
|
|
7823
|
+
|
|
7824
|
+
var text = "";
|
|
7825
|
+
if (trigger === "Anytime") {
|
|
7826
|
+
text = condition ? "If " + condition + ", then " + action : action;
|
|
7827
|
+
} else {
|
|
7828
|
+
text = trigger + (condition ? " and " + condition : "") + ", then " + action;
|
|
7829
|
+
}
|
|
7830
|
+
|
|
7831
|
+
var sRole = document.getElementById('builder-scope-role').value.trim();
|
|
7832
|
+
var sProv = document.getElementById('builder-scope-provider').value.trim();
|
|
7833
|
+
var sAgent = document.getElementById('builder-scope-agent').value.trim();
|
|
7834
|
+
var scopeObj = {};
|
|
7835
|
+
if (sRole) scopeObj.role = sRole;
|
|
7836
|
+
if (sProv) scopeObj.provider = sProv;
|
|
7837
|
+
if (sAgent) scopeObj.agent = sAgent;
|
|
7838
|
+
var scope = Object.keys(scopeObj).length > 0 ? scopeObj : 'global';
|
|
7839
|
+
|
|
6528
7840
|
var category = document.getElementById('new-rule-category').value;
|
|
6529
7841
|
var priority = document.getElementById('new-rule-priority').value;
|
|
6530
7842
|
var pq = projectParam();
|
|
6531
7843
|
lttFetch('/api/rules' + pq, {
|
|
6532
7844
|
method: 'POST',
|
|
6533
7845
|
headers: { 'Content-Type': 'application/json' },
|
|
6534
|
-
body: JSON.stringify({ action: 'add', text: text, category: category, priority: priority, created_by: 'Dashboard' })
|
|
7846
|
+
body: JSON.stringify({ action: 'add', text: text, category: category, priority: priority, scope: scope, created_by: 'Dashboard' })
|
|
6535
7847
|
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
6536
7848
|
if (res.success) {
|
|
6537
|
-
document.getElementById('
|
|
7849
|
+
document.getElementById('builder-condition').value = '';
|
|
7850
|
+
document.getElementById('builder-action').value = '';
|
|
7851
|
+
document.getElementById('builder-scope-role').value = '';
|
|
7852
|
+
document.getElementById('builder-scope-provider').value = '';
|
|
7853
|
+
document.getElementById('builder-scope-agent').value = '';
|
|
6538
7854
|
fetchRules();
|
|
6539
7855
|
}
|
|
6540
7856
|
}).catch(function(e) { console.error('Add rule failed:', e); });
|
|
@@ -6603,6 +7919,181 @@ function getMsgAvatar(name, color) {
|
|
|
6603
7919
|
|
|
6604
7920
|
// ==================== v3.0: PROFILE POPUP ====================
|
|
6605
7921
|
|
|
7922
|
+
function switchPpTab(tabName, forceInit) {
|
|
7923
|
+
var popup = document.getElementById('profile-popup');
|
|
7924
|
+
var agentName = popup.dataset.agent || editingAgent;
|
|
7925
|
+
|
|
7926
|
+
var tabs = document.querySelectorAll('.pp-tab');
|
|
7927
|
+
var contents = document.querySelectorAll('.pp-tab-content');
|
|
7928
|
+
|
|
7929
|
+
tabs.forEach(function(t) { t.classList.toggle('active', t.dataset.tab === tabName); });
|
|
7930
|
+
contents.forEach(function(c) { c.classList.toggle('active', c.id === 'pp-tab-' + tabName); });
|
|
7931
|
+
|
|
7932
|
+
activePpTab = tabName;
|
|
7933
|
+
if (tabName === 'terminal' || forceInit) {
|
|
7934
|
+
if (agentName) initTerminal(agentName);
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7937
|
+
|
|
7938
|
+
var _xtermLoadPromise = null;
|
|
7939
|
+
function loadXterm() {
|
|
7940
|
+
if (_xtermLoadPromise) return _xtermLoadPromise;
|
|
7941
|
+
_xtermLoadPromise = new Promise(function(resolve, reject) {
|
|
7942
|
+
console.log('[Neohive] Lazy-loading xterm.js dependencies...');
|
|
7943
|
+
var css = document.createElement('link');
|
|
7944
|
+
css.rel = 'stylesheet';
|
|
7945
|
+
css.href = 'https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css';
|
|
7946
|
+
document.head.appendChild(css);
|
|
7947
|
+
|
|
7948
|
+
var s1 = document.createElement('script');
|
|
7949
|
+
s1.src = 'https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js';
|
|
7950
|
+
s1.onload = function() {
|
|
7951
|
+
var s2 = document.createElement('script');
|
|
7952
|
+
s2.src = 'https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js';
|
|
7953
|
+
s2.onload = function() {
|
|
7954
|
+
console.log('[Neohive] xterm.js loaded successfully.');
|
|
7955
|
+
resolve();
|
|
7956
|
+
};
|
|
7957
|
+
s2.onerror = reject;
|
|
7958
|
+
document.head.appendChild(s2);
|
|
7959
|
+
};
|
|
7960
|
+
s1.onerror = reject;
|
|
7961
|
+
document.head.appendChild(s1);
|
|
7962
|
+
});
|
|
7963
|
+
return _xtermLoadPromise;
|
|
7964
|
+
}
|
|
7965
|
+
|
|
7966
|
+
var terminalInstances = new Map(); // agentName -> { term: Terminal, fit: FitAddon, lastMsgId: string }
|
|
7967
|
+
|
|
7968
|
+
async function initTerminal(agentName) {
|
|
7969
|
+
var container = document.getElementById('pp-terminal-container');
|
|
7970
|
+
if (!container || !agentName) return;
|
|
7971
|
+
|
|
7972
|
+
// Clear previous terminal output
|
|
7973
|
+
container.innerHTML = '<div style="padding:20px; color:var(--nh-text-muted); font-family:monospace;">Connecting terminal stream...</div>';
|
|
7974
|
+
|
|
7975
|
+
try {
|
|
7976
|
+
await loadXterm();
|
|
7977
|
+
} catch (err) {
|
|
7978
|
+
console.error('[Neohive] Failed to load xterm.js:', err);
|
|
7979
|
+
container.innerHTML = '<div style="padding:20px; color:var(--nh-red); font-family:monospace;">Terminal unavailable — check network connection.</div>';
|
|
7980
|
+
return;
|
|
7981
|
+
}
|
|
7982
|
+
|
|
7983
|
+
container.innerHTML = '';
|
|
7984
|
+
|
|
7985
|
+
if (terminalInstances.has(agentName)) {
|
|
7986
|
+
var inst = terminalInstances.get(agentName);
|
|
7987
|
+
inst.term.open(container);
|
|
7988
|
+
if (inst.fit) {
|
|
7989
|
+
setTimeout(function() { inst.fit.fit(); }, 50);
|
|
7990
|
+
}
|
|
7991
|
+
return inst.term;
|
|
7992
|
+
}
|
|
7993
|
+
|
|
7994
|
+
var term = new Terminal({
|
|
7995
|
+
cursorBlink: true,
|
|
7996
|
+
theme: { background: '#111827', cursor: '#fbbf24' },
|
|
7997
|
+
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
7998
|
+
fontSize: 12
|
|
7999
|
+
});
|
|
8000
|
+
var fit = new FitAddon.FitAddon();
|
|
8001
|
+
term.loadAddon(fit);
|
|
8002
|
+
|
|
8003
|
+
// Ensure DOM is ready
|
|
8004
|
+
setTimeout(function() {
|
|
8005
|
+
term.open(container);
|
|
8006
|
+
fit.fit();
|
|
8007
|
+
terminalInstances.set(agentName, { term: term, fit: fit, lastMsgId: null });
|
|
8008
|
+
|
|
8009
|
+
term.writeln('\x1b[33m[Neohive]\x1b[0m Connecting terminal stream for \x1b[32m' + agentName + '\x1b[0m...');
|
|
8010
|
+
|
|
8011
|
+
// Real Stream: sync current history immediately
|
|
8012
|
+
syncTerminalsFromHistory(agentName, term);
|
|
8013
|
+
}, 50);
|
|
8014
|
+
|
|
8015
|
+
return term;
|
|
8016
|
+
}
|
|
8017
|
+
|
|
8018
|
+
function syncTerminalsFromHistory(targetAgent, specificTerm) {
|
|
8019
|
+
// If targetAgent provided, sync only that one, else sync all open instances
|
|
8020
|
+
var targets = targetAgent ? [targetAgent] : Array.from(terminalInstances.keys());
|
|
8021
|
+
|
|
8022
|
+
targets.forEach(function(agent) {
|
|
8023
|
+
var inst = terminalInstances.get(agent);
|
|
8024
|
+
if (!inst) return;
|
|
8025
|
+
var term = specificTerm || inst.term;
|
|
8026
|
+
|
|
8027
|
+
// Find messages from this agent starting with [terminal:
|
|
8028
|
+
// cachedHistory is global, oldest to newest
|
|
8029
|
+
cachedHistory.forEach(function(msg) {
|
|
8030
|
+
if (msg.from === agent && msg.content && msg.content.startsWith('[terminal:')) {
|
|
8031
|
+
// Simple deduplication based on unique message ID
|
|
8032
|
+
if (inst.lastMsgId && msg.id <= inst.lastMsgId) return;
|
|
8033
|
+
|
|
8034
|
+
// Extract content: "[terminal: some command] output..." -> "output..."
|
|
8035
|
+
var clean = msg.content.replace(/^\[terminal:.*?\]/, '');
|
|
8036
|
+
if (clean) {
|
|
8037
|
+
// If the clean content is empty, it might be just the command tag
|
|
8038
|
+
// We can echo the command tag if we want, or just the output
|
|
8039
|
+
term.writeln(clean);
|
|
8040
|
+
}
|
|
8041
|
+
inst.lastMsgId = msg.id;
|
|
8042
|
+
}
|
|
8043
|
+
});
|
|
8044
|
+
});
|
|
8045
|
+
}
|
|
8046
|
+
|
|
8047
|
+
function ppPing(agentName) {
|
|
8048
|
+
var pq = projectParam();
|
|
8049
|
+
lttFetch('/api/inject' + pq, {
|
|
8050
|
+
method: 'POST',
|
|
8051
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8052
|
+
body: JSON.stringify({ from: '__user__', to: agentName, content: 'ping' })
|
|
8053
|
+
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
8054
|
+
if (res.success) {
|
|
8055
|
+
var btn = document.querySelector('#pp-actions .pp-ping-btn');
|
|
8056
|
+
if (btn) { btn.style.borderColor = 'var(--nh-green)'; setTimeout(function() { btn.style.borderColor = ''; }, 1000); }
|
|
8057
|
+
}
|
|
8058
|
+
}).catch(function(e) { log.error('Ping failed:', e); });
|
|
8059
|
+
}
|
|
8060
|
+
|
|
8061
|
+
function ppMessage(agentName) {
|
|
8062
|
+
var sel = document.getElementById('inject-target');
|
|
8063
|
+
if (sel) sel.value = agentName;
|
|
8064
|
+
var ta = document.getElementById('inject-content');
|
|
8065
|
+
if (ta) { ta.value = ''; ta.focus(); }
|
|
8066
|
+
updateSendBtn();
|
|
8067
|
+
document.getElementById('profile-popup').classList.remove('open');
|
|
8068
|
+
}
|
|
8069
|
+
|
|
8070
|
+
function ppNudge(agentName) {
|
|
8071
|
+
var sel = document.getElementById('inject-target');
|
|
8072
|
+
if (sel) sel.value = agentName;
|
|
8073
|
+
var ta = document.getElementById('inject-content');
|
|
8074
|
+
if (ta) { ta.value = '@' + agentName + ' '; ta.focus(); }
|
|
8075
|
+
updateSendBtn();
|
|
8076
|
+
document.getElementById('profile-popup').classList.remove('open');
|
|
8077
|
+
}
|
|
8078
|
+
|
|
8079
|
+
function ppFilterChat(agentName) {
|
|
8080
|
+
searchQuery = agentName.toLowerCase();
|
|
8081
|
+
var input = document.getElementById('search-input');
|
|
8082
|
+
if (input) input.value = agentName;
|
|
8083
|
+
lastMessageCount = 0;
|
|
8084
|
+
renderMessages(cachedHistory);
|
|
8085
|
+
document.getElementById('profile-popup').classList.remove('open');
|
|
8086
|
+
}
|
|
8087
|
+
|
|
8088
|
+
function ppViewHistory(agentName) {
|
|
8089
|
+
searchQuery = agentName.toLowerCase();
|
|
8090
|
+
var input = document.getElementById('search-input');
|
|
8091
|
+
if (input) input.value = agentName;
|
|
8092
|
+
lastMessageCount = 0;
|
|
8093
|
+
renderMessages(cachedHistory);
|
|
8094
|
+
document.getElementById('profile-popup').classList.remove('open');
|
|
8095
|
+
}
|
|
8096
|
+
|
|
6606
8097
|
function showProfilePopup(agentName, event) {
|
|
6607
8098
|
event.stopPropagation();
|
|
6608
8099
|
var popup = document.getElementById('profile-popup');
|
|
@@ -6610,37 +8101,206 @@ function showProfilePopup(agentName, event) {
|
|
|
6610
8101
|
if (!agent) return;
|
|
6611
8102
|
|
|
6612
8103
|
editingAgent = agentName;
|
|
8104
|
+
popup.dataset.agent = agentName;
|
|
6613
8105
|
var color = getColor(agentName);
|
|
8106
|
+
|
|
8107
|
+
// Header: avatar, name, status badge
|
|
6614
8108
|
var ppAvatar = document.getElementById('pp-avatar');
|
|
6615
8109
|
if (agent.avatar && (agent.avatar.startsWith('data:image/') || agent.avatar.startsWith('/'))) { ppAvatar.setAttribute('src', agent.avatar); ppAvatar.style.display = ''; } else { ppAvatar.style.display = 'none'; }
|
|
6616
8110
|
document.getElementById('pp-name').textContent = agent.display_name || agentName;
|
|
6617
8111
|
document.getElementById('pp-name').style.color = color;
|
|
6618
8112
|
document.getElementById('pp-id').textContent = '@' + agentName;
|
|
6619
|
-
document.getElementById('pp-role').innerHTML = agent.role ? '<span class="role-badge">' + escapeHtml(agent.role) + '</span>' : '';
|
|
6620
|
-
document.getElementById('pp-bio').textContent = agent.bio || '';
|
|
6621
8113
|
|
|
6622
|
-
//
|
|
6623
|
-
var
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
8114
|
+
// Status badge
|
|
8115
|
+
var state = agent.status || (!agent.alive ? 'offline' : agent.is_listening ? 'listening' : 'working');
|
|
8116
|
+
var badgeClass = (state === 'offline' || state === 'dead') ? 'offline' : state === 'listening' ? 'listening' : (state === 'stuck') ? 'stuck' : (state === 'unresponsive') ? 'unresponsive' : 'online';
|
|
8117
|
+
var statusLabel = state === 'listening' ? 'Listening' : (state === 'offline' || state === 'dead') ? 'Offline' : state === 'stuck' ? 'Stuck' : state === 'unresponsive' ? 'Unresponsive' : state === 'idle' ? 'Idle' : 'Online';
|
|
8118
|
+
document.getElementById('pp-status-badge').className = 'pp-status-badge ' + badgeClass;
|
|
8119
|
+
document.getElementById('pp-status-badge').textContent = statusLabel;
|
|
8120
|
+
|
|
8121
|
+
// Default to Actions tab when opening agent popup
|
|
8122
|
+
switchPpTab('actions', false);
|
|
8123
|
+
|
|
8124
|
+
// === Stats Tab ===
|
|
8125
|
+
var badgesHtml = '';
|
|
8126
|
+
if (agent.provider) badgesHtml += '<span class="role-badge" style="font-size:10px">' + escapeHtml(agent.provider) + '</span>';
|
|
8127
|
+
if (agent.role) badgesHtml += '<span class="role-badge" style="font-size:10px">' + escapeHtml(agent.role) + '</span>';
|
|
8128
|
+
document.getElementById('pp-badges').innerHTML = badgesHtml;
|
|
8129
|
+
|
|
8130
|
+
// Count tasks completed and messages for this agent
|
|
8131
|
+
var tasksCompleted = 0;
|
|
8132
|
+
var msgCount = 0;
|
|
8133
|
+
if (cachedTasks) {
|
|
8134
|
+
for (var t = 0; t < cachedTasks.length; t++) {
|
|
8135
|
+
if (cachedTasks[t].assignee === agentName && cachedTasks[t].status === 'done') tasksCompleted++;
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
if (cachedHistory) {
|
|
8139
|
+
for (var m = 0; m < cachedHistory.length; m++) {
|
|
8140
|
+
if (cachedHistory[m].from === agentName) msgCount++;
|
|
8141
|
+
}
|
|
8142
|
+
}
|
|
8143
|
+
|
|
8144
|
+
var statsHtml = '<div class="profile-popup-stat"><span>' + tasksCompleted + '</span>Tasks Done</div>' +
|
|
8145
|
+
'<div class="profile-popup-stat"><span>' + msgCount + '</span>Messages</div>';
|
|
8146
|
+
document.getElementById('pp-stats').innerHTML = statsHtml;
|
|
8147
|
+
|
|
8148
|
+
// Skills (read-only display)
|
|
8149
|
+
var skills = agent.skills || [];
|
|
8150
|
+
var skillsRoHtml = '';
|
|
8151
|
+
if (skills.length > 0) {
|
|
8152
|
+
var platformClass = 'platform-' + (agent.provider || 'unknown').toLowerCase();
|
|
8153
|
+
var platformSkills = agent.platform_skills || [];
|
|
8154
|
+
skillsRoHtml = '<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.5px;color:var(--text-muted);margin-bottom:6px">Skills</div><div class="skill-tags-inline">';
|
|
8155
|
+
for (var s = 0; s < skills.length; s++) {
|
|
8156
|
+
var isPlatform = platformSkills.indexOf(skills[s]) !== -1;
|
|
8157
|
+
skillsRoHtml += '<span class="skill-tag' + (isPlatform ? ' ' + platformClass : '') + '">' + escapeHtml(skills[s]) + '</span>';
|
|
8158
|
+
}
|
|
8159
|
+
skillsRoHtml += '</div>';
|
|
8160
|
+
}
|
|
8161
|
+
document.getElementById('pp-skills-readonly').innerHTML = skillsRoHtml;
|
|
8162
|
+
|
|
8163
|
+
// === Actions Tab ===
|
|
8164
|
+
var eName = escapeHtml(agentName);
|
|
8165
|
+
var actionsHtml = '<button class="pp-action-btn" onclick="ppMessage(\'' + eName + '\')">' +
|
|
8166
|
+
'<svg viewBox="0 0 24 24"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>' +
|
|
8167
|
+
'Message</button>' +
|
|
8168
|
+
'<button class="pp-action-btn pp-ping-btn" onclick="ppPing(\'' + eName + '\')">' +
|
|
8169
|
+
'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>' +
|
|
8170
|
+
'Ping</button>' +
|
|
8171
|
+
'<button class="pp-action-btn" onclick="ppNudge(\'' + eName + '\')">' +
|
|
8172
|
+
'<svg viewBox="0 0 24 24"><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>' +
|
|
8173
|
+
'Nudge</button>' +
|
|
8174
|
+
'<button class="pp-action-btn" onclick="ppFilterChat(\'' + eName + '\')">' +
|
|
8175
|
+
'<svg viewBox="0 0 24 24"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>' +
|
|
8176
|
+
'Messages</button>' +
|
|
8177
|
+
'<button class="pp-action-btn" onclick="ppViewHistory(\'' + eName + '\')">' +
|
|
8178
|
+
'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>' +
|
|
8179
|
+
'History</button>';
|
|
8180
|
+
document.getElementById('pp-actions').innerHTML = actionsHtml;
|
|
8181
|
+
|
|
8182
|
+
// === Profile Tab ===
|
|
8183
|
+
document.getElementById('pp-bio').textContent = agent.bio || 'No bio set.';
|
|
8184
|
+
renderSkillEditor(agentName);
|
|
8185
|
+
|
|
8186
|
+
// Position near click — find closest clickable ancestor
|
|
8187
|
+
var anchor = event.target.closest('.agent-top') || event.target.closest('.agent-card') || event.target;
|
|
8188
|
+
var rect = anchor.getBoundingClientRect();
|
|
8189
|
+
popup.style.visibility = 'hidden';
|
|
6638
8190
|
popup.classList.add('open');
|
|
8191
|
+
var popupHeight = popup.offsetHeight;
|
|
8192
|
+
|
|
8193
|
+
var topPos = rect.bottom + 4;
|
|
8194
|
+
if (topPos + popupHeight > window.innerHeight - 10) {
|
|
8195
|
+
var topSpace = rect.top;
|
|
8196
|
+
var bottomSpace = window.innerHeight - rect.bottom;
|
|
8197
|
+
if (topSpace > bottomSpace && topSpace > popupHeight) {
|
|
8198
|
+
topPos = rect.top - popupHeight - 4;
|
|
8199
|
+
} else {
|
|
8200
|
+
topPos = window.innerHeight - popupHeight - 10;
|
|
8201
|
+
}
|
|
8202
|
+
}
|
|
8203
|
+
|
|
8204
|
+
popup.style.top = Math.max(10, topPos) + 'px';
|
|
8205
|
+
popup.style.left = Math.min(rect.left, window.innerWidth - 310) + 'px';
|
|
8206
|
+
popup.style.visibility = 'visible';
|
|
8207
|
+
}
|
|
8208
|
+
|
|
8209
|
+
function renderSkillEditor(agentName) {
|
|
8210
|
+
var agent = cachedAgents[agentName];
|
|
8211
|
+
var container = document.getElementById('pp-skills-container');
|
|
8212
|
+
if (!agent || !container) return;
|
|
8213
|
+
|
|
8214
|
+
var platformClass = 'platform-' + (agent.provider || 'unknown').toLowerCase();
|
|
8215
|
+
var skills = agent.skills || [];
|
|
8216
|
+
var platformSkills = agent.platform_skills || [];
|
|
8217
|
+
|
|
8218
|
+
var html = '<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.5px;color:var(--text-muted);margin-bottom:8px">Skills & Expertise</div>';
|
|
8219
|
+
html += '<div class="skill-editor">';
|
|
8220
|
+
|
|
8221
|
+
// Existing skills
|
|
8222
|
+
html += '<div class="skill-tags-inline">';
|
|
8223
|
+
for (var i = 0; i < skills.length; i++) {
|
|
8224
|
+
var skill = skills[i];
|
|
8225
|
+
var isPlatform = platformSkills.indexOf(skill) !== -1;
|
|
8226
|
+
html += '<span class="skill-tag' + (isPlatform ? ' ' + platformClass : '') + '">' +
|
|
8227
|
+
escapeHtml(skill) +
|
|
8228
|
+
'<span class="skill-remove" onclick="removeSkill(\'' + escapeHtml(agentName) + '\', ' + i + ')">×</span>' +
|
|
8229
|
+
'</span>';
|
|
8230
|
+
}
|
|
8231
|
+
html += '</div>';
|
|
8232
|
+
|
|
8233
|
+
// Add new skill input
|
|
8234
|
+
html += '<div class="skill-input-row" style="margin-top:8px;display:flex;gap:4px">' +
|
|
8235
|
+
'<input type="text" id="new-skill-input" placeholder="Add skill..." onkeydown="if(event.key===' + "'Enter'" + ') addSkill(\'' + escapeHtml(agentName) + '\')" style="flex:1;background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:4px 8px;font-size:11px;color:var(--text)">' +
|
|
8236
|
+
'<button onclick="addSkill(\'' + escapeHtml(agentName) + '\')" style="background:var(--nh-accent-dim);border:1px solid var(--nh-accent);border-radius:4px;padding:0 8px;color:var(--nh-accent);cursor:pointer;font-size:14px">+</button>' +
|
|
8237
|
+
'</div>';
|
|
8238
|
+
|
|
8239
|
+
html += '</div>';
|
|
8240
|
+
container.innerHTML = html;
|
|
8241
|
+
}
|
|
8242
|
+
|
|
8243
|
+
function addSkill(agentName) {
|
|
8244
|
+
var input = document.getElementById('new-skill-input');
|
|
8245
|
+
var skill = input.value.trim();
|
|
8246
|
+
if (!skill) return;
|
|
8247
|
+
|
|
8248
|
+
var agent = cachedAgents[agentName];
|
|
8249
|
+
var newSkills = (agent.skills || []).slice();
|
|
8250
|
+
if (newSkills.indexOf(skill) === -1) {
|
|
8251
|
+
newSkills.push(skill);
|
|
8252
|
+
updateAgentSkills(agentName, newSkills);
|
|
8253
|
+
}
|
|
8254
|
+
input.value = '';
|
|
8255
|
+
}
|
|
8256
|
+
|
|
8257
|
+
function removeSkill(agentName, index) {
|
|
8258
|
+
var agent = cachedAgents[agentName];
|
|
8259
|
+
var newSkills = (agent.skills || []).slice();
|
|
8260
|
+
newSkills.splice(index, 1);
|
|
8261
|
+
updateAgentSkills(agentName, newSkills);
|
|
8262
|
+
}
|
|
8263
|
+
|
|
8264
|
+
function updateAgentSkills(agentName, skills) {
|
|
8265
|
+
var pq = projectParam();
|
|
8266
|
+
lttFetch('/api/agent-cards' + pq, {
|
|
8267
|
+
method: 'POST',
|
|
8268
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8269
|
+
body: JSON.stringify({ name: agentName, skills: skills })
|
|
8270
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
8271
|
+
if (data.error) {
|
|
8272
|
+
showToast('!', 'Update failed: ' + data.error);
|
|
8273
|
+
} else {
|
|
8274
|
+
// Optimistic update of cache
|
|
8275
|
+
if (cachedAgents[agentName]) {
|
|
8276
|
+
cachedAgents[agentName].skills = skills;
|
|
8277
|
+
renderAgents(cachedAgents); // Refresh sidebar
|
|
8278
|
+
renderSkillEditor(agentName); // Refresh popup
|
|
8279
|
+
}
|
|
8280
|
+
}
|
|
8281
|
+
}).catch(function() {
|
|
8282
|
+
showToast('!', 'Update failed');
|
|
8283
|
+
});
|
|
8284
|
+
}
|
|
8285
|
+
|
|
8286
|
+
function showAgentPopup(agentName) {
|
|
8287
|
+
var agent = cachedAgents[agentName];
|
|
8288
|
+
if (!agent) return;
|
|
8289
|
+
editingAgent = agentName;
|
|
8290
|
+
openProfileEditor();
|
|
8291
|
+
|
|
8292
|
+
// Auto-switch to stats tab
|
|
8293
|
+
var tabs = document.querySelectorAll('.cd-tab');
|
|
8294
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
8295
|
+
if (tabs[i].textContent === 'Stats') {
|
|
8296
|
+
cdSwitchTab('stats', tabs[i]);
|
|
8297
|
+
break;
|
|
8298
|
+
}
|
|
8299
|
+
}
|
|
6639
8300
|
}
|
|
6640
8301
|
|
|
6641
8302
|
document.addEventListener('click', function() {
|
|
6642
8303
|
document.getElementById('profile-popup').classList.remove('open');
|
|
6643
|
-
// Reset delete button if popup closes
|
|
6644
8304
|
var delBtn = document.getElementById('pp-delete-btn');
|
|
6645
8305
|
if (delBtn) { delBtn.textContent = 'Delete Agent'; delBtn._confirming = false; }
|
|
6646
8306
|
});
|
|
@@ -6938,6 +8598,33 @@ function openProfileEditor() {
|
|
|
6938
8598
|
// Reset to Body tab
|
|
6939
8599
|
cdSwitchTab('body', document.querySelector('.cd-tab'));
|
|
6940
8600
|
|
|
8601
|
+
// Calculate and populate stats
|
|
8602
|
+
var sent = 0, received = 0;
|
|
8603
|
+
for (var i = 0; i < cachedHistory.length; i++) {
|
|
8604
|
+
if (cachedHistory[i].from === editingAgent) sent++;
|
|
8605
|
+
if (cachedHistory[i].to === editingAgent) received++;
|
|
8606
|
+
}
|
|
8607
|
+
var statsHtml =
|
|
8608
|
+
'<div style="background:var(--surface-2);padding:10px;border-radius:6px;border:1px solid var(--border)">Sent: <b style="color:var(--text)">' + sent + '</b></div>' +
|
|
8609
|
+
'<div style="background:var(--surface-2);padding:10px;border-radius:6px;border:1px solid var(--border)">Received: <b style="color:var(--text)">' + received + '</b></div>' +
|
|
8610
|
+
'<div style="background:var(--surface-2);padding:10px;border-radius:6px;border:1px solid var(--border)">Provider: <b style="color:var(--text)">' + escapeHtml(agent.provider || 'unknown') + '</b></div>' +
|
|
8611
|
+
'<div style="background:var(--surface-2);padding:10px;border-radius:6px;border:1px solid var(--border)">Branch: <b style="color:var(--text)">' + escapeHtml(agent.branch || 'main') + '</b></div>';
|
|
8612
|
+
document.getElementById('pe-stats-container').innerHTML = statsHtml;
|
|
8613
|
+
|
|
8614
|
+
var pq = projectParam();
|
|
8615
|
+
lttFetch('/api/token-usage' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
8616
|
+
if (!data || !data.agents || !data.agents[editingAgent]) return;
|
|
8617
|
+
var tu = data.agents[editingAgent];
|
|
8618
|
+
var tokenHtml =
|
|
8619
|
+
'<div style="grid-column:1/-1;margin-top:12px;font-weight:800;color:var(--text);font-size:11px;text-transform:uppercase;border-bottom:1px solid var(--border);padding-bottom:4px">Token Usage (<span style="color:var(--accent);font-weight:600">' + escapeHtml(tu.model || 'unknown') + '</span>)</div>' +
|
|
8620
|
+
'<div style="background:var(--surface-2);padding:10px;border-radius:6px;border:1px solid var(--border)">Input: <b style="color:var(--text)">' + (tu.input_tokens || 0).toLocaleString() + '</b> </div>' +
|
|
8621
|
+
'<div style="background:var(--surface-2);padding:10px;border-radius:6px;border:1px solid var(--border)">Output: <b style="color:var(--text)">' + (tu.output_tokens || 0).toLocaleString() + '</b> </div>' +
|
|
8622
|
+
'<div style="background:var(--surface-2);padding:10px;border-radius:6px;border:1px solid var(--border)">Cache: <b style="color:var(--text)">' + ((tu.cache_creation_tokens || 0) + (tu.cache_read_tokens || 0)).toLocaleString() + '</b> </div>' +
|
|
8623
|
+
'<div style="background:var(--accent-dim);color:var(--accent);padding:10px;border-radius:6px;border:1px solid var(--accent);font-weight:700">Cost: $' + (tu.estimated_cost_usd || 0).toFixed(2) + '</div>';
|
|
8624
|
+
var el = document.getElementById('pe-stats-container');
|
|
8625
|
+
if (el) el.innerHTML += tokenHtml;
|
|
8626
|
+
}).catch(function() {});
|
|
8627
|
+
|
|
6941
8628
|
// Open the panel
|
|
6942
8629
|
document.getElementById('char-designer').classList.add('open');
|
|
6943
8630
|
document.getElementById('cd-backdrop').classList.add('open');
|
|
@@ -7089,6 +8776,20 @@ function buildWorkflowHTML(wf) {
|
|
|
7089
8776
|
for (var j = 0; j < wf.steps.length; j++) {
|
|
7090
8777
|
var s = wf.steps[j];
|
|
7091
8778
|
var stepColor = s.status === 'done' ? 'var(--green)' : s.status === 'in_progress' ? 'var(--accent)' : 'var(--text-muted)';
|
|
8779
|
+
|
|
8780
|
+
var hasActiveRule = false;
|
|
8781
|
+
if (typeof cachedRules !== 'undefined' && s.assignee && cachedAgents && cachedAgents[s.assignee]) {
|
|
8782
|
+
var a = cachedAgents[s.assignee];
|
|
8783
|
+
for (var r=0; r < cachedRules.length; r++) {
|
|
8784
|
+
if (!cachedRules[r].active) continue;
|
|
8785
|
+
var matchRole = !cachedRules[r].scope_role || cachedRules[r].scope_role === (a.role || '').toLowerCase();
|
|
8786
|
+
var matchProv = !cachedRules[r].scope_provider || cachedRules[r].scope_provider === (a.provider || '').toLowerCase();
|
|
8787
|
+
var matchAgt = !cachedRules[r].scope_agent || cachedRules[r].scope_agent === s.assignee;
|
|
8788
|
+
if (matchRole && matchProv && matchAgt) { hasActiveRule = true; break; }
|
|
8789
|
+
}
|
|
8790
|
+
}
|
|
8791
|
+
var shieldIcon = hasActiveRule ? '<span class="step-shield" title="Governed by active rules"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg></span>' : '';
|
|
8792
|
+
|
|
7092
8793
|
if (j > 0) stepsHtml += '<span class="step-arrow">→</span>';
|
|
7093
8794
|
var stepTimeHtml = '';
|
|
7094
8795
|
if (s.status === 'done' && s.started_at && s.completed_at) {
|
|
@@ -7100,6 +8801,7 @@ function buildWorkflowHTML(wf) {
|
|
|
7100
8801
|
}
|
|
7101
8802
|
stepsHtml += '<div class="step-card ' + s.status + '" data-step-id="' + s.id + '" title="' + escapeHtml(s.notes || '') + '">' +
|
|
7102
8803
|
'<span class="step-num">' + s.id + '</span>' +
|
|
8804
|
+
shieldIcon +
|
|
7103
8805
|
'<span class="step-status" style="font-size:10px;color:' + stepColor + ';font-weight:600;text-transform:uppercase">' + s.status.replace('_', ' ') + '</span>' +
|
|
7104
8806
|
'<div class="step-desc">' + escapeHtml(s.description) + '</div>' +
|
|
7105
8807
|
(s.assignee ? '<div class="step-assignee">' + escapeHtml(s.assignee) + '</div>' : '') +
|
|
@@ -7606,7 +9308,7 @@ function checkPlanCompletion() {
|
|
|
7606
9308
|
if (Notification.permission === 'granted') {
|
|
7607
9309
|
new Notification('Plan Complete: ' + wf.name, {
|
|
7608
9310
|
body: 'All ' + wf.steps.length + ' steps done!',
|
|
7609
|
-
icon: '/
|
|
9311
|
+
icon: '/favicon.png',
|
|
7610
9312
|
});
|
|
7611
9313
|
}
|
|
7612
9314
|
// Flash the tab title
|
|
@@ -7647,7 +9349,7 @@ function renderMonitorPanel(data) {
|
|
|
7647
9349
|
for (var i = 0; i < agentNames.length; i++) {
|
|
7648
9350
|
var name = agentNames[i];
|
|
7649
9351
|
var a = agents[name];
|
|
7650
|
-
var color = a.status === 'working' ? 'var(--green)' : a.status === 'listening' ? '
|
|
9352
|
+
var color = a.status === 'working' ? 'var(--nh-green)' : a.status === 'listening' ? 'var(--nh-blue)' : a.status === 'idle' ? 'var(--nh-accent)' : 'var(--nh-red)';
|
|
7651
9353
|
var role = a.role ? ' (' + a.role + ')' : '';
|
|
7652
9354
|
html += '<div style="padding:4px 8px;background:var(--surface-2);border-left:3px solid ' + color + ';border-radius:4px;font-size:11px">' +
|
|
7653
9355
|
'<span style="font-weight:600">' + escapeHtml(name) + '</span>' +
|
|
@@ -7912,6 +9614,8 @@ function poll() {
|
|
|
7912
9614
|
// Browser notification for new messages
|
|
7913
9615
|
if (prevHistLen > 0 && cachedHistory.length > prevHistLen) {
|
|
7914
9616
|
sendBrowserNotification(cachedHistory[cachedHistory.length - 1]);
|
|
9617
|
+
// Real-time terminal sync: new messages arrived, update any open terminals
|
|
9618
|
+
syncTerminalsFromHistory();
|
|
7915
9619
|
}
|
|
7916
9620
|
|
|
7917
9621
|
$id('stat-messages').textContent = status.messageCount;
|
|
@@ -9501,12 +11205,16 @@ function updateAgentBar(agents) {
|
|
|
9501
11205
|
var name = sorted[i][0];
|
|
9502
11206
|
var agent = sorted[i][1];
|
|
9503
11207
|
if (name === '__system__' || name === 'Dashboard') continue;
|
|
9504
|
-
var cls = 'offline';
|
|
9505
|
-
var activity =
|
|
11208
|
+
var cls = agent.status || 'offline';
|
|
11209
|
+
var activity = cls;
|
|
9506
11210
|
if (agent.alive) {
|
|
9507
11211
|
if (agent.is_listening || agent.status === 'listening') { cls = 'listening'; activity = 'listening'; }
|
|
9508
11212
|
else if (agent.status === 'idle') { cls = 'idle'; activity = 'idle'; }
|
|
9509
11213
|
else { cls = 'working'; activity = agent.status === 'working' ? 'working' : 'active'; }
|
|
11214
|
+
} else if (agent.status === 'unknown') {
|
|
11215
|
+
activity = 'never started';
|
|
11216
|
+
} else if (agent.status === 'stale') {
|
|
11217
|
+
activity = 'stale';
|
|
9510
11218
|
}
|
|
9511
11219
|
// Toast on state change
|
|
9512
11220
|
var prevState = prevAgentBarState[name];
|
|
@@ -9531,7 +11239,7 @@ function renderOverview() {
|
|
|
9531
11239
|
if (!area) return;
|
|
9532
11240
|
var savedScroll = area.scrollTop;
|
|
9533
11241
|
var pq = projectParam();
|
|
9534
|
-
var histQ = pq + (pq ? '&' : '?') + 'limit=
|
|
11242
|
+
var histQ = pq + (pq ? '&' : '?') + 'limit=12' + (activeBranch && activeBranch !== 'main' ? '&branch=' + encodeURIComponent(activeBranch) : '');
|
|
9535
11243
|
Promise.all([
|
|
9536
11244
|
lttFetch('/api/status' + pq).then(function(r) { return r.json(); }),
|
|
9537
11245
|
lttFetch('/api/agents' + pq).then(function(r) { return r.json(); }),
|
|
@@ -9550,7 +11258,7 @@ function renderOverview() {
|
|
|
9550
11258
|
var agentEntries = Object.entries(agents).filter(function(e) { return e[0] !== '__system__'; });
|
|
9551
11259
|
|
|
9552
11260
|
// Data hash to skip unnecessary rebuilds (excludes timestamps)
|
|
9553
|
-
var overviewHash = (status.aliveCount || 0) + ':' + (status.messageCount || 0) + ':' + agentEntries.length + ':' + tasks.map(function(t) { return t.status; }).join(',') + ':' + workflows.map(function(w) { return w.status; }).join(',') + ':' + history.length + ':' + notifications.length + ':b:' + (activeBranch || 'main');
|
|
11261
|
+
var overviewHash = (status.aliveCount || 0) + ':' + (status.messageCount || 0) + ':' + agentEntries.length + ':' + tasks.map(function(t) { return t.status; }).join(',') + ':' + workflows.map(function(w) { return w.status; }).join(',') + ':' + history.length + ':' + notifications.length + ':b:' + (activeBranch || 'main') + ':c:' + (status.coordinator_mode || 'responsive');
|
|
9554
11262
|
if (overviewHash === renderOverview._lastHash) { return; }
|
|
9555
11263
|
renderOverview._lastHash = overviewHash;
|
|
9556
11264
|
|
|
@@ -9568,137 +11276,152 @@ function renderOverview() {
|
|
|
9568
11276
|
|
|
9569
11277
|
// Welcome header with coordinator mode toggle
|
|
9570
11278
|
var coordMode = status.coordinator_mode || 'responsive';
|
|
9571
|
-
|
|
11279
|
+
var regTotal = agentEntries.length || (status.agentCount || 0);
|
|
11280
|
+
var aliveN = status.aliveCount || 0;
|
|
11281
|
+
html += '<div class="overview-welcome">';
|
|
9572
11282
|
html += '<div><h2>Dashboard</h2>';
|
|
9573
|
-
html += '<p>' + (status.conversation_mode === 'managed' ? 'Managed mode' : 'Direct mode') + '
|
|
9574
|
-
html += '<div
|
|
9575
|
-
html += '<span
|
|
9576
|
-
html += '<div
|
|
9577
|
-
html += '<button
|
|
9578
|
-
html += '<button
|
|
11283
|
+
html += '<p>' + (status.conversation_mode === 'managed' ? 'Managed mode' : 'Direct mode') + ' — ' + regTotal + ' agent' + (regTotal === 1 ? '' : 's') + ' registered</p></div>';
|
|
11284
|
+
html += '<div class="overview-coordinator">';
|
|
11285
|
+
html += '<span class="overview-coordinator-label">Coordinator</span>';
|
|
11286
|
+
html += '<div class="overview-coordinator-toggle" role="group" aria-label="Coordinator mode">';
|
|
11287
|
+
html += '<button type="button" class="overview-coordinator-btn' + (coordMode === 'responsive' ? ' overview-coordinator-btn--mode-responsive' : '') + '" onclick="setCoordinatorMode(\'responsive\')">Stay with me</button>';
|
|
11288
|
+
html += '<button type="button" class="overview-coordinator-btn' + (coordMode === 'autonomous' ? ' overview-coordinator-btn--mode-autonomous' : '') + '" onclick="setCoordinatorMode(\'autonomous\')">Run autonomously</button>';
|
|
9579
11289
|
html += '</div></div>';
|
|
9580
11290
|
html += '</div>';
|
|
9581
11291
|
|
|
9582
|
-
// Metric cards with icons
|
|
9583
11292
|
html += '<div class="overview-metrics">';
|
|
9584
11293
|
html += '<div class="metric-card">';
|
|
9585
11294
|
html += '<div class="metric-icon" style="background:var(--green-dim)"><svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="var(--green)" stroke-width="1.5"><circle cx="8" cy="5" r="3"/><path d="M2 14c0-3.3 2.7-6 6-6s6 2.7 6 6"/></svg></div>';
|
|
9586
|
-
html += '<div class="metric-label">Active
|
|
9587
|
-
html += '<div class="metric-value">' +
|
|
9588
|
-
html += '<div class="metric-sub">' +
|
|
11295
|
+
html += '<div class="metric-label">Active agents</div>';
|
|
11296
|
+
html += '<div class="metric-value">' + aliveN + '/' + regTotal + '</div>';
|
|
11297
|
+
html += '<div class="metric-sub">' + regTotal + ' registered</div>';
|
|
9589
11298
|
html += '</div>';
|
|
9590
11299
|
|
|
9591
11300
|
html += '<div class="metric-card">';
|
|
9592
11301
|
html += '<div class="metric-icon" style="background:var(--accent-dim)"><svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="var(--accent)" stroke-width="1.5"><path d="M2 3h12v8H6l-3 2.5V11H2z" stroke-linejoin="round"/></svg></div>';
|
|
9593
11302
|
html += '<div class="metric-label">Messages</div>';
|
|
9594
11303
|
html += '<div class="metric-value">' + (status.messageCount || 0) + '</div>';
|
|
9595
|
-
html += '<div class="metric-sub">
|
|
11304
|
+
html += '<div class="metric-sub">Total exchanged</div>';
|
|
9596
11305
|
html += '</div>';
|
|
9597
11306
|
|
|
9598
11307
|
html += '<div class="metric-card">';
|
|
9599
11308
|
html += '<div class="metric-icon" style="background:' + (taskPct === 100 ? 'var(--green-dim)' : 'var(--purple-dim)') + '"><svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="' + (taskPct === 100 ? 'var(--green)' : 'var(--purple)') + '" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><path d="M5 8l2 2 4-4"/></svg></div>';
|
|
9600
|
-
html += '<div class="metric-label">Task
|
|
11309
|
+
html += '<div class="metric-label">Task progress</div>';
|
|
9601
11310
|
html += '<div class="metric-value">' + taskPct + '<span style="font-size:18px;font-weight:600;color:var(--text-muted);margin-left:2px">%</span></div>';
|
|
11311
|
+
html += '<div class="metric-card__progress"><div class="metric-card__progress-fill" style="width:' + taskPct + '%"></div></div>';
|
|
9602
11312
|
html += '<div class="metric-sub">' + done + ' done, ' + inProgress + ' active, ' + pending + ' pending</div>';
|
|
9603
11313
|
html += '</div>';
|
|
9604
11314
|
html += '</div>';
|
|
9605
11315
|
|
|
9606
11316
|
if (!hasAgents && !hasMessages && !hasWorkflows && !hasTasks) {
|
|
9607
|
-
|
|
9608
|
-
html += '<div
|
|
9609
|
-
html += '<div
|
|
9610
|
-
html += '<div
|
|
9611
|
-
html += '<
|
|
9612
|
-
html += '<button class="overview-launch-btn" onclick="switchView(\'launch\')"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 2v12M2 8h12"/></svg> Launch Agents</button>';
|
|
11317
|
+
html += '<div class="overview-empty-state">';
|
|
11318
|
+
html += '<div class="overview-empty-state-icon"><svg viewBox="0 0 16 16" width="28" height="28" fill="none" stroke="var(--accent)" stroke-width="1.2"><path d="M8 1.5L14 5v6l-6 3.5L2 11V5z"/></svg></div>';
|
|
11319
|
+
html += '<div class="overview-empty-state-title">No agents online yet</div>';
|
|
11320
|
+
html += '<div class="overview-empty-state-desc">Open a CLI terminal and register an agent to start collaborating.</div>';
|
|
11321
|
+
html += '<button type="button" class="overview-launch-btn" onclick="switchView(\'launch\')"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 2v12M2 8h12"/></svg> Launch Agents</button>';
|
|
9613
11322
|
html += '</div>';
|
|
9614
11323
|
} else {
|
|
9615
|
-
html += '<div class="overview-
|
|
9616
|
-
|
|
9617
|
-
// Active agents panel
|
|
9618
|
-
if (hasAgents) {
|
|
9619
|
-
html += '<div class="overview-panel"><h3>Agents <a onclick="switchView(\'messages\')">View messages →</a></h3>';
|
|
9620
|
-
for (var i = 0; i < agentEntries.length; i++) {
|
|
9621
|
-
var aName = agentEntries[i][0];
|
|
9622
|
-
var aInfo = agentEntries[i][1];
|
|
9623
|
-
var dotColor = !aInfo.alive ? '#6b7280' : aInfo.is_listening ? '#3b82f6' : aInfo.status === 'idle' ? '#f59e0b' : '#22c55e';
|
|
9624
|
-
var dotGlow = !aInfo.alive ? '' : ';box-shadow:0 0 6px ' + dotColor;
|
|
9625
|
-
var statusText = !aInfo.alive ? 'offline' : aInfo.is_listening ? 'listening' : aInfo.status === 'idle' ? 'idle' : 'working';
|
|
9626
|
-
html += '<div class="overview-agent-row"><span class="overview-agent-dot" style="background:' + dotColor + dotGlow + '"></span><span class="overview-agent-name">' + aName + '</span><span class="overview-agent-status">' + statusText + ' · ' + (aInfo.provider || '?') + '</span></div>';
|
|
9627
|
-
}
|
|
9628
|
-
html += '</div>';
|
|
9629
|
-
}
|
|
11324
|
+
html += '<div class="overview-stitch-rows">';
|
|
9630
11325
|
|
|
9631
|
-
// Workflow progress panel
|
|
9632
11326
|
if (hasWorkflows) {
|
|
9633
|
-
html += '<div class="overview-panel"><h3>Workflows <a onclick="switchView(\'workflows\')">View all
|
|
11327
|
+
html += '<div class="overview-panel overview-full-width"><h3>Workflows <a onclick="switchView(\'workflows\')">View all</a></h3>';
|
|
9634
11328
|
for (var w = 0; w < activeWfs.length; w++) {
|
|
9635
11329
|
var wf = activeWfs[w];
|
|
9636
11330
|
var wfDone = wf.steps ? wf.steps.filter(function(s) { return s.status === 'done'; }).length : 0;
|
|
9637
11331
|
var wfTotal = wf.steps ? wf.steps.length : 1;
|
|
9638
11332
|
var wfPct = Math.round((wfDone / wfTotal) * 100);
|
|
9639
11333
|
html += '<div style="margin-bottom:12px"><div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px"><span style="font-size:13px;font-weight:600;color:var(--text)">' + (wf.name || 'Workflow') + '</span><span style="font-size:12px;color:var(--text-muted)">' + wfPct + '<span style="font-size:0.8em;margin-left:1px">%</span></span></div>';
|
|
9640
|
-
html += '<div
|
|
11334
|
+
html += '<div class="metric-card__progress"><div class="metric-card__progress-fill" style="width:' + wfPct + '%"></div></div></div>';
|
|
9641
11335
|
}
|
|
9642
11336
|
html += '</div>';
|
|
9643
11337
|
}
|
|
9644
11338
|
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
11339
|
+
html += '<div class="overview-grid overview-grid--pair">';
|
|
11340
|
+
html += '<div class="overview-panel"><h3>Agents <a onclick="switchView(\'messages\')">View details</a></h3>';
|
|
11341
|
+
if (hasAgents) {
|
|
11342
|
+
for (var i = 0; i < agentEntries.length; i++) {
|
|
11343
|
+
var aName = agentEntries[i][0];
|
|
11344
|
+
var aInfo = agentEntries[i][1];
|
|
11345
|
+
var dotMod = !aInfo.alive ? 'offline' : aInfo.is_listening ? 'listening' : aInfo.status === 'idle' ? 'idle' : 'working';
|
|
11346
|
+
var statusText = !aInfo.alive ? 'offline' : aInfo.is_listening ? 'listening' : aInfo.status === 'idle' ? 'idle' : 'working';
|
|
11347
|
+
var role = aInfo.role || '';
|
|
11348
|
+
html += '<div class="overview-agent-row"><span class="nh-presence-dot nh-presence-dot--' + dotMod + '" aria-hidden="true"></span><span class="overview-agent-name">' + aName + '</span>';
|
|
11349
|
+
if (role) html += '<span class="overview-role-pill">' + escapeHtml(role) + '</span>';
|
|
11350
|
+
html += '<span class="overview-agent-status" style="margin-left:auto">' + statusText + '</span></div>';
|
|
11351
|
+
}
|
|
11352
|
+
} else {
|
|
11353
|
+
html += '<p class="overview-panel-empty">No agents registered</p>';
|
|
11354
|
+
}
|
|
11355
|
+
html += '</div>';
|
|
11356
|
+
|
|
11357
|
+
html += '<div class="overview-panel"><h3>Tasks <a onclick="switchView(\'tasks\')">View board</a></h3>';
|
|
11358
|
+
if (hasTasks) {
|
|
11359
|
+
var rem = pending + inProgress;
|
|
11360
|
+
html += '<div class="overview-task-stitch"><span class="overview-task-stitch-done">' + done + ' done</span><span class="overview-task-stitch-rem">' + rem + ' remaining</span></div>';
|
|
11361
|
+
html += '<div class="overview-task-summary" style="margin-top:10px">';
|
|
9649
11362
|
if (pending > 0) html += '<span class="overview-task-badge pending">' + pending + ' pending</span>';
|
|
9650
11363
|
if (inProgress > 0) html += '<span class="overview-task-badge active">' + inProgress + ' active</span>';
|
|
9651
|
-
if (done > 0) html += '<span class="overview-task-badge done">' + done + ' done</span>';
|
|
9652
|
-
html += '</div>';
|
|
9653
11364
|
html += '</div>';
|
|
11365
|
+
} else {
|
|
11366
|
+
html += '<p class="overview-panel-empty">No tasks yet</p>';
|
|
9654
11367
|
}
|
|
11368
|
+
html += '</div>';
|
|
11369
|
+
html += '</div>';
|
|
9655
11370
|
|
|
9656
|
-
|
|
11371
|
+
html += '<div class="overview-grid overview-grid--pair">';
|
|
11372
|
+
html += '<div class="overview-panel"><h3>Recent Activity <a onclick="switchView(\'messages\')">View all</a></h3>';
|
|
9657
11373
|
if (hasMessages) {
|
|
9658
|
-
html += '<div class="overview-panel overview-full-width"><h3>Recent Activity <a onclick="switchView(\'messages\')">View all →</a></h3>';
|
|
9659
11374
|
for (var m = 0; m < history.length; m++) {
|
|
9660
11375
|
var msg = history[m];
|
|
9661
|
-
var
|
|
9662
|
-
var
|
|
9663
|
-
var
|
|
9664
|
-
|
|
11376
|
+
var fromN = msg.from || '?';
|
|
11377
|
+
var fromL = fromN.toLowerCase();
|
|
11378
|
+
var fk = fromL === 'coordinator' ? 'coord' : (fromL === 'system' || fromL === '__system__') ? 'system' : 'agent';
|
|
11379
|
+
var t = new Date(msg.timestamp);
|
|
11380
|
+
var th = t.getHours();
|
|
11381
|
+
var tm = String(t.getMinutes()).padStart(2, '0');
|
|
11382
|
+
var ap = th >= 12 ? 'PM' : 'AM';
|
|
11383
|
+
var th12 = th % 12;
|
|
11384
|
+
if (th12 === 0) th12 = 12;
|
|
11385
|
+
var timeBracket = '[' + th12 + ':' + tm + ' ' + ap + ']';
|
|
11386
|
+
var preview = (msg.content || '').replace(/[#*`_~\[\]]/g, '').substring(0, 120);
|
|
11387
|
+
html += '<div class="overview-activity-row"><span class="overview-activity-time">' + timeBracket + '</span><span class="overview-activity-body"><span class="overview-msg-from--' + fk + '">' + escapeHtml(fromN) + '</span><span class="overview-activity-preview">: ' + escapeHtml(preview) + '</span></span></div>';
|
|
9665
11388
|
}
|
|
9666
|
-
|
|
11389
|
+
} else {
|
|
11390
|
+
html += '<p class="overview-panel-empty">No recent messages</p>';
|
|
9667
11391
|
}
|
|
11392
|
+
html += '</div>';
|
|
9668
11393
|
|
|
9669
|
-
|
|
9670
|
-
if (
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
11394
|
+
html += '<div class="overview-panel"><h3>Recent Notifications</h3>';
|
|
11395
|
+
if (notifications.length > 0) {
|
|
11396
|
+
var NOTIF_STITCH = {
|
|
11397
|
+
task_done: { icon: '✓', label: 'Success', cls: 'overview-notif--success' },
|
|
11398
|
+
agent_online: { icon: '✓', label: 'Success', cls: 'overview-notif--success' },
|
|
11399
|
+
workflow_advanced: { icon: 'i', label: 'Info', cls: 'overview-notif--info' },
|
|
11400
|
+
agent_listening: { icon: 'i', label: 'Info', cls: 'overview-notif--info' },
|
|
11401
|
+
agent_busy: { icon: 'i', label: 'Info', cls: 'overview-notif--info' },
|
|
11402
|
+
agent_offline: { icon: '–', label: 'Info', cls: 'overview-notif--neutral' },
|
|
11403
|
+
approval_needed: { icon: '!', label: 'Warning', cls: 'overview-notif--warn' }
|
|
11404
|
+
};
|
|
11405
|
+
var recentN = notifications.slice(-8).reverse();
|
|
11406
|
+
for (var ni = 0; ni < recentN.length; ni++) {
|
|
11407
|
+
var nv = recentN[ni];
|
|
11408
|
+
var meta = NOTIF_STITCH[nv.type] || { icon: '•', label: 'Info', cls: 'overview-notif--neutral' };
|
|
11409
|
+
var ntime = new Date(nv.timestamp);
|
|
11410
|
+
var nth = ntime.getHours();
|
|
11411
|
+
var ntm = String(ntime.getMinutes()).padStart(2, '0');
|
|
11412
|
+
var nap = nth >= 12 ? 'PM' : 'AM';
|
|
11413
|
+
var nth12 = nth % 12;
|
|
11414
|
+
if (nth12 === 0) nth12 = 12;
|
|
11415
|
+
var ntimeStr = nth12 + ':' + ntm + ' ' + nap;
|
|
11416
|
+
var nbody = escapeHtml(nv.message || nv.summary || nv.type || '');
|
|
11417
|
+
html += '<div class="overview-notif-row-stitch ' + meta.cls + '"><span class="overview-notif-icon-stitch">' + meta.icon + '</span><div style="flex:1;min-width:0"><span class="overview-notif-tag">[' + meta.label + ']</span><span class="overview-notif-msg">' + nbody + '</span></div><span class="overview-notif-time-stitch">' + ntimeStr + '</span></div>';
|
|
11418
|
+
}
|
|
11419
|
+
} else {
|
|
11420
|
+
html += '<p class="overview-panel-empty">No notifications</p>';
|
|
9678
11421
|
}
|
|
9679
|
-
|
|
9680
11422
|
html += '</div>';
|
|
9681
|
-
|
|
11423
|
+
html += '</div>';
|
|
9682
11424
|
|
|
9683
|
-
// Notifications feed (always show if there are recent notifications)
|
|
9684
|
-
if (notifications.length > 0) {
|
|
9685
|
-
var NOTIF_ICONS = { task_done: '✓', workflow_advanced: '▶', agent_online: '+', agent_offline: '-', approval_needed: '!' };
|
|
9686
|
-
var NOTIF_COLORS = { task_done: 'var(--green)', workflow_advanced: 'var(--accent)', agent_online: 'var(--green)', agent_offline: 'var(--text-muted)', approval_needed: 'var(--orange)' };
|
|
9687
|
-
html += '<div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px 20px">';
|
|
9688
|
-
html += '<div style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:12px">Recent Notifications</div>';
|
|
9689
|
-
var recent = notifications.slice(-8).reverse();
|
|
9690
|
-
for (var ni = 0; ni < recent.length; ni++) {
|
|
9691
|
-
var n = recent[ni];
|
|
9692
|
-
var ntime = new Date(n.timestamp);
|
|
9693
|
-
var ntimeStr = ntime.getHours() + ':' + String(ntime.getMinutes()).padStart(2, '0');
|
|
9694
|
-
var ncolor = NOTIF_COLORS[n.type] || 'var(--text-muted)';
|
|
9695
|
-
var nicon = NOTIF_ICONS[n.type] || '•';
|
|
9696
|
-
html += '<div style="display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);font-size:13px">';
|
|
9697
|
-
html += '<span style="width:22px;height:22px;border-radius:6px;background:' + ncolor + '15;color:' + ncolor + ';display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex-shrink:0">' + nicon + '</span>';
|
|
9698
|
-
html += '<span style="flex:1;color:var(--text-dim)">' + (n.summary || n.type) + '</span>';
|
|
9699
|
-
html += '<span style="font-size:11px;color:var(--text-muted);flex-shrink:0">' + ntimeStr + '</span>';
|
|
9700
|
-
html += '</div>';
|
|
9701
|
-
}
|
|
9702
11425
|
html += '</div>';
|
|
9703
11426
|
}
|
|
9704
11427
|
|