agent-relay 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +176 -6
  2. package/dist/bridge/config.d.ts +41 -0
  3. package/dist/bridge/config.d.ts.map +1 -0
  4. package/dist/bridge/config.js +143 -0
  5. package/dist/bridge/config.js.map +1 -0
  6. package/dist/bridge/index.d.ts +10 -0
  7. package/dist/bridge/index.d.ts.map +1 -0
  8. package/dist/bridge/index.js +10 -0
  9. package/dist/bridge/index.js.map +1 -0
  10. package/dist/bridge/multi-project-client.d.ts +99 -0
  11. package/dist/bridge/multi-project-client.d.ts.map +1 -0
  12. package/dist/bridge/multi-project-client.js +386 -0
  13. package/dist/bridge/multi-project-client.js.map +1 -0
  14. package/dist/bridge/spawner.d.ts +46 -0
  15. package/dist/bridge/spawner.d.ts.map +1 -0
  16. package/dist/bridge/spawner.js +223 -0
  17. package/dist/bridge/spawner.js.map +1 -0
  18. package/dist/bridge/types.d.ts +55 -0
  19. package/dist/bridge/types.d.ts.map +1 -0
  20. package/dist/bridge/types.js +6 -0
  21. package/dist/bridge/types.js.map +1 -0
  22. package/dist/bridge/utils.d.ts +30 -0
  23. package/dist/bridge/utils.d.ts.map +1 -0
  24. package/dist/bridge/utils.js +54 -0
  25. package/dist/bridge/utils.js.map +1 -0
  26. package/dist/cli/index.d.ts +2 -0
  27. package/dist/cli/index.d.ts.map +1 -1
  28. package/dist/cli/index.js +906 -6
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/daemon/agent-registry.d.ts +60 -0
  31. package/dist/daemon/agent-registry.d.ts.map +1 -0
  32. package/dist/daemon/agent-registry.js +163 -0
  33. package/dist/daemon/agent-registry.js.map +1 -0
  34. package/dist/daemon/connection.d.ts +33 -1
  35. package/dist/daemon/connection.d.ts.map +1 -1
  36. package/dist/daemon/connection.js +86 -11
  37. package/dist/daemon/connection.js.map +1 -1
  38. package/dist/daemon/index.d.ts +2 -0
  39. package/dist/daemon/index.d.ts.map +1 -1
  40. package/dist/daemon/index.js +2 -0
  41. package/dist/daemon/index.js.map +1 -1
  42. package/dist/daemon/registry.d.ts +9 -0
  43. package/dist/daemon/registry.d.ts.map +1 -0
  44. package/dist/daemon/registry.js +9 -0
  45. package/dist/daemon/registry.js.map +1 -0
  46. package/dist/daemon/router.d.ts +61 -2
  47. package/dist/daemon/router.d.ts.map +1 -1
  48. package/dist/daemon/router.js +219 -4
  49. package/dist/daemon/router.js.map +1 -1
  50. package/dist/daemon/server.d.ts +9 -0
  51. package/dist/daemon/server.d.ts.map +1 -1
  52. package/dist/daemon/server.js +135 -16
  53. package/dist/daemon/server.js.map +1 -1
  54. package/dist/dashboard/metrics.d.ts +105 -0
  55. package/dist/dashboard/metrics.d.ts.map +1 -0
  56. package/dist/dashboard/metrics.js +192 -0
  57. package/dist/dashboard/metrics.js.map +1 -0
  58. package/dist/dashboard/needs-attention.d.ts +24 -0
  59. package/dist/dashboard/needs-attention.d.ts.map +1 -0
  60. package/dist/dashboard/needs-attention.js +78 -0
  61. package/dist/dashboard/needs-attention.js.map +1 -0
  62. package/dist/dashboard/public/bridge.html +1272 -0
  63. package/dist/dashboard/public/index.html +2094 -347
  64. package/dist/dashboard/public/js/app.js +184 -0
  65. package/dist/dashboard/public/js/app.js.map +7 -0
  66. package/dist/dashboard/public/metrics.html +999 -0
  67. package/dist/dashboard/server.d.ts +14 -1
  68. package/dist/dashboard/server.d.ts.map +1 -1
  69. package/dist/dashboard/server.js +689 -16
  70. package/dist/dashboard/server.js.map +1 -1
  71. package/dist/dashboard/start.js +1 -1
  72. package/dist/dashboard/start.js.map +1 -1
  73. package/dist/dashboard-v2/index.d.ts +10 -0
  74. package/dist/dashboard-v2/index.d.ts.map +1 -0
  75. package/dist/dashboard-v2/index.js +54 -0
  76. package/dist/dashboard-v2/index.js.map +1 -0
  77. package/dist/dashboard-v2/lib/api.d.ts +95 -0
  78. package/dist/dashboard-v2/lib/api.d.ts.map +1 -0
  79. package/dist/dashboard-v2/lib/api.js +270 -0
  80. package/dist/dashboard-v2/lib/api.js.map +1 -0
  81. package/dist/dashboard-v2/lib/colors.d.ts +61 -0
  82. package/dist/dashboard-v2/lib/colors.d.ts.map +1 -0
  83. package/dist/dashboard-v2/lib/colors.js +198 -0
  84. package/dist/dashboard-v2/lib/colors.js.map +1 -0
  85. package/dist/dashboard-v2/lib/hierarchy.d.ts +74 -0
  86. package/dist/dashboard-v2/lib/hierarchy.d.ts.map +1 -0
  87. package/dist/dashboard-v2/lib/hierarchy.js +196 -0
  88. package/dist/dashboard-v2/lib/hierarchy.js.map +1 -0
  89. package/dist/dashboard-v2/types/index.d.ts +154 -0
  90. package/dist/dashboard-v2/types/index.d.ts.map +1 -0
  91. package/dist/dashboard-v2/types/index.js +6 -0
  92. package/dist/dashboard-v2/types/index.js.map +1 -0
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/protocol/types.d.ts +15 -1
  96. package/dist/protocol/types.d.ts.map +1 -1
  97. package/dist/storage/adapter.d.ts +74 -1
  98. package/dist/storage/adapter.d.ts.map +1 -1
  99. package/dist/storage/adapter.js +39 -0
  100. package/dist/storage/adapter.js.map +1 -1
  101. package/dist/storage/sqlite-adapter.d.ts +92 -1
  102. package/dist/storage/sqlite-adapter.d.ts.map +1 -1
  103. package/dist/storage/sqlite-adapter.js +615 -47
  104. package/dist/storage/sqlite-adapter.js.map +1 -1
  105. package/dist/utils/agent-config.d.ts +45 -0
  106. package/dist/utils/agent-config.d.ts.map +1 -0
  107. package/dist/utils/agent-config.js +118 -0
  108. package/dist/utils/agent-config.js.map +1 -0
  109. package/dist/utils/project-namespace.d.ts.map +1 -1
  110. package/dist/utils/project-namespace.js +22 -1
  111. package/dist/utils/project-namespace.js.map +1 -1
  112. package/dist/wrapper/client.d.ts +30 -3
  113. package/dist/wrapper/client.d.ts.map +1 -1
  114. package/dist/wrapper/client.js +85 -9
  115. package/dist/wrapper/client.js.map +1 -1
  116. package/dist/wrapper/parser.d.ts +127 -4
  117. package/dist/wrapper/parser.d.ts.map +1 -1
  118. package/dist/wrapper/parser.js +622 -86
  119. package/dist/wrapper/parser.js.map +1 -1
  120. package/dist/wrapper/tmux-wrapper.d.ts +136 -10
  121. package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
  122. package/dist/wrapper/tmux-wrapper.js +599 -79
  123. package/dist/wrapper/tmux-wrapper.js.map +1 -1
  124. package/docs/AGENTS.md +132 -27
  125. package/docs/ARCHITECTURE_DECISIONS.md +175 -0
  126. package/docs/CHANGELOG.md +1 -1
  127. package/docs/COMPETITIVE_ANALYSIS.md +897 -0
  128. package/docs/DESIGN_BRIDGE_STAFFING.md +878 -0
  129. package/docs/DESIGN_V2.md +1079 -0
  130. package/docs/INTEGRATION-GUIDE.md +926 -0
  131. package/docs/MONETIZATION.md +1679 -0
  132. package/docs/PROPOSAL-trajectories.md +1582 -0
  133. package/docs/PROTOCOL.md +3 -3
  134. package/docs/SCALING_ANALYSIS.md +280 -0
  135. package/docs/TMUX_IMPLEMENTATION_NOTES.md +9 -9
  136. package/docs/TMUX_IMPROVEMENTS.md +968 -0
  137. package/docs/agent-relay-snippet.md +61 -0
  138. package/docs/competitive-analysis-mcp-agent-mail.md +389 -0
  139. package/docs/dashboard-v2-plan.md +179 -0
  140. package/package.json +10 -3
