agent-relay 2.0.23 → 2.0.25
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/dist/src/cli/index.js +160 -17
- package/package.json +18 -52
- package/packages/api-types/package.json +1 -1
- package/packages/bridge/package.json +8 -8
- package/packages/cli-tester/package.json +1 -1
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/package.json +6 -6
- package/deploy/init-db.sql +0 -5
- package/deploy/scripts/setup-fly-workspaces.sh +0 -69
- package/deploy/scripts/setup-railway.sh +0 -75
- package/dist/src/cloud/index.d.ts +0 -8
- package/dist/src/cloud/index.js +0 -8
- package/packages/cloud/dist/api/admin.d.ts +0 -8
- package/packages/cloud/dist/api/admin.js +0 -225
- package/packages/cloud/dist/api/auth.d.ts +0 -20
- package/packages/cloud/dist/api/auth.js +0 -138
- package/packages/cloud/dist/api/billing.d.ts +0 -7
- package/packages/cloud/dist/api/billing.js +0 -564
- package/packages/cloud/dist/api/cli-pty-runner.d.ts +0 -53
- package/packages/cloud/dist/api/cli-pty-runner.js +0 -175
- package/packages/cloud/dist/api/codex-auth-helper.d.ts +0 -21
- package/packages/cloud/dist/api/codex-auth-helper.js +0 -327
- package/packages/cloud/dist/api/consensus.d.ts +0 -13
- package/packages/cloud/dist/api/consensus.js +0 -261
- package/packages/cloud/dist/api/coordinators.d.ts +0 -8
- package/packages/cloud/dist/api/coordinators.js +0 -750
- package/packages/cloud/dist/api/daemons.d.ts +0 -12
- package/packages/cloud/dist/api/daemons.js +0 -535
- package/packages/cloud/dist/api/email-auth.d.ts +0 -11
- package/packages/cloud/dist/api/email-auth.js +0 -347
- package/packages/cloud/dist/api/generic-webhooks.d.ts +0 -8
- package/packages/cloud/dist/api/generic-webhooks.js +0 -129
- package/packages/cloud/dist/api/git.d.ts +0 -8
- package/packages/cloud/dist/api/git.js +0 -269
- package/packages/cloud/dist/api/github-app.d.ts +0 -11
- package/packages/cloud/dist/api/github-app.js +0 -223
- package/packages/cloud/dist/api/middleware/planLimits.d.ts +0 -43
- package/packages/cloud/dist/api/middleware/planLimits.js +0 -202
- package/packages/cloud/dist/api/monitoring.d.ts +0 -11
- package/packages/cloud/dist/api/monitoring.js +0 -578
- package/packages/cloud/dist/api/nango-auth.d.ts +0 -9
- package/packages/cloud/dist/api/nango-auth.js +0 -741
- package/packages/cloud/dist/api/onboarding.d.ts +0 -15
- package/packages/cloud/dist/api/onboarding.js +0 -679
- package/packages/cloud/dist/api/policy.d.ts +0 -8
- package/packages/cloud/dist/api/policy.js +0 -229
- package/packages/cloud/dist/api/provider-env.d.ts +0 -26
- package/packages/cloud/dist/api/provider-env.js +0 -141
- package/packages/cloud/dist/api/providers.d.ts +0 -7
- package/packages/cloud/dist/api/providers.js +0 -574
- package/packages/cloud/dist/api/repos.d.ts +0 -8
- package/packages/cloud/dist/api/repos.js +0 -577
- package/packages/cloud/dist/api/sessions.d.ts +0 -11
- package/packages/cloud/dist/api/sessions.js +0 -302
- package/packages/cloud/dist/api/teams.d.ts +0 -7
- package/packages/cloud/dist/api/teams.js +0 -281
- package/packages/cloud/dist/api/test-helpers.d.ts +0 -10
- package/packages/cloud/dist/api/test-helpers.js +0 -745
- package/packages/cloud/dist/api/usage.d.ts +0 -7
- package/packages/cloud/dist/api/usage.js +0 -111
- package/packages/cloud/dist/api/webhooks.d.ts +0 -8
- package/packages/cloud/dist/api/webhooks.js +0 -645
- package/packages/cloud/dist/api/workspaces.d.ts +0 -25
- package/packages/cloud/dist/api/workspaces.js +0 -1799
- package/packages/cloud/dist/billing/index.d.ts +0 -9
- package/packages/cloud/dist/billing/index.js +0 -9
- package/packages/cloud/dist/billing/plans.d.ts +0 -39
- package/packages/cloud/dist/billing/plans.js +0 -245
- package/packages/cloud/dist/billing/service.d.ts +0 -80
- package/packages/cloud/dist/billing/service.js +0 -388
- package/packages/cloud/dist/billing/types.d.ts +0 -141
- package/packages/cloud/dist/billing/types.js +0 -7
- package/packages/cloud/dist/config.d.ts +0 -5
- package/packages/cloud/dist/config.js +0 -5
- package/packages/cloud/dist/db/bulk-ingest.d.ts +0 -89
- package/packages/cloud/dist/db/bulk-ingest.js +0 -268
- package/packages/cloud/dist/db/drizzle.d.ts +0 -290
- package/packages/cloud/dist/db/drizzle.js +0 -1422
- package/packages/cloud/dist/db/index.d.ts +0 -56
- package/packages/cloud/dist/db/index.js +0 -70
- package/packages/cloud/dist/db/schema.d.ts +0 -5117
- package/packages/cloud/dist/db/schema.js +0 -656
- package/packages/cloud/dist/index.d.ts +0 -11
- package/packages/cloud/dist/index.js +0 -38
- package/packages/cloud/dist/provisioner/index.d.ts +0 -207
- package/packages/cloud/dist/provisioner/index.js +0 -2118
- package/packages/cloud/dist/server.d.ts +0 -17
- package/packages/cloud/dist/server.js +0 -2055
- package/packages/cloud/dist/services/auto-scaler.d.ts +0 -152
- package/packages/cloud/dist/services/auto-scaler.js +0 -439
- package/packages/cloud/dist/services/capacity-manager.d.ts +0 -148
- package/packages/cloud/dist/services/capacity-manager.js +0 -449
- package/packages/cloud/dist/services/ci-agent-spawner.d.ts +0 -49
- package/packages/cloud/dist/services/ci-agent-spawner.js +0 -373
- package/packages/cloud/dist/services/cloud-message-bus.d.ts +0 -28
- package/packages/cloud/dist/services/cloud-message-bus.js +0 -19
- package/packages/cloud/dist/services/compute-enforcement.d.ts +0 -57
- package/packages/cloud/dist/services/compute-enforcement.js +0 -175
- package/packages/cloud/dist/services/coordinator.d.ts +0 -62
- package/packages/cloud/dist/services/coordinator.js +0 -389
- package/packages/cloud/dist/services/index.d.ts +0 -17
- package/packages/cloud/dist/services/index.js +0 -25
- package/packages/cloud/dist/services/intro-expiration.d.ts +0 -60
- package/packages/cloud/dist/services/intro-expiration.js +0 -252
- package/packages/cloud/dist/services/mention-handler.d.ts +0 -65
- package/packages/cloud/dist/services/mention-handler.js +0 -405
- package/packages/cloud/dist/services/nango.d.ts +0 -219
- package/packages/cloud/dist/services/nango.js +0 -424
- package/packages/cloud/dist/services/persistence.d.ts +0 -131
- package/packages/cloud/dist/services/persistence.js +0 -200
- package/packages/cloud/dist/services/planLimits.d.ts +0 -147
- package/packages/cloud/dist/services/planLimits.js +0 -335
- package/packages/cloud/dist/services/presence-registry.d.ts +0 -56
- package/packages/cloud/dist/services/presence-registry.js +0 -91
- package/packages/cloud/dist/services/scaling-orchestrator.d.ts +0 -159
- package/packages/cloud/dist/services/scaling-orchestrator.js +0 -502
- package/packages/cloud/dist/services/scaling-policy.d.ts +0 -121
- package/packages/cloud/dist/services/scaling-policy.js +0 -415
- package/packages/cloud/dist/services/ssh-security.d.ts +0 -31
- package/packages/cloud/dist/services/ssh-security.js +0 -63
- package/packages/cloud/dist/services/workspace-keepalive.d.ts +0 -76
- package/packages/cloud/dist/services/workspace-keepalive.js +0 -234
- package/packages/cloud/dist/shims/consensus.d.ts +0 -23
- package/packages/cloud/dist/shims/consensus.js +0 -5
- package/packages/cloud/dist/webhooks/index.d.ts +0 -24
- package/packages/cloud/dist/webhooks/index.js +0 -29
- package/packages/cloud/dist/webhooks/parsers/github.d.ts +0 -8
- package/packages/cloud/dist/webhooks/parsers/github.js +0 -234
- package/packages/cloud/dist/webhooks/parsers/index.d.ts +0 -23
- package/packages/cloud/dist/webhooks/parsers/index.js +0 -30
- package/packages/cloud/dist/webhooks/parsers/linear.d.ts +0 -9
- package/packages/cloud/dist/webhooks/parsers/linear.js +0 -258
- package/packages/cloud/dist/webhooks/parsers/slack.d.ts +0 -9
- package/packages/cloud/dist/webhooks/parsers/slack.js +0 -214
- package/packages/cloud/dist/webhooks/responders/github.d.ts +0 -8
- package/packages/cloud/dist/webhooks/responders/github.js +0 -73
- package/packages/cloud/dist/webhooks/responders/index.d.ts +0 -23
- package/packages/cloud/dist/webhooks/responders/index.js +0 -30
- package/packages/cloud/dist/webhooks/responders/linear.d.ts +0 -9
- package/packages/cloud/dist/webhooks/responders/linear.js +0 -149
- package/packages/cloud/dist/webhooks/responders/slack.d.ts +0 -20
- package/packages/cloud/dist/webhooks/responders/slack.js +0 -178
- package/packages/cloud/dist/webhooks/router.d.ts +0 -25
- package/packages/cloud/dist/webhooks/router.js +0 -504
- package/packages/cloud/dist/webhooks/rules-engine.d.ts +0 -24
- package/packages/cloud/dist/webhooks/rules-engine.js +0 -287
- package/packages/cloud/dist/webhooks/types.d.ts +0 -186
- package/packages/cloud/dist/webhooks/types.js +0 -8
- package/packages/cloud/package.json +0 -60
- package/scripts/run-migrations.js +0 -43
- package/scripts/setup-stripe-products.ts +0 -312
- package/scripts/verify-schema.js +0 -134
|
@@ -1,578 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Monitoring API Routes
|
|
3
|
-
*
|
|
4
|
-
* Provides endpoints for:
|
|
5
|
-
* - Real-time memory metrics collection
|
|
6
|
-
* - Crash insights and history
|
|
7
|
-
* - Proactive alerting
|
|
8
|
-
* - System health dashboard
|
|
9
|
-
*/
|
|
10
|
-
import { Router } from 'express';
|
|
11
|
-
import { createHash } from 'crypto';
|
|
12
|
-
import { eq, desc, and, gte, sql } from 'drizzle-orm';
|
|
13
|
-
import { requireAuth } from './auth.js';
|
|
14
|
-
import { db as dbModule } from '../db/index.js';
|
|
15
|
-
import { getDb } from '../db/drizzle.js';
|
|
16
|
-
import { agentMetrics, agentCrashes, memoryAlerts, } from '../db/schema.js';
|
|
17
|
-
export const monitoringRouter = Router();
|
|
18
|
-
/**
|
|
19
|
-
* Hash an API key for lookup
|
|
20
|
-
*/
|
|
21
|
-
function hashApiKey(apiKey) {
|
|
22
|
-
return createHash('sha256').update(apiKey).digest('hex');
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Middleware to authenticate daemon by API key
|
|
26
|
-
*/
|
|
27
|
-
async function requireDaemonAuth(req, res, next) {
|
|
28
|
-
const authHeader = req.headers.authorization;
|
|
29
|
-
if (!authHeader || !authHeader.startsWith('Bearer ar_live_')) {
|
|
30
|
-
res.status(401).json({ error: 'Invalid API key format' });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const apiKey = authHeader.replace('Bearer ', '');
|
|
34
|
-
const apiKeyHash = hashApiKey(apiKey);
|
|
35
|
-
try {
|
|
36
|
-
const daemon = await dbModule.linkedDaemons.findByApiKeyHash(apiKeyHash);
|
|
37
|
-
if (!daemon) {
|
|
38
|
-
res.status(401).json({ error: 'Invalid API key' });
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
req.daemon = daemon;
|
|
42
|
-
next();
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
console.error('Daemon auth error:', error);
|
|
46
|
-
res.status(500).json({ error: 'Authentication failed' });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Daemon API (authenticated with API key)
|
|
51
|
-
// ============================================================================
|
|
52
|
-
/**
|
|
53
|
-
* POST /api/monitoring/metrics
|
|
54
|
-
* Report agent memory metrics from daemon
|
|
55
|
-
*/
|
|
56
|
-
monitoringRouter.post('/metrics', requireDaemonAuth, async (req, res) => {
|
|
57
|
-
const daemon = req.daemon;
|
|
58
|
-
const { agents } = req.body;
|
|
59
|
-
if (!agents || !Array.isArray(agents)) {
|
|
60
|
-
return res.status(400).json({ error: 'agents array is required' });
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
const db = getDb();
|
|
64
|
-
const now = new Date();
|
|
65
|
-
// Insert metrics for each agent
|
|
66
|
-
for (const agent of agents) {
|
|
67
|
-
const metricsData = {
|
|
68
|
-
rssBytes: agent.rssBytes || 0,
|
|
69
|
-
heapUsedBytes: agent.heapUsedBytes || 0,
|
|
70
|
-
heapTotalBytes: agent.heapTotalBytes || 0,
|
|
71
|
-
cpuPercent: agent.cpuPercent || 0,
|
|
72
|
-
trend: agent.trend || 'unknown',
|
|
73
|
-
trendRatePerMinute: agent.trendRatePerMinute || 0,
|
|
74
|
-
alertLevel: agent.alertLevel || 'normal',
|
|
75
|
-
highWatermark: agent.highWatermark || 0,
|
|
76
|
-
averageRss: agent.averageRss || 0,
|
|
77
|
-
};
|
|
78
|
-
await db.insert(agentMetrics).values({
|
|
79
|
-
daemonId: daemon.id,
|
|
80
|
-
agentName: agent.name,
|
|
81
|
-
pid: agent.pid,
|
|
82
|
-
status: agent.status || 'unknown',
|
|
83
|
-
rssBytes: agent.rssBytes,
|
|
84
|
-
heapUsedBytes: agent.heapUsedBytes,
|
|
85
|
-
cpuPercent: Math.round(agent.cpuPercent || 0),
|
|
86
|
-
trend: agent.trend,
|
|
87
|
-
trendRatePerMinute: Math.round(agent.trendRatePerMinute || 0),
|
|
88
|
-
alertLevel: agent.alertLevel,
|
|
89
|
-
highWatermark: agent.highWatermark,
|
|
90
|
-
averageRss: Math.round(agent.averageRss || 0),
|
|
91
|
-
metricsData,
|
|
92
|
-
uptimeMs: agent.uptimeMs,
|
|
93
|
-
startedAt: agent.startedAt ? new Date(agent.startedAt) : null,
|
|
94
|
-
recordedAt: now,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
res.json({ success: true, recorded: agents.length });
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
console.error('Error recording metrics:', error);
|
|
101
|
-
res.status(500).json({ error: 'Failed to record metrics' });
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
/**
|
|
105
|
-
* POST /api/monitoring/crash
|
|
106
|
-
* Report an agent crash from daemon
|
|
107
|
-
*/
|
|
108
|
-
monitoringRouter.post('/crash', requireDaemonAuth, async (req, res) => {
|
|
109
|
-
const daemon = req.daemon;
|
|
110
|
-
const { crash } = req.body;
|
|
111
|
-
if (!crash || !crash.agentName) {
|
|
112
|
-
return res.status(400).json({ error: 'crash object with agentName is required' });
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
const db = getDb();
|
|
116
|
-
const insightData = {
|
|
117
|
-
likelyCause: crash.likelyCause || 'unknown',
|
|
118
|
-
confidence: crash.confidence || 'low',
|
|
119
|
-
summary: crash.summary || '',
|
|
120
|
-
details: crash.details || [],
|
|
121
|
-
recommendations: crash.recommendations || [],
|
|
122
|
-
peakMemory: crash.peakMemory || 0,
|
|
123
|
-
lastKnownMemory: crash.lastKnownMemory || null,
|
|
124
|
-
};
|
|
125
|
-
const [inserted] = await db.insert(agentCrashes).values({
|
|
126
|
-
daemonId: daemon.id,
|
|
127
|
-
agentName: crash.agentName,
|
|
128
|
-
pid: crash.pid,
|
|
129
|
-
exitCode: crash.exitCode,
|
|
130
|
-
signal: crash.signal,
|
|
131
|
-
reason: crash.reason,
|
|
132
|
-
likelyCause: crash.likelyCause,
|
|
133
|
-
confidence: crash.confidence,
|
|
134
|
-
summary: crash.summary,
|
|
135
|
-
peakMemory: crash.peakMemory,
|
|
136
|
-
lastKnownMemory: crash.lastKnownMemory,
|
|
137
|
-
memoryTrend: crash.memoryTrend,
|
|
138
|
-
insightData,
|
|
139
|
-
lastOutput: crash.lastOutput?.slice(0, 10000), // Limit to 10KB
|
|
140
|
-
crashedAt: crash.crashedAt ? new Date(crash.crashedAt) : new Date(),
|
|
141
|
-
}).returning();
|
|
142
|
-
res.json({ success: true, crashId: inserted.id });
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
console.error('Error recording crash:', error);
|
|
146
|
-
res.status(500).json({ error: 'Failed to record crash' });
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
/**
|
|
150
|
-
* POST /api/monitoring/alert
|
|
151
|
-
* Report a memory alert from daemon
|
|
152
|
-
*/
|
|
153
|
-
monitoringRouter.post('/alert', requireDaemonAuth, async (req, res) => {
|
|
154
|
-
const daemon = req.daemon;
|
|
155
|
-
const { alert } = req.body;
|
|
156
|
-
if (!alert || !alert.agentName || !alert.alertType) {
|
|
157
|
-
return res.status(400).json({ error: 'alert object with agentName and alertType is required' });
|
|
158
|
-
}
|
|
159
|
-
try {
|
|
160
|
-
const db = getDb();
|
|
161
|
-
const [inserted] = await db.insert(memoryAlerts).values({
|
|
162
|
-
daemonId: daemon.id,
|
|
163
|
-
agentName: alert.agentName,
|
|
164
|
-
alertType: alert.alertType,
|
|
165
|
-
currentRss: alert.currentRss,
|
|
166
|
-
threshold: alert.threshold,
|
|
167
|
-
message: alert.message,
|
|
168
|
-
recommendation: alert.recommendation,
|
|
169
|
-
}).returning();
|
|
170
|
-
res.json({ success: true, alertId: inserted.id });
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
console.error('Error recording alert:', error);
|
|
174
|
-
res.status(500).json({ error: 'Failed to record alert' });
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
// ============================================================================
|
|
178
|
-
// Browser API (authenticated with session)
|
|
179
|
-
// ============================================================================
|
|
180
|
-
/**
|
|
181
|
-
* GET /api/monitoring/overview
|
|
182
|
-
* Get monitoring overview for user's daemons
|
|
183
|
-
*/
|
|
184
|
-
monitoringRouter.get('/overview', requireAuth, async (req, res) => {
|
|
185
|
-
const userId = req.session.userId;
|
|
186
|
-
try {
|
|
187
|
-
const db = getDb();
|
|
188
|
-
// Get all user's daemons
|
|
189
|
-
const daemons = await dbModule.linkedDaemons.findByUserId(userId);
|
|
190
|
-
if (daemons.length === 0) {
|
|
191
|
-
return res.json({
|
|
192
|
-
daemons: [],
|
|
193
|
-
summary: {
|
|
194
|
-
totalAgents: 0,
|
|
195
|
-
healthyAgents: 0,
|
|
196
|
-
warningAgents: 0,
|
|
197
|
-
criticalAgents: 0,
|
|
198
|
-
totalCrashes24h: 0,
|
|
199
|
-
totalAlerts24h: 0,
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
const daemonIds = daemons.map(d => d.id);
|
|
204
|
-
const last24h = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
205
|
-
// Get latest metrics for each agent (subquery to get latest per agent)
|
|
206
|
-
const latestMetrics = await db
|
|
207
|
-
.select()
|
|
208
|
-
.from(agentMetrics)
|
|
209
|
-
.where(and(sql `${agentMetrics.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, gte(agentMetrics.recordedAt, last24h)))
|
|
210
|
-
.orderBy(desc(agentMetrics.recordedAt))
|
|
211
|
-
.limit(100);
|
|
212
|
-
// Get crash count in last 24h
|
|
213
|
-
const crashCount = await db
|
|
214
|
-
.select({ count: sql `count(*)` })
|
|
215
|
-
.from(agentCrashes)
|
|
216
|
-
.where(and(sql `${agentCrashes.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, gte(agentCrashes.crashedAt, last24h)));
|
|
217
|
-
// Get alert count in last 24h
|
|
218
|
-
const alertCount = await db
|
|
219
|
-
.select({ count: sql `count(*)` })
|
|
220
|
-
.from(memoryAlerts)
|
|
221
|
-
.where(and(sql `${memoryAlerts.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, gte(memoryAlerts.createdAt, last24h)));
|
|
222
|
-
// Aggregate by alert level
|
|
223
|
-
const byAlertLevel = {
|
|
224
|
-
normal: 0,
|
|
225
|
-
warning: 0,
|
|
226
|
-
critical: 0,
|
|
227
|
-
oom_imminent: 0,
|
|
228
|
-
};
|
|
229
|
-
// Deduplicate by agent name (keep latest)
|
|
230
|
-
const agentLatest = new Map();
|
|
231
|
-
for (const m of latestMetrics) {
|
|
232
|
-
const key = `${m.daemonId}:${m.agentName}`;
|
|
233
|
-
if (!agentLatest.has(key)) {
|
|
234
|
-
agentLatest.set(key, m);
|
|
235
|
-
byAlertLevel[m.alertLevel] =
|
|
236
|
-
(byAlertLevel[m.alertLevel] || 0) + 1;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
res.json({
|
|
240
|
-
daemons: daemons.map(d => ({
|
|
241
|
-
id: d.id,
|
|
242
|
-
name: d.name,
|
|
243
|
-
machineId: d.machineId,
|
|
244
|
-
status: d.status,
|
|
245
|
-
lastSeenAt: d.lastSeenAt,
|
|
246
|
-
})),
|
|
247
|
-
summary: {
|
|
248
|
-
totalAgents: agentLatest.size,
|
|
249
|
-
healthyAgents: byAlertLevel.normal,
|
|
250
|
-
warningAgents: byAlertLevel.warning,
|
|
251
|
-
criticalAgents: byAlertLevel.critical + byAlertLevel.oom_imminent,
|
|
252
|
-
totalCrashes24h: Number(crashCount[0]?.count || 0),
|
|
253
|
-
totalAlerts24h: Number(alertCount[0]?.count || 0),
|
|
254
|
-
},
|
|
255
|
-
latestMetrics: Array.from(agentLatest.values()),
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
catch (error) {
|
|
259
|
-
console.error('Error fetching monitoring overview:', error);
|
|
260
|
-
res.status(500).json({ error: 'Failed to fetch monitoring overview' });
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
/**
|
|
264
|
-
* GET /api/monitoring/agents/:agentName/metrics
|
|
265
|
-
* Get detailed metrics history for an agent
|
|
266
|
-
*/
|
|
267
|
-
monitoringRouter.get('/agents/:agentName/metrics', requireAuth, async (req, res) => {
|
|
268
|
-
const userId = req.session.userId;
|
|
269
|
-
const agentName = req.params.agentName;
|
|
270
|
-
const { daemonId, hours = '24' } = req.query;
|
|
271
|
-
try {
|
|
272
|
-
const db = getDb();
|
|
273
|
-
// Verify daemon belongs to user
|
|
274
|
-
if (daemonId) {
|
|
275
|
-
const daemon = await dbModule.linkedDaemons.findById(daemonId);
|
|
276
|
-
if (!daemon || daemon.userId !== userId) {
|
|
277
|
-
return res.status(404).json({ error: 'Daemon not found' });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const since = new Date(Date.now() - parseInt(hours) * 60 * 60 * 1000);
|
|
281
|
-
// Get user's daemons
|
|
282
|
-
const daemons = await dbModule.linkedDaemons.findByUserId(userId);
|
|
283
|
-
const daemonIds = daemonId ? [daemonId] : daemons.map(d => d.id);
|
|
284
|
-
const metrics = await db
|
|
285
|
-
.select()
|
|
286
|
-
.from(agentMetrics)
|
|
287
|
-
.where(and(sql `${agentMetrics.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, eq(agentMetrics.agentName, agentName), gte(agentMetrics.recordedAt, since)))
|
|
288
|
-
.orderBy(desc(agentMetrics.recordedAt))
|
|
289
|
-
.limit(1000);
|
|
290
|
-
// Calculate statistics
|
|
291
|
-
const rssSamples = metrics.map(m => Number(m.rssBytes || 0));
|
|
292
|
-
const stats = {
|
|
293
|
-
count: metrics.length,
|
|
294
|
-
avgRss: rssSamples.length > 0 ? rssSamples.reduce((a, b) => a + b, 0) / rssSamples.length : 0,
|
|
295
|
-
maxRss: rssSamples.length > 0 ? Math.max(...rssSamples) : 0,
|
|
296
|
-
minRss: rssSamples.length > 0 ? Math.min(...rssSamples) : 0,
|
|
297
|
-
latestTrend: metrics[0]?.trend || 'unknown',
|
|
298
|
-
latestAlertLevel: metrics[0]?.alertLevel || 'normal',
|
|
299
|
-
};
|
|
300
|
-
res.json({
|
|
301
|
-
agentName,
|
|
302
|
-
metrics,
|
|
303
|
-
stats,
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
console.error('Error fetching agent metrics:', error);
|
|
308
|
-
res.status(500).json({ error: 'Failed to fetch agent metrics' });
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
/**
|
|
312
|
-
* GET /api/monitoring/crashes
|
|
313
|
-
* Get crash history
|
|
314
|
-
*/
|
|
315
|
-
monitoringRouter.get('/crashes', requireAuth, async (req, res) => {
|
|
316
|
-
const userId = req.session.userId;
|
|
317
|
-
const { daemonId, agentName, limit = '50' } = req.query;
|
|
318
|
-
try {
|
|
319
|
-
const db = getDb();
|
|
320
|
-
// Get user's daemons
|
|
321
|
-
const daemons = await dbModule.linkedDaemons.findByUserId(userId);
|
|
322
|
-
const daemonIds = daemonId ? [daemonId] : daemons.map(d => d.id);
|
|
323
|
-
let query = db
|
|
324
|
-
.select()
|
|
325
|
-
.from(agentCrashes)
|
|
326
|
-
.where(sql `${agentCrashes.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`);
|
|
327
|
-
if (agentName) {
|
|
328
|
-
query = db
|
|
329
|
-
.select()
|
|
330
|
-
.from(agentCrashes)
|
|
331
|
-
.where(and(sql `${agentCrashes.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, eq(agentCrashes.agentName, agentName)));
|
|
332
|
-
}
|
|
333
|
-
const crashes = await query
|
|
334
|
-
.orderBy(desc(agentCrashes.crashedAt))
|
|
335
|
-
.limit(parseInt(limit));
|
|
336
|
-
// Get crash statistics by cause
|
|
337
|
-
const byCause = {};
|
|
338
|
-
for (const crash of crashes) {
|
|
339
|
-
const cause = crash.likelyCause || 'unknown';
|
|
340
|
-
byCause[cause] = (byCause[cause] || 0) + 1;
|
|
341
|
-
}
|
|
342
|
-
res.json({
|
|
343
|
-
crashes,
|
|
344
|
-
stats: {
|
|
345
|
-
total: crashes.length,
|
|
346
|
-
byCause,
|
|
347
|
-
},
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
console.error('Error fetching crashes:', error);
|
|
352
|
-
res.status(500).json({ error: 'Failed to fetch crashes' });
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
/**
|
|
356
|
-
* GET /api/monitoring/crashes/:id
|
|
357
|
-
* Get detailed crash information
|
|
358
|
-
*/
|
|
359
|
-
monitoringRouter.get('/crashes/:id', requireAuth, async (req, res) => {
|
|
360
|
-
const userId = req.session.userId;
|
|
361
|
-
const id = req.params.id;
|
|
362
|
-
try {
|
|
363
|
-
const db = getDb();
|
|
364
|
-
const [crash] = await db
|
|
365
|
-
.select()
|
|
366
|
-
.from(agentCrashes)
|
|
367
|
-
.where(eq(agentCrashes.id, id))
|
|
368
|
-
.limit(1);
|
|
369
|
-
if (!crash) {
|
|
370
|
-
return res.status(404).json({ error: 'Crash not found' });
|
|
371
|
-
}
|
|
372
|
-
// Verify user owns this daemon
|
|
373
|
-
const daemon = await dbModule.linkedDaemons.findById(crash.daemonId);
|
|
374
|
-
if (!daemon || daemon.userId !== userId) {
|
|
375
|
-
return res.status(404).json({ error: 'Crash not found' });
|
|
376
|
-
}
|
|
377
|
-
res.json({ crash, daemon: { id: daemon.id, name: daemon.name } });
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
console.error('Error fetching crash:', error);
|
|
381
|
-
res.status(500).json({ error: 'Failed to fetch crash' });
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
/**
|
|
385
|
-
* GET /api/monitoring/alerts
|
|
386
|
-
* Get memory alerts
|
|
387
|
-
*/
|
|
388
|
-
monitoringRouter.get('/alerts', requireAuth, async (req, res) => {
|
|
389
|
-
const userId = req.session.userId;
|
|
390
|
-
const { daemonId, acknowledged, limit = '100' } = req.query;
|
|
391
|
-
try {
|
|
392
|
-
const db = getDb();
|
|
393
|
-
// Get user's daemons
|
|
394
|
-
const daemons = await dbModule.linkedDaemons.findByUserId(userId);
|
|
395
|
-
const daemonIds = daemonId ? [daemonId] : daemons.map(d => d.id);
|
|
396
|
-
const whereConditions = [
|
|
397
|
-
sql `${memoryAlerts.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`
|
|
398
|
-
];
|
|
399
|
-
if (acknowledged !== undefined) {
|
|
400
|
-
whereConditions.push(eq(memoryAlerts.acknowledged, acknowledged === 'true'));
|
|
401
|
-
}
|
|
402
|
-
const alerts = await db
|
|
403
|
-
.select()
|
|
404
|
-
.from(memoryAlerts)
|
|
405
|
-
.where(and(...whereConditions))
|
|
406
|
-
.orderBy(desc(memoryAlerts.createdAt))
|
|
407
|
-
.limit(parseInt(limit));
|
|
408
|
-
// Count unacknowledged
|
|
409
|
-
const unacknowledgedCount = await db
|
|
410
|
-
.select({ count: sql `count(*)` })
|
|
411
|
-
.from(memoryAlerts)
|
|
412
|
-
.where(and(sql `${memoryAlerts.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, eq(memoryAlerts.acknowledged, false)));
|
|
413
|
-
res.json({
|
|
414
|
-
alerts,
|
|
415
|
-
unacknowledgedCount: Number(unacknowledgedCount[0]?.count || 0),
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
catch (error) {
|
|
419
|
-
console.error('Error fetching alerts:', error);
|
|
420
|
-
res.status(500).json({ error: 'Failed to fetch alerts' });
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
/**
|
|
424
|
-
* POST /api/monitoring/alerts/:id/acknowledge
|
|
425
|
-
* Acknowledge an alert
|
|
426
|
-
*/
|
|
427
|
-
monitoringRouter.post('/alerts/:id/acknowledge', requireAuth, async (req, res) => {
|
|
428
|
-
const userId = req.session.userId;
|
|
429
|
-
const id = req.params.id;
|
|
430
|
-
try {
|
|
431
|
-
const db = getDb();
|
|
432
|
-
// Get the alert
|
|
433
|
-
const [alert] = await db
|
|
434
|
-
.select()
|
|
435
|
-
.from(memoryAlerts)
|
|
436
|
-
.where(eq(memoryAlerts.id, id))
|
|
437
|
-
.limit(1);
|
|
438
|
-
if (!alert) {
|
|
439
|
-
return res.status(404).json({ error: 'Alert not found' });
|
|
440
|
-
}
|
|
441
|
-
// Verify user owns this daemon
|
|
442
|
-
const daemon = await dbModule.linkedDaemons.findById(alert.daemonId);
|
|
443
|
-
if (!daemon || daemon.userId !== userId) {
|
|
444
|
-
return res.status(404).json({ error: 'Alert not found' });
|
|
445
|
-
}
|
|
446
|
-
// Update alert
|
|
447
|
-
await db
|
|
448
|
-
.update(memoryAlerts)
|
|
449
|
-
.set({
|
|
450
|
-
acknowledged: true,
|
|
451
|
-
acknowledgedAt: new Date(),
|
|
452
|
-
})
|
|
453
|
-
.where(eq(memoryAlerts.id, id));
|
|
454
|
-
res.json({ success: true });
|
|
455
|
-
}
|
|
456
|
-
catch (error) {
|
|
457
|
-
console.error('Error acknowledging alert:', error);
|
|
458
|
-
res.status(500).json({ error: 'Failed to acknowledge alert' });
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
/**
|
|
462
|
-
* GET /api/monitoring/insights
|
|
463
|
-
* Get overall system insights and recommendations
|
|
464
|
-
*/
|
|
465
|
-
monitoringRouter.get('/insights', requireAuth, async (req, res) => {
|
|
466
|
-
const userId = req.session.userId;
|
|
467
|
-
try {
|
|
468
|
-
const db = getDb();
|
|
469
|
-
// Get user's daemons
|
|
470
|
-
const daemons = await dbModule.linkedDaemons.findByUserId(userId);
|
|
471
|
-
if (daemons.length === 0) {
|
|
472
|
-
return res.json({
|
|
473
|
-
healthScore: 100,
|
|
474
|
-
summary: 'No daemons connected. Link a daemon to start monitoring.',
|
|
475
|
-
issues: [],
|
|
476
|
-
recommendations: ['Connect a local daemon using `agent-relay cloud link`'],
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
const daemonIds = daemons.map(d => d.id);
|
|
480
|
-
const last24h = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
481
|
-
const last7d = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
482
|
-
// Get crash stats
|
|
483
|
-
const crashes24h = await db
|
|
484
|
-
.select()
|
|
485
|
-
.from(agentCrashes)
|
|
486
|
-
.where(and(sql `${agentCrashes.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, gte(agentCrashes.crashedAt, last24h)));
|
|
487
|
-
const crashes7d = await db
|
|
488
|
-
.select()
|
|
489
|
-
.from(agentCrashes)
|
|
490
|
-
.where(and(sql `${agentCrashes.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, gte(agentCrashes.crashedAt, last7d)));
|
|
491
|
-
// Get unacknowledged alerts
|
|
492
|
-
const pendingAlerts = await db
|
|
493
|
-
.select()
|
|
494
|
-
.from(memoryAlerts)
|
|
495
|
-
.where(and(sql `${memoryAlerts.daemonId} IN (${sql.join(daemonIds.map(id => sql `${id}`), sql `, `)})`, eq(memoryAlerts.acknowledged, false)))
|
|
496
|
-
.limit(10);
|
|
497
|
-
// Calculate health score
|
|
498
|
-
let healthScore = 100;
|
|
499
|
-
const issues = [];
|
|
500
|
-
const recommendations = [];
|
|
501
|
-
// Deduct for OOM crashes
|
|
502
|
-
const oomCrashes = crashes24h.filter(c => c.likelyCause === 'oom').length;
|
|
503
|
-
if (oomCrashes > 0) {
|
|
504
|
-
healthScore -= oomCrashes * 15;
|
|
505
|
-
issues.push({
|
|
506
|
-
severity: 'critical',
|
|
507
|
-
message: `${oomCrashes} out-of-memory crash${oomCrashes > 1 ? 'es' : ''} in last 24 hours`,
|
|
508
|
-
});
|
|
509
|
-
recommendations.push('Increase memory limits or optimize agent memory usage');
|
|
510
|
-
}
|
|
511
|
-
// Deduct for memory leak crashes
|
|
512
|
-
const leakCrashes = crashes24h.filter(c => c.likelyCause === 'memory_leak').length;
|
|
513
|
-
if (leakCrashes > 0) {
|
|
514
|
-
healthScore -= leakCrashes * 10;
|
|
515
|
-
issues.push({
|
|
516
|
-
severity: 'high',
|
|
517
|
-
message: `${leakCrashes} likely memory leak crash${leakCrashes > 1 ? 'es' : ''} detected`,
|
|
518
|
-
});
|
|
519
|
-
recommendations.push('Investigate agents for memory leaks');
|
|
520
|
-
}
|
|
521
|
-
// Deduct for other crashes
|
|
522
|
-
const otherCrashes = crashes24h.length - oomCrashes - leakCrashes;
|
|
523
|
-
if (otherCrashes > 0) {
|
|
524
|
-
healthScore -= otherCrashes * 5;
|
|
525
|
-
issues.push({
|
|
526
|
-
severity: 'medium',
|
|
527
|
-
message: `${otherCrashes} other crash${otherCrashes > 1 ? 'es' : ''} in last 24 hours`,
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
// Deduct for pending critical alerts
|
|
531
|
-
const criticalAlerts = pendingAlerts.filter(a => a.alertType === 'critical' || a.alertType === 'oom_imminent').length;
|
|
532
|
-
if (criticalAlerts > 0) {
|
|
533
|
-
healthScore -= criticalAlerts * 8;
|
|
534
|
-
issues.push({
|
|
535
|
-
severity: 'high',
|
|
536
|
-
message: `${criticalAlerts} unacknowledged critical alert${criticalAlerts > 1 ? 's' : ''}`,
|
|
537
|
-
});
|
|
538
|
-
recommendations.push('Review and acknowledge pending alerts');
|
|
539
|
-
}
|
|
540
|
-
// Clamp health score
|
|
541
|
-
healthScore = Math.max(0, Math.min(100, healthScore));
|
|
542
|
-
// Generate summary
|
|
543
|
-
let summary;
|
|
544
|
-
if (healthScore >= 90) {
|
|
545
|
-
summary = 'System is healthy. All agents operating normally.';
|
|
546
|
-
}
|
|
547
|
-
else if (healthScore >= 70) {
|
|
548
|
-
summary = 'Some issues detected. Review warnings and recommendations.';
|
|
549
|
-
}
|
|
550
|
-
else if (healthScore >= 50) {
|
|
551
|
-
summary = 'Multiple issues detected. Action recommended.';
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
summary = 'Critical issues detected. Immediate action required.';
|
|
555
|
-
}
|
|
556
|
-
res.json({
|
|
557
|
-
healthScore,
|
|
558
|
-
summary,
|
|
559
|
-
issues: issues.sort((a, b) => {
|
|
560
|
-
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
561
|
-
return (order[a.severity] || 4) - (order[b.severity] || 4);
|
|
562
|
-
}),
|
|
563
|
-
recommendations,
|
|
564
|
-
stats: {
|
|
565
|
-
crashes24h: crashes24h.length,
|
|
566
|
-
crashes7d: crashes7d.length,
|
|
567
|
-
pendingAlerts: pendingAlerts.length,
|
|
568
|
-
connectedDaemons: daemons.filter(d => d.status === 'online').length,
|
|
569
|
-
totalDaemons: daemons.length,
|
|
570
|
-
},
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
console.error('Error fetching insights:', error);
|
|
575
|
-
res.status(500).json({ error: 'Failed to fetch insights' });
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
//# sourceMappingURL=monitoring.js.map
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nango Auth API Routes
|
|
3
|
-
*
|
|
4
|
-
* Handles GitHub OAuth via Nango with two-connection pattern:
|
|
5
|
-
* - github: User login (identity)
|
|
6
|
-
* - github-app-oauth: Repository access
|
|
7
|
-
*/
|
|
8
|
-
export declare const nangoAuthRouter: import("express-serve-static-core").Router;
|
|
9
|
-
//# sourceMappingURL=nango-auth.d.ts.map
|