@agent-relay/dashboard-server 0.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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Health Worker Manager
3
+ *
4
+ * Manages the health check worker thread, sending periodic stats updates
5
+ * and handling worker lifecycle.
6
+ */
7
+ export interface HealthWorkerConfig {
8
+ /** Port for health check server (default: main port + 1) */
9
+ port: number;
10
+ /** Interval for sending stats updates (default: 5000ms) */
11
+ statsInterval?: number;
12
+ }
13
+ export interface HealthStatsProvider {
14
+ getUptime: () => number;
15
+ getMemoryMB: () => number;
16
+ getRelayConnected: () => boolean;
17
+ getAgentCount: () => number;
18
+ getStatus: () => 'healthy' | 'busy' | 'degraded';
19
+ }
20
+ export declare class HealthWorkerManager {
21
+ private worker;
22
+ private statsInterval;
23
+ private config;
24
+ private statsProvider;
25
+ private ready;
26
+ constructor(config: HealthWorkerConfig, statsProvider: HealthStatsProvider);
27
+ /**
28
+ * Start the health worker thread
29
+ */
30
+ start(): Promise<void>;
31
+ /**
32
+ * Stop the health worker thread
33
+ */
34
+ stop(): Promise<void>;
35
+ /**
36
+ * Check if worker is ready
37
+ */
38
+ isReady(): boolean;
39
+ /**
40
+ * Get the port the health worker is listening on
41
+ */
42
+ getPort(): number;
43
+ /**
44
+ * Start periodic stats updates to worker
45
+ */
46
+ private startStatsUpdates;
47
+ /**
48
+ * Stop stats updates
49
+ */
50
+ private stopStatsUpdates;
51
+ /**
52
+ * Send current stats to worker
53
+ */
54
+ private sendStats;
55
+ }
56
+ /** Default health port offset from main port */
57
+ export declare const HEALTH_PORT_OFFSET = 1;
58
+ /**
59
+ * Calculate health port from main port
60
+ */
61
+ export declare function getHealthPort(mainPort: number): number;
62
+ //# sourceMappingURL=health-worker-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-worker-manager.d.ts","sourceRoot":"","sources":["../src/health-worker-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,OAAO,CAAC;IACjC,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;CAClD;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,kBAAkB,EAAE,aAAa,EAAE,mBAAmB;IAQ1E;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgD5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;OAEG;IACH,OAAO,CAAC,SAAS;CAiBlB;AAED,gDAAgD;AAChD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Health Worker Manager
3
+ *
4
+ * Manages the health check worker thread, sending periodic stats updates
5
+ * and handling worker lifecycle.
6
+ */
7
+ import { Worker } from 'node:worker_threads';
8
+ import path from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ export class HealthWorkerManager {
13
+ worker = null;
14
+ statsInterval = null;
15
+ config;
16
+ statsProvider;
17
+ ready = false;
18
+ constructor(config, statsProvider) {
19
+ this.config = {
20
+ statsInterval: 5000,
21
+ ...config,
22
+ };
23
+ this.statsProvider = statsProvider;
24
+ }
25
+ /**
26
+ * Start the health worker thread
27
+ */
28
+ async start() {
29
+ if (this.worker) {
30
+ console.warn('[health-manager] Worker already running');
31
+ return;
32
+ }
33
+ return new Promise((resolve, reject) => {
34
+ // Worker script path - handle both dev (src) and prod (dist)
35
+ const workerPath = path.join(__dirname, 'health-worker.js');
36
+ this.worker = new Worker(workerPath, {
37
+ workerData: { port: this.config.port },
38
+ });
39
+ this.worker.on('message', (msg) => {
40
+ if (msg.type === 'ready') {
41
+ this.ready = true;
42
+ console.log(`[health-manager] Worker ready on port ${msg.port}`);
43
+ this.startStatsUpdates();
44
+ resolve();
45
+ }
46
+ else if (msg.type === 'error') {
47
+ console.error('[health-manager] Worker error:', msg.error);
48
+ }
49
+ });
50
+ this.worker.on('error', (err) => {
51
+ console.error('[health-manager] Worker thread error:', err);
52
+ if (!this.ready) {
53
+ reject(err);
54
+ }
55
+ });
56
+ this.worker.on('exit', (code) => {
57
+ console.log(`[health-manager] Worker exited with code ${code}`);
58
+ this.ready = false;
59
+ this.worker = null;
60
+ this.stopStatsUpdates();
61
+ });
62
+ // Timeout for worker startup
63
+ setTimeout(() => {
64
+ if (!this.ready) {
65
+ reject(new Error('Health worker startup timeout'));
66
+ }
67
+ }, 10000);
68
+ });
69
+ }
70
+ /**
71
+ * Stop the health worker thread
72
+ */
73
+ async stop() {
74
+ this.stopStatsUpdates();
75
+ if (this.worker) {
76
+ await this.worker.terminate();
77
+ this.worker = null;
78
+ this.ready = false;
79
+ }
80
+ }
81
+ /**
82
+ * Check if worker is ready
83
+ */
84
+ isReady() {
85
+ return this.ready;
86
+ }
87
+ /**
88
+ * Get the port the health worker is listening on
89
+ */
90
+ getPort() {
91
+ return this.config.port;
92
+ }
93
+ /**
94
+ * Start periodic stats updates to worker
95
+ */
96
+ startStatsUpdates() {
97
+ if (this.statsInterval)
98
+ return;
99
+ // Send initial stats
100
+ this.sendStats();
101
+ // Send periodic updates
102
+ this.statsInterval = setInterval(() => {
103
+ this.sendStats();
104
+ }, this.config.statsInterval);
105
+ }
106
+ /**
107
+ * Stop stats updates
108
+ */
109
+ stopStatsUpdates() {
110
+ if (this.statsInterval) {
111
+ clearInterval(this.statsInterval);
112
+ this.statsInterval = null;
113
+ }
114
+ }
115
+ /**
116
+ * Send current stats to worker
117
+ */
118
+ sendStats() {
119
+ if (!this.worker || !this.ready)
120
+ return;
121
+ try {
122
+ const stats = {
123
+ uptime: this.statsProvider.getUptime(),
124
+ memoryMB: this.statsProvider.getMemoryMB(),
125
+ relayConnected: this.statsProvider.getRelayConnected(),
126
+ agentCount: this.statsProvider.getAgentCount(),
127
+ status: this.statsProvider.getStatus(),
128
+ };
129
+ this.worker.postMessage(stats);
130
+ }
131
+ catch (err) {
132
+ console.error('[health-manager] Failed to send stats:', err);
133
+ }
134
+ }
135
+ }
136
+ /** Default health port offset from main port */
137
+ export const HEALTH_PORT_OFFSET = 1;
138
+ /**
139
+ * Calculate health port from main port
140
+ */
141
+ export function getHealthPort(mainPort) {
142
+ return mainPort + HEALTH_PORT_OFFSET;
143
+ }
144
+ //# sourceMappingURL=health-worker-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-worker-manager.js","sourceRoot":"","sources":["../src/health-worker-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Health Check Worker Thread
3
+ *
4
+ * Runs a minimal HTTP server on a separate thread to handle health checks.
5
+ * This ensures health checks respond even when the main event loop is blocked
6
+ * by heavy compute tasks (builds, large file operations, etc.).
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=health-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-worker.d.ts","sourceRoot":"","sources":["../src/health-worker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Health Check Worker Thread
3
+ *
4
+ * Runs a minimal HTTP server on a separate thread to handle health checks.
5
+ * This ensures health checks respond even when the main event loop is blocked
6
+ * by heavy compute tasks (builds, large file operations, etc.).
7
+ */
8
+ import { parentPort, workerData } from 'node:worker_threads';
9
+ import http from 'node:http';
10
+ // Default stats until we receive updates from main thread
11
+ let currentStats = {
12
+ uptime: 0,
13
+ memoryMB: 0,
14
+ relayConnected: false,
15
+ agentCount: 0,
16
+ status: 'healthy',
17
+ };
18
+ // Track when we last received stats from main thread
19
+ let lastStatsUpdate = Date.now();
20
+ const STATS_STALE_THRESHOLD_MS = 60_000; // 1 minute
21
+ // Listen for stats updates from main thread
22
+ if (parentPort) {
23
+ parentPort.on('message', (stats) => {
24
+ currentStats = stats;
25
+ lastStatsUpdate = Date.now();
26
+ });
27
+ }
28
+ const { port } = workerData;
29
+ const server = http.createServer((req, res) => {
30
+ // Only handle /health endpoint
31
+ if (req.url !== '/health' && req.url !== '/') {
32
+ res.writeHead(404, { 'Content-Type': 'application/json' });
33
+ res.end(JSON.stringify({ error: 'Not found' }));
34
+ return;
35
+ }
36
+ // Check if stats are stale (main thread might be blocked)
37
+ const statsAge = Date.now() - lastStatsUpdate;
38
+ const isStale = statsAge > STATS_STALE_THRESHOLD_MS;
39
+ // Determine status
40
+ let status = currentStats.status;
41
+ if (isStale) {
42
+ status = 'busy'; // Main thread is likely blocked
43
+ }
44
+ const response = {
45
+ status,
46
+ uptime: currentStats.uptime,
47
+ memoryMB: currentStats.memoryMB,
48
+ relayConnected: currentStats.relayConnected,
49
+ agentCount: currentStats.agentCount,
50
+ statsAgeMs: statsAge,
51
+ worker: true, // Indicates response is from worker thread
52
+ };
53
+ // Return 200 for healthy/busy, 503 for degraded
54
+ const statusCode = status === 'degraded' ? 503 : 200;
55
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
56
+ res.end(JSON.stringify(response));
57
+ });
58
+ server.listen(port, '0.0.0.0', () => {
59
+ console.log(`[health-worker] Health check server listening on port ${port}`);
60
+ // Notify main thread we're ready
61
+ if (parentPort) {
62
+ parentPort.postMessage({ type: 'ready', port });
63
+ }
64
+ });
65
+ // Handle errors gracefully
66
+ server.on('error', (err) => {
67
+ console.error('[health-worker] Server error:', err);
68
+ if (parentPort) {
69
+ parentPort.postMessage({ type: 'error', error: String(err) });
70
+ }
71
+ });
72
+ // Keep the worker alive
73
+ process.on('uncaughtException', (err) => {
74
+ console.error('[health-worker] Uncaught exception:', err);
75
+ });
76
+ process.on('unhandledRejection', (err) => {
77
+ console.error('[health-worker] Unhandled rejection:', err);
78
+ });
79
+ //# sourceMappingURL=health-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-worker.js","sourceRoot":"","sources":["../src/health-worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @relay/dashboard-server
3
+ *
4
+ * HTTP/WebSocket server for agent coordination and dashboard UI.
5
+ *
6
+ * This package provides:
7
+ * - Dashboard HTTP API for agent management
8
+ * - WebSocket connections for real-time updates
9
+ * - User bridge for browser-based users
10
+ * - Metrics collection and reporting
11
+ */
12
+ export { startDashboard } from './server.js';
13
+ export { UserBridge, type IRelayClient } from './user-bridge.js';
14
+ export { computeNeedsAttention, type AttentionMessage } from './needs-attention.js';
15
+ export { computeSystemMetrics, formatPrometheusMetrics, type AgentMetrics, type ThroughputMetrics, } from './metrics.js';
16
+ export { HealthWorkerManager, getHealthPort, type HealthWorkerConfig, type HealthStatsProvider } from './health-worker-manager.js';
17
+ export type { ThreadMetadata } from './types/threading.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnI,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @relay/dashboard-server
3
+ *
4
+ * HTTP/WebSocket server for agent coordination and dashboard UI.
5
+ *
6
+ * This package provides:
7
+ * - Dashboard HTTP API for agent management
8
+ * - WebSocket connections for real-time updates
9
+ * - User bridge for browser-based users
10
+ * - Metrics collection and reporting
11
+ */
12
+ export { startDashboard } from './server.js';
13
+ export { UserBridge } from './user-bridge.js';
14
+ export { computeNeedsAttention } from './needs-attention.js';
15
+ export { computeSystemMetrics, formatPrometheusMetrics, } from './metrics.js';
16
+ export { HealthWorkerManager, getHealthPort } from './health-worker-manager.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAqB,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAyB,MAAM,sBAAsB,CAAC;AACpF,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GAGxB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAqD,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Metrics collection and computation for the Agent Relay dashboard.
3
+ * Provides real-time metrics for monitoring: throughput, agent health, error rates.
4
+ */
5
+ export interface AgentMetrics {
6
+ name: string;
7
+ messagesSent: number;
8
+ messagesReceived: number;
9
+ firstSeen: string;
10
+ lastSeen: string;
11
+ isOnline: boolean;
12
+ uptimeSeconds: number;
13
+ }
14
+ export interface ThroughputMetrics {
15
+ messagesLastMinute: number;
16
+ messagesLastHour: number;
17
+ messagesLast24Hours: number;
18
+ avgMessagesPerMinute: number;
19
+ }
20
+ export interface SessionMetrics {
21
+ totalSessions: number;
22
+ activeSessions: number;
23
+ closedByAgent: number;
24
+ closedByDisconnect: number;
25
+ closedByError: number;
26
+ errorRate: number;
27
+ recentSessions: Array<{
28
+ id: string;
29
+ agentName: string;
30
+ startedAt: string;
31
+ endedAt?: string;
32
+ closedBy?: 'agent' | 'disconnect' | 'error';
33
+ messageCount: number;
34
+ }>;
35
+ }
36
+ export interface SystemMetrics {
37
+ totalAgents: number;
38
+ onlineAgents: number;
39
+ offlineAgents: number;
40
+ totalMessages: number;
41
+ throughput: ThroughputMetrics;
42
+ sessions: SessionMetrics;
43
+ agents: AgentMetrics[];
44
+ timestamp: string;
45
+ }
46
+ export interface PrometheusMetric {
47
+ name: string;
48
+ help: string;
49
+ type: 'counter' | 'gauge' | 'histogram';
50
+ values: Array<{
51
+ labels: Record<string, string>;
52
+ value: number;
53
+ }>;
54
+ }
55
+ /**
56
+ * Compute agent-level metrics from registry data
57
+ */
58
+ export declare function computeAgentMetrics(agents: Array<{
59
+ name: string;
60
+ messagesSent: number;
61
+ messagesReceived: number;
62
+ firstSeen: string;
63
+ lastSeen: string;
64
+ }>): AgentMetrics[];
65
+ /**
66
+ * Compute throughput metrics from message history
67
+ */
68
+ export declare function computeThroughputMetrics(messages: Array<{
69
+ timestamp: string;
70
+ }>): ThroughputMetrics;
71
+ /**
72
+ * Compute session lifecycle metrics from session history
73
+ */
74
+ export declare function computeSessionMetrics(sessions: Array<{
75
+ id: string;
76
+ agentName: string;
77
+ startedAt: number;
78
+ endedAt?: number;
79
+ closedBy?: 'agent' | 'disconnect' | 'error';
80
+ messageCount: number;
81
+ }>): SessionMetrics;
82
+ /**
83
+ * Compute full system metrics
84
+ */
85
+ export declare function computeSystemMetrics(agents: Array<{
86
+ name: string;
87
+ messagesSent: number;
88
+ messagesReceived: number;
89
+ firstSeen: string;
90
+ lastSeen: string;
91
+ }>, messages: Array<{
92
+ timestamp: string;
93
+ }>, sessions?: Array<{
94
+ id: string;
95
+ agentName: string;
96
+ startedAt: number;
97
+ endedAt?: number;
98
+ closedBy?: 'agent' | 'disconnect' | 'error';
99
+ messageCount: number;
100
+ }>): SystemMetrics;
101
+ /**
102
+ * Format metrics in Prometheus exposition format
103
+ */
104
+ export declare function formatPrometheusMetrics(metrics: SystemMetrics): string;
105
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Metrics collection and computation for the Agent Relay dashboard.
3
+ * Provides real-time metrics for monitoring: throughput, agent health, error rates.
4
+ */
5
+ // Consider agents offline after 30 seconds of inactivity
6
+ // Aligns with heartbeat timeout (5s heartbeat * 6 multiplier = 30s)
7
+ const OFFLINE_THRESHOLD_MS = 30 * 1000;
8
+ /**
9
+ * Compute agent-level metrics from registry data
10
+ */
11
+ export function computeAgentMetrics(agents) {
12
+ const now = Date.now();
13
+ return agents.map((agent) => {
14
+ const lastSeenTime = new Date(agent.lastSeen).getTime();
15
+ const firstSeenTime = new Date(agent.firstSeen).getTime();
16
+ const isOnline = now - lastSeenTime < OFFLINE_THRESHOLD_MS;
17
+ const uptimeSeconds = Math.floor((now - firstSeenTime) / 1000);
18
+ return {
19
+ name: agent.name,
20
+ messagesSent: agent.messagesSent,
21
+ messagesReceived: agent.messagesReceived,
22
+ firstSeen: agent.firstSeen,
23
+ lastSeen: agent.lastSeen,
24
+ isOnline,
25
+ uptimeSeconds,
26
+ };
27
+ });
28
+ }
29
+ /**
30
+ * Compute throughput metrics from message history
31
+ */
32
+ export function computeThroughputMetrics(messages) {
33
+ const now = Date.now();
34
+ const oneMinuteAgo = now - 60 * 1000;
35
+ const oneHourAgo = now - 60 * 60 * 1000;
36
+ const oneDayAgo = now - 24 * 60 * 60 * 1000;
37
+ let lastMinute = 0;
38
+ let lastHour = 0;
39
+ let last24Hours = 0;
40
+ for (const msg of messages) {
41
+ const ts = new Date(msg.timestamp).getTime();
42
+ if (ts >= oneMinuteAgo)
43
+ lastMinute++;
44
+ if (ts >= oneHourAgo)
45
+ lastHour++;
46
+ if (ts >= oneDayAgo)
47
+ last24Hours++;
48
+ }
49
+ // Calculate average messages per minute over the last hour
50
+ const avgMessagesPerMinute = lastHour / 60;
51
+ return {
52
+ messagesLastMinute: lastMinute,
53
+ messagesLastHour: lastHour,
54
+ messagesLast24Hours: last24Hours,
55
+ avgMessagesPerMinute: Math.round(avgMessagesPerMinute * 100) / 100,
56
+ };
57
+ }
58
+ /**
59
+ * Compute session lifecycle metrics from session history
60
+ */
61
+ export function computeSessionMetrics(sessions) {
62
+ let activeSessions = 0;
63
+ let closedByAgent = 0;
64
+ let closedByDisconnect = 0;
65
+ let closedByError = 0;
66
+ for (const session of sessions) {
67
+ if (!session.endedAt) {
68
+ activeSessions++;
69
+ }
70
+ else {
71
+ switch (session.closedBy) {
72
+ case 'agent':
73
+ closedByAgent++;
74
+ break;
75
+ case 'disconnect':
76
+ closedByDisconnect++;
77
+ break;
78
+ case 'error':
79
+ closedByError++;
80
+ break;
81
+ default:
82
+ // Ended but no closedBy - treat as disconnect
83
+ closedByDisconnect++;
84
+ }
85
+ }
86
+ }
87
+ const closedSessions = closedByAgent + closedByDisconnect + closedByError;
88
+ const errorRate = closedSessions > 0 ? (closedByError / closedSessions) * 100 : 0;
89
+ // Format recent sessions for display (most recent 10)
90
+ const recentSessions = sessions
91
+ .slice(0, 10)
92
+ .map((s) => ({
93
+ id: s.id,
94
+ agentName: s.agentName,
95
+ startedAt: new Date(s.startedAt).toISOString(),
96
+ endedAt: s.endedAt ? new Date(s.endedAt).toISOString() : undefined,
97
+ closedBy: s.closedBy,
98
+ messageCount: s.messageCount,
99
+ }));
100
+ return {
101
+ totalSessions: sessions.length,
102
+ activeSessions,
103
+ closedByAgent,
104
+ closedByDisconnect,
105
+ closedByError,
106
+ errorRate: Math.round(errorRate * 100) / 100,
107
+ recentSessions,
108
+ };
109
+ }
110
+ /**
111
+ * Compute full system metrics
112
+ */
113
+ export function computeSystemMetrics(agents, messages, sessions = []) {
114
+ const agentMetrics = computeAgentMetrics(agents);
115
+ const throughput = computeThroughputMetrics(messages);
116
+ const sessionMetrics = computeSessionMetrics(sessions);
117
+ const onlineAgents = agentMetrics.filter((a) => a.isOnline).length;
118
+ const totalMessages = agents.reduce((sum, a) => sum + a.messagesSent + a.messagesReceived, 0) / 2; // Divide by 2 since each message is counted twice (sent + received)
119
+ return {
120
+ totalAgents: agents.length,
121
+ onlineAgents,
122
+ offlineAgents: agents.length - onlineAgents,
123
+ totalMessages: Math.round(totalMessages),
124
+ throughput,
125
+ sessions: sessionMetrics,
126
+ agents: agentMetrics,
127
+ timestamp: new Date().toISOString(),
128
+ };
129
+ }
130
+ /**
131
+ * Format metrics in Prometheus exposition format
132
+ */
133
+ export function formatPrometheusMetrics(metrics) {
134
+ const lines = [];
135
+ // Agent counts
136
+ lines.push('# HELP agent_relay_agents_total Total number of registered agents');
137
+ lines.push('# TYPE agent_relay_agents_total gauge');
138
+ lines.push(`agent_relay_agents_total ${metrics.totalAgents}`);
139
+ lines.push('# HELP agent_relay_agents_online Number of online agents');
140
+ lines.push('# TYPE agent_relay_agents_online gauge');
141
+ lines.push(`agent_relay_agents_online ${metrics.onlineAgents}`);
142
+ // Message throughput
143
+ lines.push('# HELP agent_relay_messages_total Total messages processed');
144
+ lines.push('# TYPE agent_relay_messages_total counter');
145
+ lines.push(`agent_relay_messages_total ${metrics.totalMessages}`);
146
+ lines.push('# HELP agent_relay_messages_last_minute Messages in last minute');
147
+ lines.push('# TYPE agent_relay_messages_last_minute gauge');
148
+ lines.push(`agent_relay_messages_last_minute ${metrics.throughput.messagesLastMinute}`);
149
+ lines.push('# HELP agent_relay_messages_last_hour Messages in last hour');
150
+ lines.push('# TYPE agent_relay_messages_last_hour gauge');
151
+ lines.push(`agent_relay_messages_last_hour ${metrics.throughput.messagesLastHour}`);
152
+ lines.push('# HELP agent_relay_messages_avg_per_minute Average messages per minute');
153
+ lines.push('# TYPE agent_relay_messages_avg_per_minute gauge');
154
+ lines.push(`agent_relay_messages_avg_per_minute ${metrics.throughput.avgMessagesPerMinute}`);
155
+ // Per-agent metrics
156
+ lines.push('# HELP agent_relay_agent_messages_sent Messages sent by agent');
157
+ lines.push('# TYPE agent_relay_agent_messages_sent counter');
158
+ for (const agent of metrics.agents) {
159
+ lines.push(`agent_relay_agent_messages_sent{agent="${agent.name}"} ${agent.messagesSent}`);
160
+ }
161
+ lines.push('# HELP agent_relay_agent_messages_received Messages received by agent');
162
+ lines.push('# TYPE agent_relay_agent_messages_received counter');
163
+ for (const agent of metrics.agents) {
164
+ lines.push(`agent_relay_agent_messages_received{agent="${agent.name}"} ${agent.messagesReceived}`);
165
+ }
166
+ lines.push('# HELP agent_relay_agent_online Agent online status (1=online, 0=offline)');
167
+ lines.push('# TYPE agent_relay_agent_online gauge');
168
+ for (const agent of metrics.agents) {
169
+ lines.push(`agent_relay_agent_online{agent="${agent.name}"} ${agent.isOnline ? 1 : 0}`);
170
+ }
171
+ lines.push('# HELP agent_relay_agent_uptime_seconds Agent uptime in seconds');
172
+ lines.push('# TYPE agent_relay_agent_uptime_seconds gauge');
173
+ for (const agent of metrics.agents) {
174
+ lines.push(`agent_relay_agent_uptime_seconds{agent="${agent.name}"} ${agent.uptimeSeconds}`);
175
+ }
176
+ // Session lifecycle metrics
177
+ lines.push('# HELP agent_relay_sessions_total Total number of sessions');
178
+ lines.push('# TYPE agent_relay_sessions_total counter');
179
+ lines.push(`agent_relay_sessions_total ${metrics.sessions.totalSessions}`);
180
+ lines.push('# HELP agent_relay_sessions_active Number of active sessions');
181
+ lines.push('# TYPE agent_relay_sessions_active gauge');
182
+ lines.push(`agent_relay_sessions_active ${metrics.sessions.activeSessions}`);
183
+ lines.push('# HELP agent_relay_sessions_closed_total Sessions closed by type');
184
+ lines.push('# TYPE agent_relay_sessions_closed_total counter');
185
+ lines.push(`agent_relay_sessions_closed_total{closed_by="agent"} ${metrics.sessions.closedByAgent}`);
186
+ lines.push(`agent_relay_sessions_closed_total{closed_by="disconnect"} ${metrics.sessions.closedByDisconnect}`);
187
+ lines.push(`agent_relay_sessions_closed_total{closed_by="error"} ${metrics.sessions.closedByError}`);
188
+ lines.push('# HELP agent_relay_sessions_error_rate Percentage of sessions closed by error');
189
+ lines.push('# TYPE agent_relay_sessions_error_rate gauge');
190
+ lines.push(`agent_relay_sessions_error_rate ${metrics.sessions.errorRate}`);
191
+ return lines.join('\n') + '\n';
192
+ }
193
+ //# sourceMappingURL=metrics.js.map