@@ -0,0 +1,999 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Metrics | Agent Relay</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --bg-deep: #0a0c0f;
13
+ --bg-panel: #0f1318;
14
+ --bg-card: #141a21;
15
+ --bg-elevated: #1a2129;
16
+
17
+ --border-dim: rgba(255, 255, 255, 0.06);
18
+ --border-subtle: rgba(255, 255, 255, 0.1);
19
+ --border-glow: rgba(0, 255, 200, 0.3);
20
+
21
+ --text-primary: #e8eaed;
22
+ --text-secondary: #9aa0a6;
23
+ --text-muted: #5f6368;
24
+
25
+ --accent-cyan: #00ffc8;
26
+ --accent-blue: #4a9eff;
27
+ --accent-purple: #b388ff;
28
+ --accent-orange: #ff9e40;
29
+ --accent-red: #ff5c5c;
30
+ --accent-green: #00e676;
31
+
32
+ --glow-cyan: 0 0 20px rgba(0, 255, 200, 0.3);
33
+ --glow-green: 0 0 15px rgba(0, 230, 118, 0.4);
34
+ --glow-red: 0 0 15px rgba(255, 92, 92, 0.4);
35
+
36
+ --font-display: 'Space Grotesk', sans-serif;
37
+ --font-mono: 'IBM Plex Mono', monospace;
38
+ }
39
+
40
+ *, *::before, *::after {
41
+ box-sizing: border-box;
42
+ margin: 0;
43
+ padding: 0;
44
+ }
45
+
46
+ html, body {
47
+ height: 100%;
48
+ overflow-x: hidden;
49
+ }
50
+
51
+ body {
52
+ font-family: var(--font-display);
53
+ background: var(--bg-deep);
54
+ color: var(--text-primary);
55
+ line-height: 1.5;
56
+ -webkit-font-smoothing: antialiased;
57
+ }
58
+
59
+ /* Subtle grid background */
60
+ body::before {
61
+ content: '';
62
+ position: fixed;
63
+ inset: 0;
64
+ background-image:
65
+ linear-gradient(rgba(0, 255, 200, 0.02) 1px, transparent 1px),
66
+ linear-gradient(90deg, rgba(0, 255, 200, 0.02) 1px, transparent 1px);
67
+ background-size: 40px 40px;
68
+ pointer-events: none;
69
+ z-index: 0;
70
+ }
71
+
72
+ /* Header */
73
+ .header {
74
+ position: sticky;
75
+ top: 0;
76
+ z-index: 100;
77
+ background: linear-gradient(to bottom, var(--bg-deep) 0%, transparent 100%);
78
+ padding: 24px 32px 48px;
79
+ }
80
+
81
+ .header-content {
82
+ max-width: 1400px;
83
+ margin: 0 auto;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ }
88
+
89
+ .header-left {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 16px;
93
+ }
94
+
95
+ .back-link {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 8px;
99
+ color: var(--text-secondary);
100
+ text-decoration: none;
101
+ font-size: 14px;
102
+ font-weight: 500;
103
+ padding: 8px 12px;
104
+ border-radius: 6px;
105
+ transition: all 0.2s;
106
+ }
107
+
108
+ .back-link:hover {
109
+ color: var(--accent-cyan);
110
+ background: rgba(0, 255, 200, 0.1);
111
+ }
112
+
113
+ .logo {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 12px;
117
+ }
118
+
119
+ .logo-icon {
120
+ width: 32px;
121
+ height: 32px;
122
+ background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
123
+ border-radius: 8px;
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ }
128
+
129
+ .logo-text {
130
+ font-size: 20px;
131
+ font-weight: 700;
132
+ letter-spacing: -0.5px;
133
+ }
134
+
135
+ .logo-text span {
136
+ color: var(--accent-cyan);
137
+ }
138
+
139
+ .live-indicator {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: 8px;
143
+ padding: 6px 12px;
144
+ background: rgba(0, 230, 118, 0.1);
145
+ border: 1px solid rgba(0, 230, 118, 0.3);
146
+ border-radius: 20px;
147
+ font-family: var(--font-mono);
148
+ font-size: 12px;
149
+ font-weight: 500;
150
+ color: var(--accent-green);
151
+ }
152
+
153
+ .live-dot {
154
+ width: 8px;
155
+ height: 8px;
156
+ background: var(--accent-green);
157
+ border-radius: 50%;
158
+ animation: pulse 2s ease-in-out infinite;
159
+ box-shadow: var(--glow-green);
160
+ }
161
+
162
+ @keyframes pulse {
163
+ 0%, 100% { opacity: 1; transform: scale(1); }
164
+ 50% { opacity: 0.5; transform: scale(0.9); }
165
+ }
166
+
167
+ /* Main content */
168
+ .main {
169
+ position: relative;
170
+ z-index: 1;
171
+ max-width: 1400px;
172
+ margin: 0 auto;
173
+ padding: 0 32px 48px;
174
+ }
175
+
176
+ /* Stats overview */
177
+ .stats-grid {
178
+ display: grid;
179
+ grid-template-columns: repeat(4, 1fr);
180
+ gap: 20px;
181
+ margin-bottom: 32px;
182
+ }
183
+
184
+ .stat-card {
185
+ background: var(--bg-card);
186
+ border: 1px solid var(--border-dim);
187
+ border-radius: 12px;
188
+ padding: 24px;
189
+ position: relative;
190
+ overflow: hidden;
191
+ transition: all 0.3s ease;
192
+ }
193
+
194
+ .stat-card::before {
195
+ content: '';
196
+ position: absolute;
197
+ top: 0;
198
+ left: 0;
199
+ right: 0;
200
+ height: 2px;
201
+ background: linear-gradient(90deg, transparent, var(--accent-cyan), transparent);
202
+ opacity: 0;
203
+ transition: opacity 0.3s;
204
+ }
205
+
206
+ .stat-card:hover {
207
+ border-color: var(--border-glow);
208
+ transform: translateY(-2px);
209
+ }
210
+
211
+ .stat-card:hover::before {
212
+ opacity: 1;
213
+ }
214
+
215
+ .stat-label {
216
+ font-size: 12px;
217
+ font-weight: 500;
218
+ text-transform: uppercase;
219
+ letter-spacing: 1px;
220
+ color: var(--text-muted);
221
+ margin-bottom: 8px;
222
+ }
223
+
224
+ .stat-value {
225
+ font-family: var(--font-mono);
226
+ font-size: 36px;
227
+ font-weight: 700;
228
+ color: var(--text-primary);
229
+ line-height: 1;
230
+ }
231
+
232
+ .stat-value.accent-cyan { color: var(--accent-cyan); }
233
+ .stat-value.accent-green { color: var(--accent-green); }
234
+ .stat-value.accent-orange { color: var(--accent-orange); }
235
+ .stat-value.accent-blue { color: var(--accent-blue); }
236
+
237
+ .stat-subtext {
238
+ font-size: 13px;
239
+ color: var(--text-secondary);
240
+ margin-top: 8px;
241
+ font-family: var(--font-mono);
242
+ }
243
+
244
+ /* Sections */
245
+ .section {
246
+ margin-bottom: 32px;
247
+ }
248
+
249
+ .section-header {
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: space-between;
253
+ margin-bottom: 16px;
254
+ }
255
+
256
+ .section-title {
257
+ font-size: 14px;
258
+ font-weight: 600;
259
+ text-transform: uppercase;
260
+ letter-spacing: 1.5px;
261
+ color: var(--text-secondary);
262
+ display: flex;
263
+ align-items: center;
264
+ gap: 10px;
265
+ }
266
+
267
+ .section-title::before {
268
+ content: '';
269
+ width: 3px;
270
+ height: 16px;
271
+ background: var(--accent-cyan);
272
+ border-radius: 2px;
273
+ }
274
+
275
+ /* Throughput panel */
276
+ .throughput-panel {
277
+ background: var(--bg-card);
278
+ border: 1px solid var(--border-dim);
279
+ border-radius: 12px;
280
+ padding: 28px;
281
+ }
282
+
283
+ .throughput-grid {
284
+ display: grid;
285
+ grid-template-columns: repeat(4, 1fr);
286
+ gap: 32px;
287
+ }
288
+
289
+ .throughput-item {
290
+ text-align: center;
291
+ }
292
+
293
+ .throughput-value {
294
+ font-family: var(--font-mono);
295
+ font-size: 48px;
296
+ font-weight: 700;
297
+ color: var(--accent-cyan);
298
+ line-height: 1;
299
+ text-shadow: var(--glow-cyan);
300
+ }
301
+
302
+ .throughput-label {
303
+ font-size: 13px;
304
+ color: var(--text-secondary);
305
+ margin-top: 8px;
306
+ text-transform: uppercase;
307
+ letter-spacing: 0.5px;
308
+ }
309
+
310
+ .throughput-bar {
311
+ height: 4px;
312
+ background: var(--bg-elevated);
313
+ border-radius: 2px;
314
+ margin-top: 12px;
315
+ overflow: hidden;
316
+ }
317
+
318
+ .throughput-bar-fill {
319
+ height: 100%;
320
+ background: linear-gradient(90deg, var(--accent-cyan), var(--accent-blue));
321
+ border-radius: 2px;
322
+ transition: width 0.5s ease;
323
+ }
324
+
325
+ /* Agent table */
326
+ .agents-table-container {
327
+ background: var(--bg-card);
328
+ border: 1px solid var(--border-dim);
329
+ border-radius: 12px;
330
+ overflow: hidden;
331
+ }
332
+
333
+ .agents-table {
334
+ width: 100%;
335
+ border-collapse: collapse;
336
+ }
337
+
338
+ .agents-table th,
339
+ .agents-table td {
340
+ padding: 16px 20px;
341
+ text-align: left;
342
+ }
343
+
344
+ .agents-table th {
345
+ background: var(--bg-elevated);
346
+ font-size: 11px;
347
+ font-weight: 600;
348
+ text-transform: uppercase;
349
+ letter-spacing: 1px;
350
+ color: var(--text-muted);
351
+ border-bottom: 1px solid var(--border-dim);
352
+ }
353
+
354
+ .agents-table tr {
355
+ border-bottom: 1px solid var(--border-dim);
356
+ transition: background 0.2s;
357
+ }
358
+
359
+ .agents-table tr:last-child {
360
+ border-bottom: none;
361
+ }
362
+
363
+ .agents-table tr:hover {
364
+ background: rgba(0, 255, 200, 0.02);
365
+ }
366
+
367
+ .agent-name {
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 12px;
371
+ }
372
+
373
+ .agent-avatar {
374
+ width: 32px;
375
+ height: 32px;
376
+ border-radius: 6px;
377
+ display: flex;
378
+ align-items: center;
379
+ justify-content: center;
380
+ font-weight: 600;
381
+ font-size: 12px;
382
+ color: white;
383
+ }
384
+
385
+ .agent-name-text {
386
+ font-weight: 600;
387
+ font-family: var(--font-mono);
388
+ }
389
+
390
+ .status-badge {
391
+ display: inline-flex;
392
+ align-items: center;
393
+ gap: 6px;
394
+ padding: 4px 10px;
395
+ border-radius: 12px;
396
+ font-size: 12px;
397
+ font-weight: 500;
398
+ }
399
+
400
+ .status-badge.online {
401
+ background: rgba(0, 230, 118, 0.15);
402
+ color: var(--accent-green);
403
+ }
404
+
405
+ .status-badge.offline {
406
+ background: rgba(255, 92, 92, 0.15);
407
+ color: var(--accent-red);
408
+ }
409
+
410
+ .status-badge::before {
411
+ content: '';
412
+ width: 6px;
413
+ height: 6px;
414
+ border-radius: 50%;
415
+ background: currentColor;
416
+ }
417
+
418
+ .metric-cell {
419
+ font-family: var(--font-mono);
420
+ font-size: 14px;
421
+ }
422
+
423
+ .metric-cell.sent {
424
+ color: var(--accent-blue);
425
+ }
426
+
427
+ .metric-cell.received {
428
+ color: var(--accent-purple);
429
+ }
430
+
431
+ .uptime-cell {
432
+ font-family: var(--font-mono);
433
+ font-size: 13px;
434
+ color: var(--text-secondary);
435
+ }
436
+
437
+ /* Session lifecycle panel */
438
+ .lifecycle-panel {
439
+ background: var(--bg-card);
440
+ border: 1px solid var(--border-dim);
441
+ border-radius: 12px;
442
+ padding: 28px;
443
+ }
444
+
445
+ .lifecycle-grid {
446
+ display: grid;
447
+ grid-template-columns: repeat(5, 1fr);
448
+ gap: 24px;
449
+ }
450
+
451
+ .lifecycle-item {
452
+ text-align: center;
453
+ }
454
+
455
+ .lifecycle-value {
456
+ font-family: var(--font-mono);
457
+ font-size: 36px;
458
+ font-weight: 700;
459
+ line-height: 1;
460
+ }
461
+
462
+ .lifecycle-value.accent-green { color: var(--accent-green); }
463
+ .lifecycle-value.accent-blue { color: var(--accent-blue); }
464
+ .lifecycle-value.accent-orange { color: var(--accent-orange); }
465
+ .lifecycle-value.accent-red { color: var(--accent-red); }
466
+ .lifecycle-value.accent-purple { color: var(--accent-purple); }
467
+
468
+ .lifecycle-label {
469
+ font-size: 12px;
470
+ color: var(--text-secondary);
471
+ margin-top: 8px;
472
+ text-transform: uppercase;
473
+ letter-spacing: 0.5px;
474
+ }
475
+
476
+ .error-rate-indicator {
477
+ display: inline-flex;
478
+ align-items: center;
479
+ gap: 6px;
480
+ padding: 4px 10px;
481
+ border-radius: 12px;
482
+ font-size: 12px;
483
+ font-weight: 600;
484
+ font-family: var(--font-mono);
485
+ }
486
+
487
+ .error-rate-indicator.healthy {
488
+ background: rgba(0, 230, 118, 0.15);
489
+ color: var(--accent-green);
490
+ }
491
+
492
+ .error-rate-indicator.warning {
493
+ background: rgba(255, 158, 64, 0.15);
494
+ color: var(--accent-orange);
495
+ }
496
+
497
+ .error-rate-indicator.critical {
498
+ background: rgba(255, 92, 92, 0.15);
499
+ color: var(--accent-red);
500
+ }
501
+
502
+ .sessions-table {
503
+ width: 100%;
504
+ border-collapse: collapse;
505
+ margin-top: 20px;
506
+ }
507
+
508
+ .sessions-table th,
509
+ .sessions-table td {
510
+ padding: 12px 16px;
511
+ text-align: left;
512
+ }
513
+
514
+ .sessions-table th {
515
+ background: var(--bg-elevated);
516
+ font-size: 11px;
517
+ font-weight: 600;
518
+ text-transform: uppercase;
519
+ letter-spacing: 1px;
520
+ color: var(--text-muted);
521
+ border-bottom: 1px solid var(--border-dim);
522
+ }
523
+
524
+ .sessions-table tr {
525
+ border-bottom: 1px solid var(--border-dim);
526
+ }
527
+
528
+ .sessions-table tr:last-child {
529
+ border-bottom: none;
530
+ }
531
+
532
+ .closed-badge {
533
+ display: inline-flex;
534
+ align-items: center;
535
+ gap: 4px;
536
+ padding: 2px 8px;
537
+ border-radius: 10px;
538
+ font-size: 11px;
539
+ font-weight: 500;
540
+ }
541
+
542
+ .closed-badge.agent {
543
+ background: rgba(0, 230, 118, 0.15);
544
+ color: var(--accent-green);
545
+ }
546
+
547
+ .closed-badge.disconnect {
548
+ background: rgba(255, 158, 64, 0.15);
549
+ color: var(--accent-orange);
550
+ }
551
+
552
+ .closed-badge.error {
553
+ background: rgba(255, 92, 92, 0.15);
554
+ color: var(--accent-red);
555
+ }
556
+
557
+ .closed-badge.active {
558
+ background: rgba(74, 158, 255, 0.15);
559
+ color: var(--accent-blue);
560
+ }
561
+
562
+ @media (max-width: 1200px) {
563
+ .lifecycle-grid {
564
+ grid-template-columns: repeat(3, 1fr);
565
+ }
566
+ }
567
+
568
+ @media (max-width: 768px) {
569
+ .lifecycle-grid {
570
+ grid-template-columns: repeat(2, 1fr);
571
+ }
572
+ }
573
+
574
+ /* Empty state */
575
+ .empty-state {
576
+ padding: 64px 32px;
577
+ text-align: center;
578
+ }
579
+
580
+ .empty-state-icon {
581
+ width: 64px;
582
+ height: 64px;
583
+ margin: 0 auto 16px;
584
+ color: var(--text-muted);
585
+ opacity: 0.5;
586
+ }
587
+
588
+ .empty-state-text {
589
+ color: var(--text-muted);
590
+ font-size: 14px;
591
+ }
592
+
593
+ /* Last updated */
594
+ .last-updated {
595
+ text-align: center;
596
+ padding: 24px;
597
+ font-size: 12px;
598
+ color: var(--text-muted);
599
+ font-family: var(--font-mono);
600
+ }
601
+
602
+ /* Loading state */
603
+ .loading {
604
+ display: flex;
605
+ align-items: center;
606
+ justify-content: center;
607
+ padding: 64px;
608
+ }
609
+
610
+ .spinner {
611
+ width: 32px;
612
+ height: 32px;
613
+ border: 2px solid var(--border-subtle);
614
+ border-top-color: var(--accent-cyan);
615
+ border-radius: 50%;
616
+ animation: spin 1s linear infinite;
617
+ }
618
+
619
+ @keyframes spin {
620
+ to { transform: rotate(360deg); }
621
+ }
622
+
623
+ /* Responsive */
624
+ @media (max-width: 1200px) {
625
+ .stats-grid {
626
+ grid-template-columns: repeat(2, 1fr);
627
+ }
628
+ .throughput-grid {
629
+ grid-template-columns: repeat(2, 1fr);
630
+ gap: 24px;
631
+ }
632
+ }
633
+
634
+ @media (max-width: 768px) {
635
+ .header {
636
+ padding: 16px 20px 32px;
637
+ }
638
+ .main {
639
+ padding: 0 20px 32px;
640
+ }
641
+ .stats-grid {
642
+ grid-template-columns: 1fr;
643
+ }
644
+ .throughput-grid {
645
+ grid-template-columns: 1fr;
646
+ }
647
+ .agents-table th,
648
+ .agents-table td {
649
+ padding: 12px;
650
+ }
651
+ }
652
+
653
+ /* Animation on load */
654
+ .stat-card, .throughput-panel, .lifecycle-panel, .agents-table-container {
655
+ animation: fadeIn 0.5s ease forwards;
656
+ opacity: 0;
657
+ }
658
+
659
+ .stat-card:nth-child(1) { animation-delay: 0.1s; }
660
+ .stat-card:nth-child(2) { animation-delay: 0.15s; }
661
+ .stat-card:nth-child(3) { animation-delay: 0.2s; }
662
+ .stat-card:nth-child(4) { animation-delay: 0.25s; }
663
+ .throughput-panel { animation-delay: 0.3s; }
664
+ .lifecycle-panel { animation-delay: 0.35s; }
665
+ .agents-table-container { animation-delay: 0.45s; }
666
+
667
+ @keyframes fadeIn {
668
+ from {
669
+ opacity: 0;
670
+ transform: translateY(10px);
671
+ }
672
+ to {
673
+ opacity: 1;
674
+ transform: translateY(0);
675
+ }
676
+ }
677
+ </style>
678
+ </head>
679
+ <body>
680
+ <header class="header">
681
+ <div class="header-content">
682
+ <div class="header-left">
683
+ <a href="/" class="back-link">
684
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
685
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
686
+ </svg>
687
+ Dashboard
688
+ </a>
689
+ <div class="logo">
690
+ <div class="logo-icon">
691
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
692
+ <path d="M3 3v18h18"/>
693
+ <path d="M18 17V9"/>
694
+ <path d="M13 17V5"/>
695
+ <path d="M8 17v-3"/>
696
+ </svg>
697
+ </div>
698
+ <div class="logo-text">Agent <span>Metrics</span></div>
699
+ </div>
700
+ </div>
701
+ <div class="live-indicator">
702
+ <span class="live-dot"></span>
703
+ LIVE
704
+ </div>
705
+ </div>
706
+ </header>
707
+
708
+ <main class="main">
709
+ <div id="content">
710
+ <div class="loading">
711
+ <div class="spinner"></div>
712
+ </div>
713
+ </div>
714
+ </main>
715
+
716
+ <script>
717
+ const REFRESH_INTERVAL = 5000;
718
+ const COLORS = ['#4a9eff', '#b388ff', '#ff9e40', '#00e676', '#ff5c5c', '#00ffc8'];
719
+
720
+ function getAvatarColor(name) {
721
+ let hash = 0;
722
+ for (let i = 0; i < name.length; i++) {
723
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
724
+ }
725
+ return COLORS[Math.abs(hash) % COLORS.length];
726
+ }
727
+
728
+ function getInitials(name) {
729
+ return name.slice(0, 2).toUpperCase();
730
+ }
731
+
732
+ function formatDuration(seconds) {
733
+ if (seconds < 60) return `${seconds}s`;
734
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
735
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
736
+ return `${Math.floor(seconds / 86400)}d ${Math.floor((seconds % 86400) / 3600)}h`;
737
+ }
738
+
739
+ function formatTime(isoString) {
740
+ return new Date(isoString).toLocaleTimeString('en-US', {
741
+ hour: '2-digit',
742
+ minute: '2-digit',
743
+ second: '2-digit',
744
+ hour12: false
745
+ });
746
+ }
747
+
748
+ function getErrorRateClass(rate) {
749
+ if (rate <= 1) return 'healthy';
750
+ if (rate <= 5) return 'warning';
751
+ return 'critical';
752
+ }
753
+
754
+ function getClosedByLabel(closedBy) {
755
+ if (!closedBy) return 'Active';
756
+ switch (closedBy) {
757
+ case 'agent': return 'Clean';
758
+ case 'disconnect': return 'Disconnect';
759
+ case 'error': return 'Error';
760
+ default: return closedBy;
761
+ }
762
+ }
763
+
764
+ function render(metrics) {
765
+ const content = document.getElementById('content');
766
+
767
+ // Calculate max throughput for bar scaling
768
+ const maxThroughput = Math.max(
769
+ metrics.throughput.messagesLastMinute * 60,
770
+ metrics.throughput.messagesLastHour,
771
+ metrics.throughput.messagesLast24Hours / 24,
772
+ 1
773
+ );
774
+
775
+ content.innerHTML = `
776
+ <!-- Stats Overview -->
777
+ <div class="stats-grid">
778
+ <div class="stat-card">
779
+ <div class="stat-label">Total Agents</div>
780
+ <div class="stat-value accent-cyan">${metrics.totalAgents}</div>
781
+ <div class="stat-subtext">${metrics.onlineAgents} online / ${metrics.offlineAgents} offline</div>
782
+ </div>
783
+ <div class="stat-card">
784
+ <div class="stat-label">Online Now</div>
785
+ <div class="stat-value accent-green">${metrics.onlineAgents}</div>
786
+ <div class="stat-subtext">${metrics.totalAgents > 0 ? Math.round((metrics.onlineAgents / metrics.totalAgents) * 100) : 0}% availability</div>
787
+ </div>
788
+ <div class="stat-card">
789
+ <div class="stat-label">Total Messages</div>
790
+ <div class="stat-value accent-blue">${metrics.totalMessages.toLocaleString()}</div>
791
+ <div class="stat-subtext">all time</div>
792
+ </div>
793
+ <div class="stat-card">
794
+ <div class="stat-label">Avg. Throughput</div>
795
+ <div class="stat-value accent-orange">${metrics.throughput.avgMessagesPerMinute}</div>
796
+ <div class="stat-subtext">messages / minute</div>
797
+ </div>
798
+ </div>
799
+
800
+ <!-- Throughput Section -->
801
+ <section class="section">
802
+ <div class="section-header">
803
+ <h2 class="section-title">Message Throughput</h2>
804
+ </div>
805
+ <div class="throughput-panel">
806
+ <div class="throughput-grid">
807
+ <div class="throughput-item">
808
+ <div class="throughput-value">${metrics.throughput.messagesLastMinute}</div>
809
+ <div class="throughput-label">Last Minute</div>
810
+ <div class="throughput-bar">
811
+ <div class="throughput-bar-fill" style="width: ${Math.min((metrics.throughput.messagesLastMinute / 10) * 100, 100)}%"></div>
812
+ </div>
813
+ </div>
814
+ <div class="throughput-item">
815
+ <div class="throughput-value">${metrics.throughput.messagesLastHour}</div>
816
+ <div class="throughput-label">Last Hour</div>
817
+ <div class="throughput-bar">
818
+ <div class="throughput-bar-fill" style="width: ${Math.min((metrics.throughput.messagesLastHour / 100) * 100, 100)}%"></div>
819
+ </div>
820
+ </div>
821
+ <div class="throughput-item">
822
+ <div class="throughput-value">${metrics.throughput.messagesLast24Hours}</div>
823
+ <div class="throughput-label">Last 24 Hours</div>
824
+ <div class="throughput-bar">
825
+ <div class="throughput-bar-fill" style="width: ${Math.min((metrics.throughput.messagesLast24Hours / 1000) * 100, 100)}%"></div>
826
+ </div>
827
+ </div>
828
+ <div class="throughput-item">
829
+ <div class="throughput-value">${metrics.throughput.avgMessagesPerMinute}</div>
830
+ <div class="throughput-label">Avg / Min</div>
831
+ <div class="throughput-bar">
832
+ <div class="throughput-bar-fill" style="width: ${Math.min((metrics.throughput.avgMessagesPerMinute / 5) * 100, 100)}%"></div>
833
+ </div>
834
+ </div>
835
+ </div>
836
+ </div>
837
+ </section>
838
+
839
+ <!-- Session Lifecycle Section -->
840
+ <section class="section">
841
+ <div class="section-header">
842
+ <h2 class="section-title">Session Lifecycle</h2>
843
+ <span class="error-rate-indicator ${getErrorRateClass(metrics.sessions?.errorRate || 0)}">
844
+ ${(metrics.sessions?.errorRate || 0).toFixed(1)}% error rate
845
+ </span>
846
+ </div>
847
+ <div class="lifecycle-panel">
848
+ <div class="lifecycle-grid">
849
+ <div class="lifecycle-item">
850
+ <div class="lifecycle-value accent-purple">${metrics.sessions?.totalSessions || 0}</div>
851
+ <div class="lifecycle-label">Total Sessions</div>
852
+ </div>
853
+ <div class="lifecycle-item">
854
+ <div class="lifecycle-value accent-blue">${metrics.sessions?.activeSessions || 0}</div>
855
+ <div class="lifecycle-label">Active</div>
856
+ </div>
857
+ <div class="lifecycle-item">
858
+ <div class="lifecycle-value accent-green">${metrics.sessions?.closedByAgent || 0}</div>
859
+ <div class="lifecycle-label">Clean Close</div>
860
+ </div>
861
+ <div class="lifecycle-item">
862
+ <div class="lifecycle-value accent-orange">${metrics.sessions?.closedByDisconnect || 0}</div>
863
+ <div class="lifecycle-label">Disconnect</div>
864
+ </div>
865
+ <div class="lifecycle-item">
866
+ <div class="lifecycle-value accent-red">${metrics.sessions?.closedByError || 0}</div>
867
+ <div class="lifecycle-label">Error</div>
868
+ </div>
869
+ </div>
870
+ ${metrics.sessions?.recentSessions?.length > 0 ? `
871
+ <table class="sessions-table">
872
+ <thead>
873
+ <tr>
874
+ <th>Agent</th>
875
+ <th>Status</th>
876
+ <th>Messages</th>
877
+ <th>Started</th>
878
+ <th>Duration</th>
879
+ </tr>
880
+ </thead>
881
+ <tbody>
882
+ ${metrics.sessions.recentSessions.slice(0, 5).map(session => {
883
+ const started = new Date(session.startedAt);
884
+ const ended = session.endedAt ? new Date(session.endedAt) : new Date();
885
+ const durationSec = Math.floor((ended - started) / 1000);
886
+ const closedClass = session.closedBy || 'active';
887
+ return `
888
+ <tr>
889
+ <td>
890
+ <div class="agent-name">
891
+ <div class="agent-avatar" style="background: ${getAvatarColor(session.agentName)}">
892
+ ${getInitials(session.agentName)}
893
+ </div>
894
+ <span class="agent-name-text">${session.agentName}</span>
895
+ </div>
896
+ </td>
897
+ <td>
898
+ <span class="closed-badge ${closedClass}">
899
+ ${getClosedByLabel(session.closedBy)}
900
+ </span>
901
+ </td>
902
+ <td class="metric-cell">${session.messageCount}</td>
903
+ <td class="uptime-cell">${formatTime(session.startedAt)}</td>
904
+ <td class="uptime-cell">${formatDuration(durationSec)}</td>
905
+ </tr>
906
+ `;
907
+ }).join('')}
908
+ </tbody>
909
+ </table>
910
+ ` : ''}
911
+ </div>
912
+ </section>
913
+
914
+ <!-- Agents Section -->
915
+ <section class="section">
916
+ <div class="section-header">
917
+ <h2 class="section-title">Agent Health</h2>
918
+ </div>
919
+ <div class="agents-table-container">
920
+ ${metrics.agents.length === 0 ? `
921
+ <div class="empty-state">
922
+ <svg class="empty-state-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
923
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
924
+ <circle cx="12" cy="7" r="4"/>
925
+ </svg>
926
+ <p class="empty-state-text">No agents registered yet</p>
927
+ </div>
928
+ ` : `
929
+ <table class="agents-table">
930
+ <thead>
931
+ <tr>
932
+ <th>Agent</th>
933
+ <th>Status</th>
934
+ <th>Messages Sent</th>
935
+ <th>Messages Received</th>
936
+ <th>Uptime</th>
937
+ <th>Last Seen</th>
938
+ </tr>
939
+ </thead>
940
+ <tbody>
941
+ ${metrics.agents.map(agent => `
942
+ <tr>
943
+ <td>
944
+ <div class="agent-name">
945
+ <div class="agent-avatar" style="background: ${getAvatarColor(agent.name)}">
946
+ ${getInitials(agent.name)}
947
+ </div>
948
+ <span class="agent-name-text">${agent.name}</span>
949
+ </div>
950
+ </td>
951
+ <td>
952
+ <span class="status-badge ${agent.isOnline ? 'online' : 'offline'}">
953
+ ${agent.isOnline ? 'Online' : 'Offline'}
954
+ </span>
955
+ </td>
956
+ <td class="metric-cell sent">${agent.messagesSent.toLocaleString()}</td>
957
+ <td class="metric-cell received">${agent.messagesReceived.toLocaleString()}</td>
958
+ <td class="uptime-cell">${formatDuration(agent.uptimeSeconds)}</td>
959
+ <td class="uptime-cell">${formatTime(agent.lastSeen)}</td>
960
+ </tr>
961
+ `).join('')}
962
+ </tbody>
963
+ </table>
964
+ `}
965
+ </div>
966
+ </section>
967
+
968
+ <div class="last-updated">
969
+ Last updated: ${formatTime(metrics.timestamp)}
970
+ </div>
971
+ `;
972
+ }
973
+
974
+ async function fetchMetrics() {
975
+ try {
976
+ const response = await fetch('/api/metrics');
977
+ if (!response.ok) throw new Error('Failed to fetch metrics');
978
+ const metrics = await response.json();
979
+ render(metrics);
980
+ } catch (err) {
981
+ console.error('Error fetching metrics:', err);
982
+ document.getElementById('content').innerHTML = `
983
+ <div class="empty-state">
984
+ <svg class="empty-state-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
985
+ <circle cx="12" cy="12" r="10"/>
986
+ <path d="M12 8v4M12 16h.01"/>
987
+ </svg>
988
+ <p class="empty-state-text">Failed to load metrics. Retrying...</p>
989
+ </div>
990
+ `;
991
+ }
992
+ }
993
+
994
+ // Initial fetch and set up refresh
995
+ fetchMetrics();
996
+ setInterval(fetchMetrics, REFRESH_INTERVAL);
997
+ </script>
998
+ </body>
999
+ </html>