ai-agent-command-center 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +20 -0
- package/LICENSE +21 -0
- package/README.md +402 -2
- package/bin/ai-center.js +183 -0
- package/client/dist/assets/index-BRl_ORtB.css +1 -0
- package/client/dist/assets/index-CLhAmxoi.js +4444 -0
- package/client/dist/icon-192.png +0 -0
- package/client/dist/icon-512.png +0 -0
- package/client/dist/icon.svg +43 -0
- package/client/dist/index.html +31 -0
- package/client/dist/manifest.webmanifest +30 -0
- package/client/dist/sw.js +75 -0
- package/package.json +66 -4
- package/server/.env.example +9 -0
- package/server/dist/server/src/adapters/base.js +4 -0
- package/server/dist/server/src/adapters/customApi.js +353 -0
- package/server/dist/server/src/adapters/hermes.js +82 -0
- package/server/dist/server/src/adapters/preset.js +221 -0
- package/server/dist/server/src/adapters/router.js +32 -0
- package/server/dist/server/src/config/index.js +9 -0
- package/server/dist/server/src/db/index.js +180 -0
- package/server/dist/server/src/db/schemaSql.js +522 -0
- package/server/dist/server/src/index.js +241 -0
- package/server/dist/server/src/middleware/rateLimiter.js +76 -0
- package/server/dist/server/src/middleware/securityHeaders.js +21 -0
- package/server/dist/server/src/routes/actions.js +79 -0
- package/server/dist/server/src/routes/activities.js +35 -0
- package/server/dist/server/src/routes/agents.js +354 -0
- package/server/dist/server/src/routes/alerts.js +174 -0
- package/server/dist/server/src/routes/auth.js +138 -0
- package/server/dist/server/src/routes/dashboard.js +41 -0
- package/server/dist/server/src/routes/dataAnalyst.js +90 -0
- package/server/dist/server/src/routes/documents.js +242 -0
- package/server/dist/server/src/routes/domain.js +58 -0
- package/server/dist/server/src/routes/evals.js +56 -0
- package/server/dist/server/src/routes/mcp.js +160 -0
- package/server/dist/server/src/routes/memoryGraph.js +91 -0
- package/server/dist/server/src/routes/monitoring.js +41 -0
- package/server/dist/server/src/routes/notifications.js +88 -0
- package/server/dist/server/src/routes/orchestrator.js +54 -0
- package/server/dist/server/src/routes/pipeline.js +261 -0
- package/server/dist/server/src/routes/playground.js +91 -0
- package/server/dist/server/src/routes/projects.js +183 -0
- package/server/dist/server/src/routes/providers.js +403 -0
- package/server/dist/server/src/routes/rag.js +118 -0
- package/server/dist/server/src/routes/reports.js +123 -0
- package/server/dist/server/src/routes/schedules.js +165 -0
- package/server/dist/server/src/routes/skills.js +102 -0
- package/server/dist/server/src/routes/sre.js +84 -0
- package/server/dist/server/src/routes/swarm.js +165 -0
- package/server/dist/server/src/routes/system.js +199 -0
- package/server/dist/server/src/routes/tasks.js +314 -0
- package/server/dist/server/src/routes/warRooms.js +113 -0
- package/server/dist/server/src/routes/webResearch.js +60 -0
- package/server/dist/server/src/routes/webhooks.js +102 -0
- package/server/dist/server/src/routes/workflows.js +168 -0
- package/server/dist/server/src/services/actionManager.js +246 -0
- package/server/dist/server/src/services/alertingService.js +289 -0
- package/server/dist/server/src/services/dataAnalystService.js +463 -0
- package/server/dist/server/src/services/documentParserService.js +205 -0
- package/server/dist/server/src/services/domainManagerService.js +286 -0
- package/server/dist/server/src/services/embedding.js +178 -0
- package/server/dist/server/src/services/evalService.js +359 -0
- package/server/dist/server/src/services/logger.js +61 -0
- package/server/dist/server/src/services/mcpClientService.js +518 -0
- package/server/dist/server/src/services/memoryGraphService.js +545 -0
- package/server/dist/server/src/services/monitoring.js +90 -0
- package/server/dist/server/src/services/orchestrator.js +463 -0
- package/server/dist/server/src/services/ragService.js +308 -0
- package/server/dist/server/src/services/realtime.js +74 -0
- package/server/dist/server/src/services/sandbox.js +673 -0
- package/server/dist/server/src/services/schedulerService.js +548 -0
- package/server/dist/server/src/services/skillStudioService.js +366 -0
- package/server/dist/server/src/services/sreAgentService.js +405 -0
- package/server/dist/server/src/services/swarmArtifactService.js +258 -0
- package/server/dist/server/src/services/swarmService.js +623 -0
- package/server/dist/server/src/services/warRoomService.js +461 -0
- package/server/dist/server/src/services/webResearchService.js +295 -0
- package/server/dist/server/src/services/workflowEngine.js +611 -0
- package/server/dist/server/src/utils/auth.js +33 -0
- package/server/dist/server/src/utils/crypto.js +55 -0
- package/server/dist/server/src/utils/ssrf.js +112 -0
- package/server/dist/shared/constants.js +113 -0
- package/server/dist/shared/types.js +5 -0
- package/shared/constants.ts +129 -0
- package/shared/types.ts +1360 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Agent Command Center — Backend Server Entry Point
|
|
3
|
+
*/
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import cors from 'cors';
|
|
6
|
+
import { config } from './config/index.js';
|
|
7
|
+
import { initializeDatabase, checkDatabaseHealth } from './db/index.js';
|
|
8
|
+
import { realtimeHub } from './services/realtime.js';
|
|
9
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
import { securityHeadersMiddleware } from './middleware/securityHeaders.js';
|
|
11
|
+
import { authRateLimiter, computeRateLimiter, globalApiRateLimiter } from './middleware/rateLimiter.js';
|
|
12
|
+
import agentsRouter from './routes/agents.js';
|
|
13
|
+
import providersRouter from './routes/providers.js';
|
|
14
|
+
import tasksRouter from './routes/tasks.js';
|
|
15
|
+
import projectsRouter from './routes/projects.js';
|
|
16
|
+
import activitiesRouter from './routes/activities.js';
|
|
17
|
+
import monitoringRouter from './routes/monitoring.js';
|
|
18
|
+
import dashboardRouter from './routes/dashboard.js';
|
|
19
|
+
import authRouter from './routes/auth.js';
|
|
20
|
+
import reportsRouter from './routes/reports.js';
|
|
21
|
+
import notificationsRouter from './routes/notifications.js';
|
|
22
|
+
import documentsRouter from './routes/documents.js';
|
|
23
|
+
import orchestratorRouter from './routes/orchestrator.js';
|
|
24
|
+
import systemRouter from './routes/system.js';
|
|
25
|
+
import playgroundRouter from './routes/playground.js';
|
|
26
|
+
import webhooksRouter from './routes/webhooks.js';
|
|
27
|
+
import { pipelineRouter } from './routes/pipeline.js';
|
|
28
|
+
import actionsRouter from './routes/actions.js';
|
|
29
|
+
import ragRouter from './routes/rag.js';
|
|
30
|
+
import swarmRouter from './routes/swarm.js';
|
|
31
|
+
import { schedulesRouter } from './routes/schedules.js';
|
|
32
|
+
import { alertsRouter } from './routes/alerts.js';
|
|
33
|
+
import { evalsRouter } from './routes/evals.js';
|
|
34
|
+
import { mcpRouter } from './routes/mcp.js';
|
|
35
|
+
import { workflowsRouter } from './routes/workflows.js';
|
|
36
|
+
import { warRoomsRouter } from './routes/warRooms.js';
|
|
37
|
+
import { webResearchRouter } from './routes/webResearch.js';
|
|
38
|
+
import { sreRouter } from './routes/sre.js';
|
|
39
|
+
import { dataAnalystRouter } from './routes/dataAnalyst.js';
|
|
40
|
+
import { skillsRouter } from './routes/skills.js';
|
|
41
|
+
import memoryGraphRouter from './routes/memoryGraph.js';
|
|
42
|
+
import domainRouter from './routes/domain.js';
|
|
43
|
+
import { systemLogger } from './services/logger.js';
|
|
44
|
+
export const app = express();
|
|
45
|
+
// Security Hardening: Trust Proxy for accurate client IP resolution behind Caddy
|
|
46
|
+
app.set('trust proxy', 1);
|
|
47
|
+
// Security Hardening: Disable fingerprinting headers
|
|
48
|
+
app.disable('x-powered-by');
|
|
49
|
+
// Global Graceful Shutdown State
|
|
50
|
+
let isShuttingDown = false;
|
|
51
|
+
// Reject new incoming traffic with 503 if server is gracefully shutting down
|
|
52
|
+
app.use((req, res, next) => {
|
|
53
|
+
if (isShuttingDown) {
|
|
54
|
+
res.setHeader('Connection', 'close');
|
|
55
|
+
return res.status(503).json({
|
|
56
|
+
error: {
|
|
57
|
+
code: 'SERVER_SHUTTING_DOWN',
|
|
58
|
+
message: 'Server sedang dalam proses shutdown. Silakan coba sesaat lagi.'
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
next();
|
|
63
|
+
});
|
|
64
|
+
// Security & Standard Middlewares
|
|
65
|
+
app.use(securityHeadersMiddleware);
|
|
66
|
+
app.use(cors());
|
|
67
|
+
app.use(express.json({ limit: '15mb' }));
|
|
68
|
+
// DoS & Flood Protection
|
|
69
|
+
app.use('/api', globalApiRateLimiter);
|
|
70
|
+
// Request logging (sanitized, zero secret leaking)
|
|
71
|
+
app.use((req, _res, next) => {
|
|
72
|
+
const sanitizedUrl = req.originalUrl.split('?')[0];
|
|
73
|
+
console.log(`[API] ${req.method} ${sanitizedUrl}`);
|
|
74
|
+
if (!sanitizedUrl.startsWith('/api/realtime') && !sanitizedUrl.startsWith('/api/monitoring/logs')) {
|
|
75
|
+
systemLogger.debug('API', `${req.method} ${sanitizedUrl}`);
|
|
76
|
+
}
|
|
77
|
+
next();
|
|
78
|
+
});
|
|
79
|
+
// Real-time Server-Sent Events (SSE) Stream
|
|
80
|
+
app.get('/api/realtime', (req, res) => {
|
|
81
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
82
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
83
|
+
res.setHeader('Connection', 'keep-alive');
|
|
84
|
+
res.flushHeaders();
|
|
85
|
+
const clientId = `client-${uuidv4().slice(0, 8)}`;
|
|
86
|
+
realtimeHub.addClient(clientId, res);
|
|
87
|
+
});
|
|
88
|
+
// Targeted Abuse-Protection Rate Limiters
|
|
89
|
+
app.use('/api/auth/login', authRateLimiter);
|
|
90
|
+
app.use('/api/playground/execute', computeRateLimiter);
|
|
91
|
+
app.use('/api/evals/run', computeRateLimiter);
|
|
92
|
+
app.use('/api/webhooks/dispatch-task', computeRateLimiter);
|
|
93
|
+
// Mount modular feature routes
|
|
94
|
+
app.use('/api/auth', authRouter);
|
|
95
|
+
app.use('/api/agents', agentsRouter);
|
|
96
|
+
app.use('/api/providers', providersRouter);
|
|
97
|
+
app.use('/api/tasks', tasksRouter);
|
|
98
|
+
app.use('/api/projects', projectsRouter);
|
|
99
|
+
app.use('/api/activities', activitiesRouter);
|
|
100
|
+
app.use('/api/monitoring', monitoringRouter);
|
|
101
|
+
app.use('/api/dashboard', dashboardRouter);
|
|
102
|
+
app.use('/api/reports', reportsRouter);
|
|
103
|
+
app.use('/api/notifications', notificationsRouter);
|
|
104
|
+
app.use('/api/documents', documentsRouter);
|
|
105
|
+
app.use('/api/orchestrator', orchestratorRouter);
|
|
106
|
+
app.use('/api/system', systemRouter);
|
|
107
|
+
app.use('/api/playground', playgroundRouter);
|
|
108
|
+
app.use('/api/webhooks', webhooksRouter);
|
|
109
|
+
app.use('/api/pipeline', pipelineRouter);
|
|
110
|
+
app.use('/api/actions', actionsRouter);
|
|
111
|
+
app.use('/api/rag', ragRouter);
|
|
112
|
+
app.use('/api/swarm', swarmRouter);
|
|
113
|
+
app.use('/api/schedules', schedulesRouter);
|
|
114
|
+
app.use('/api/alerts', alertsRouter);
|
|
115
|
+
app.use('/api/evals', evalsRouter);
|
|
116
|
+
app.use('/api/mcp', mcpRouter);
|
|
117
|
+
app.use('/api/workflows', workflowsRouter);
|
|
118
|
+
app.use('/api/war-rooms', warRoomsRouter);
|
|
119
|
+
app.use('/api/web-research', webResearchRouter);
|
|
120
|
+
app.use('/api/sre', sreRouter);
|
|
121
|
+
app.use('/api/data-analyst', dataAnalystRouter);
|
|
122
|
+
app.use('/api/skills', skillsRouter);
|
|
123
|
+
app.use('/api/memory-graph', memoryGraphRouter);
|
|
124
|
+
app.use('/api/system/domain', domainRouter);
|
|
125
|
+
// Comprehensive Root Healthcheck with Database Connectivity Validation
|
|
126
|
+
app.get('/healthz', async (_req, res) => {
|
|
127
|
+
const dbHealth = await checkDatabaseHealth();
|
|
128
|
+
const isHealthy = dbHealth.ok && !isShuttingDown;
|
|
129
|
+
res.status(isHealthy ? 200 : 503).json({
|
|
130
|
+
status: isHealthy ? 'ok' : 'degraded',
|
|
131
|
+
database: dbHealth.ok ? 'connected' : 'disconnected',
|
|
132
|
+
dbLatencyMs: dbHealth.latencyMs,
|
|
133
|
+
uptimeSeconds: Math.floor(process.uptime()),
|
|
134
|
+
timestamp: new Date().toISOString()
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
// Serve frontend static files if client/dist exists
|
|
138
|
+
import path from 'path';
|
|
139
|
+
import fs from 'fs';
|
|
140
|
+
import { fileURLToPath } from 'url';
|
|
141
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
142
|
+
const __dirname = path.dirname(__filename);
|
|
143
|
+
const candidateDistPaths = [
|
|
144
|
+
path.resolve(process.cwd(), 'client/dist'),
|
|
145
|
+
path.resolve(__dirname, '../../../client/dist'),
|
|
146
|
+
path.resolve(__dirname, '../../../../client/dist'),
|
|
147
|
+
path.resolve(__dirname, '../../client/dist')
|
|
148
|
+
];
|
|
149
|
+
const clientDist = candidateDistPaths.find(p => fs.existsSync(p)) || candidateDistPaths[0];
|
|
150
|
+
if (fs.existsSync(clientDist)) {
|
|
151
|
+
app.use(express.static(clientDist));
|
|
152
|
+
app.get('*', (req, res, next) => {
|
|
153
|
+
if (req.path.startsWith('/api') || req.path === '/healthz') {
|
|
154
|
+
return next();
|
|
155
|
+
}
|
|
156
|
+
res.sendFile(path.join(clientDist, 'index.html'));
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Resilient Global Error Handler (Prevents ERR_HTTP_HEADERS_SENT crashes)
|
|
160
|
+
app.use((err, _req, res, next) => {
|
|
161
|
+
if (res.headersSent) {
|
|
162
|
+
return next(err);
|
|
163
|
+
}
|
|
164
|
+
console.error('[Error]', err);
|
|
165
|
+
res.status(err.status || 500).json({
|
|
166
|
+
error: {
|
|
167
|
+
code: err.code || 'INTERNAL_SERVER_ERROR',
|
|
168
|
+
message: err.message || 'Terjadi kesalahan internal pada server.'
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
export async function startServer() {
|
|
173
|
+
await initializeDatabase();
|
|
174
|
+
const { SwarmService } = await import('./services/swarmService.js');
|
|
175
|
+
await SwarmService.recoverInterruptedMissions();
|
|
176
|
+
const { SchedulerService } = await import('./services/schedulerService.js');
|
|
177
|
+
SchedulerService.startDaemon(30000);
|
|
178
|
+
const server = app.listen(config.port, '0.0.0.0', () => {
|
|
179
|
+
console.log(`[Server] AI Agent Command Center backend listening on port ${config.port} (0.0.0.0)`);
|
|
180
|
+
});
|
|
181
|
+
// Architectural Resilience: True Graceful Shutdown Handler
|
|
182
|
+
const gracefulShutdown = async (signal) => {
|
|
183
|
+
if (isShuttingDown)
|
|
184
|
+
return;
|
|
185
|
+
isShuttingDown = true;
|
|
186
|
+
console.log(`[Server] Received ${signal}. Initiating graceful shutdown...`);
|
|
187
|
+
// 10-second hard fallback timeout to avoid hanging processes
|
|
188
|
+
const forceExitTimer = setTimeout(() => {
|
|
189
|
+
console.error('[Server] Graceful shutdown timed out (10s). Forcing termination.');
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}, 10000);
|
|
192
|
+
forceExitTimer.unref();
|
|
193
|
+
try {
|
|
194
|
+
// 1. Stop background daemons and schedulers
|
|
195
|
+
const { taskRunner } = await import('./services/orchestrator.js');
|
|
196
|
+
taskRunner.stop();
|
|
197
|
+
SchedulerService.stopDaemon();
|
|
198
|
+
// 2. Disconnect real-time SSE clients
|
|
199
|
+
realtimeHub.closeAll();
|
|
200
|
+
// 3. Stop accepting new HTTP requests and drain active requests
|
|
201
|
+
await new Promise((resolve) => {
|
|
202
|
+
server.close((err) => {
|
|
203
|
+
if (err)
|
|
204
|
+
console.error('[Server] Error while closing HTTP server:', err);
|
|
205
|
+
else
|
|
206
|
+
console.log('[Server] In-flight HTTP requests drained and server closed.');
|
|
207
|
+
resolve();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
// 4. Safely terminate database pool after HTTP connections have drained
|
|
211
|
+
const { pool } = await import('./db/index.js');
|
|
212
|
+
await pool.end();
|
|
213
|
+
console.log('[Server] Database pool closed cleanly. Shutdown complete.');
|
|
214
|
+
clearTimeout(forceExitTimer);
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
catch (shutdownErr) {
|
|
218
|
+
console.error('[Server] Error during graceful shutdown:', shutdownErr);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
223
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
224
|
+
return server;
|
|
225
|
+
}
|
|
226
|
+
// Global Process Stability Handlers
|
|
227
|
+
process.on('uncaughtException', (err) => {
|
|
228
|
+
console.error('[Process UncaughtException]', err);
|
|
229
|
+
systemLogger.error('SYSTEM', `Uncaught exception: ${err.message}`, { stack: err.stack });
|
|
230
|
+
});
|
|
231
|
+
process.on('unhandledRejection', (reason) => {
|
|
232
|
+
console.error('[Process UnhandledRejection]', reason);
|
|
233
|
+
systemLogger.error('SYSTEM', `Unhandled rejection: ${reason?.message || reason}`);
|
|
234
|
+
});
|
|
235
|
+
// Auto-start if executed directly
|
|
236
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
237
|
+
startServer().catch(err => {
|
|
238
|
+
console.error('Failed to start server:', err);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates an in-memory sliding-window rate limiter middleware
|
|
3
|
+
*/
|
|
4
|
+
export function createRateLimiter(options) {
|
|
5
|
+
const { windowMs, max, message = 'Terlalu banyak permintaan. Silakan coba beberapa saat lagi.', code = 'RATE_LIMIT_EXCEEDED', skipInTests = true } = options;
|
|
6
|
+
const hits = new Map();
|
|
7
|
+
// Periodic memory garbage collection every 60 seconds
|
|
8
|
+
const cleanupInterval = setInterval(() => {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
for (const [key, record] of hits.entries()) {
|
|
11
|
+
if (now > record.resetAt) {
|
|
12
|
+
hits.delete(key);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}, 60000);
|
|
16
|
+
// Prevent interval from blocking process shutdown
|
|
17
|
+
cleanupInterval.unref();
|
|
18
|
+
return (req, res, next) => {
|
|
19
|
+
if (skipInTests && process.env.NODE_ENV === 'test' && !req.headers['x-test-rate-limit']) {
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
const clientIp = (req.ip || req.socket.remoteAddress || 'unknown').toString();
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const existing = hits.get(clientIp);
|
|
25
|
+
if (!existing || now > existing.resetAt) {
|
|
26
|
+
hits.set(clientIp, {
|
|
27
|
+
count: 1,
|
|
28
|
+
resetAt: now + windowMs
|
|
29
|
+
});
|
|
30
|
+
res.setHeader('X-RateLimit-Limit', max);
|
|
31
|
+
res.setHeader('X-RateLimit-Remaining', max - 1);
|
|
32
|
+
res.setHeader('X-RateLimit-Reset', Math.ceil((now + windowMs) / 1000));
|
|
33
|
+
return next();
|
|
34
|
+
}
|
|
35
|
+
if (existing.count >= max) {
|
|
36
|
+
const retryAfterSec = Math.ceil((existing.resetAt - now) / 1000);
|
|
37
|
+
res.setHeader('Retry-After', retryAfterSec);
|
|
38
|
+
res.setHeader('X-RateLimit-Limit', max);
|
|
39
|
+
res.setHeader('X-RateLimit-Remaining', 0);
|
|
40
|
+
res.setHeader('X-RateLimit-Reset', Math.ceil(existing.resetAt / 1000));
|
|
41
|
+
return res.status(429).json({
|
|
42
|
+
error: {
|
|
43
|
+
code,
|
|
44
|
+
message,
|
|
45
|
+
retryAfterSeconds: retryAfterSec
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
existing.count += 1;
|
|
50
|
+
res.setHeader('X-RateLimit-Limit', max);
|
|
51
|
+
res.setHeader('X-RateLimit-Remaining', Math.max(0, max - existing.count));
|
|
52
|
+
res.setHeader('X-RateLimit-Reset', Math.ceil(existing.resetAt / 1000));
|
|
53
|
+
return next();
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// 1. Strict rate limiter for Authentication (prevent credential brute-force)
|
|
57
|
+
export const authRateLimiter = createRateLimiter({
|
|
58
|
+
windowMs: 60 * 1000, // 1 minute
|
|
59
|
+
max: 15,
|
|
60
|
+
message: 'Terlalu banyak percobaan login gagal. Mohon tunggu 1 menit sebelum mencoba kembali.',
|
|
61
|
+
code: 'AUTH_RATE_LIMIT_EXCEEDED'
|
|
62
|
+
});
|
|
63
|
+
// 2. Compute / Task execution rate limiter (prevent LLM compute exhaustion)
|
|
64
|
+
export const computeRateLimiter = createRateLimiter({
|
|
65
|
+
windowMs: 60 * 1000, // 1 minute
|
|
66
|
+
max: 30,
|
|
67
|
+
message: 'Batas eksekusi komputasi intensif tercapai. Silakan coba kembali dalam 1 menit.',
|
|
68
|
+
code: 'COMPUTE_RATE_LIMIT_EXCEEDED'
|
|
69
|
+
});
|
|
70
|
+
// 3. Global API flood protection limiter (prevent DoS flooding)
|
|
71
|
+
export const globalApiRateLimiter = createRateLimiter({
|
|
72
|
+
windowMs: 60 * 1000, // 1 minute
|
|
73
|
+
max: 300,
|
|
74
|
+
message: 'Terlalu banyak request ke API. Silakan perlambat frekuensi panggilan Anda.',
|
|
75
|
+
code: 'API_RATE_LIMIT_EXCEEDED'
|
|
76
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Headers Middleware — Enforces HTTP defense-in-depth protections
|
|
3
|
+
* Prevents MIME sniffing, clickjacking, inline script execution attacks, and removes fingerprinting headers.
|
|
4
|
+
*/
|
|
5
|
+
export function securityHeadersMiddleware(req, res, next) {
|
|
6
|
+
// 1. Remove technology fingerprint headers
|
|
7
|
+
res.removeHeader('X-Powered-By');
|
|
8
|
+
// 2. Prevent MIME-type confusion attacks
|
|
9
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
10
|
+
// 3. Mitigate clickjacking via framing restrictions
|
|
11
|
+
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
12
|
+
// 4. Enable browser XSS filter block mode
|
|
13
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
14
|
+
// 5. Enforce HTTPS via HSTS
|
|
15
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
16
|
+
// 6. Referrer leakage defense
|
|
17
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
18
|
+
// 7. Restrict unneeded browser hardware capabilities
|
|
19
|
+
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
|
20
|
+
next();
|
|
21
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Actions & Tool Calling Router — AI Agent Command Center
|
|
3
|
+
*/
|
|
4
|
+
import { Router } from 'express';
|
|
5
|
+
import { ActionManager } from '../services/actionManager.js';
|
|
6
|
+
import { AVAILABLE_TOOLS } from '../services/sandbox.js';
|
|
7
|
+
import { pool } from '../db/index.js';
|
|
8
|
+
const router = Router();
|
|
9
|
+
// GET /api/actions — List recent actions
|
|
10
|
+
router.get('/', async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const status = req.query.status;
|
|
13
|
+
const limit = parseInt(req.query.limit, 10) || 50;
|
|
14
|
+
const actions = await ActionManager.listActions(status, limit);
|
|
15
|
+
res.json({ actions });
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
res.status(500).json({ error: { message: err.message } });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
// GET /api/actions/tools — List available sandbox tools and risk definitions
|
|
22
|
+
router.get('/tools', (_req, res) => {
|
|
23
|
+
res.json({ tools: Object.values(AVAILABLE_TOOLS) });
|
|
24
|
+
});
|
|
25
|
+
// GET /api/actions/pending-count — Quick count of actions awaiting approval
|
|
26
|
+
router.get('/pending-count', async (_req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const countRes = await pool.query("SELECT count(*) as count FROM agent_actions WHERE status = 'PENDING';");
|
|
29
|
+
res.json({ pendingCount: parseInt(countRes.rows[0].count, 10) });
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
res.status(500).json({ error: { message: err.message } });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
// POST /api/actions/request — Request a new tool execution
|
|
36
|
+
router.post('/request', async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const { agentId, toolName, toolInput, taskId, runId, autoApprove } = req.body;
|
|
39
|
+
if (!agentId || !toolName || !toolInput) {
|
|
40
|
+
return res.status(400).json({ error: { message: 'agentId, toolName, dan toolInput wajib diisi.' } });
|
|
41
|
+
}
|
|
42
|
+
const action = await ActionManager.requestAction({
|
|
43
|
+
agentId,
|
|
44
|
+
toolName,
|
|
45
|
+
toolInput,
|
|
46
|
+
taskId,
|
|
47
|
+
runId,
|
|
48
|
+
autoApprove
|
|
49
|
+
});
|
|
50
|
+
res.status(201).json(action);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
res.status(500).json({ error: { message: err.message } });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// POST /api/actions/:id/approve — Operator approves an action
|
|
57
|
+
router.post('/:id/approve', async (req, res) => {
|
|
58
|
+
try {
|
|
59
|
+
const operatorId = req.body.operatorId || 'lead-operator';
|
|
60
|
+
const action = await ActionManager.executeAction(req.params.id, operatorId);
|
|
61
|
+
res.json(action);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
res.status(500).json({ error: { message: err.message } });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// POST /api/actions/:id/reject — Operator rejects an action
|
|
68
|
+
router.post('/:id/reject', async (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
const operatorId = req.body.operatorId || 'lead-operator';
|
|
71
|
+
const reason = req.body.reason;
|
|
72
|
+
const action = await ActionManager.rejectAction(req.params.id, operatorId, reason);
|
|
73
|
+
res.json(action);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
res.status(500).json({ error: { message: err.message } });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
export default router;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activities API Routes — AI Agent Command Center
|
|
3
|
+
*/
|
|
4
|
+
import { Router } from 'express';
|
|
5
|
+
import { pool } from '../db/index.js';
|
|
6
|
+
const router = Router();
|
|
7
|
+
// GET /api/activities
|
|
8
|
+
router.get('/', async (req, res) => {
|
|
9
|
+
try {
|
|
10
|
+
const limit = parseInt(req.query.limit || '30', 10);
|
|
11
|
+
const result = await pool.query(`
|
|
12
|
+
SELECT id, workspace_id, actor_type, actor_id, actor_name, event_type, summary, target_type, target_id, created_at
|
|
13
|
+
FROM activities
|
|
14
|
+
ORDER BY created_at DESC
|
|
15
|
+
LIMIT $1;
|
|
16
|
+
`, [limit]);
|
|
17
|
+
const activities = result.rows.map(row => ({
|
|
18
|
+
id: row.id,
|
|
19
|
+
workspaceId: row.workspace_id,
|
|
20
|
+
actorType: row.actor_type,
|
|
21
|
+
actorId: row.actor_id,
|
|
22
|
+
actorName: row.actor_name,
|
|
23
|
+
eventType: row.event_type,
|
|
24
|
+
summary: row.summary,
|
|
25
|
+
targetType: row.target_type,
|
|
26
|
+
targetId: row.target_id,
|
|
27
|
+
createdAt: row.created_at
|
|
28
|
+
}));
|
|
29
|
+
res.json(activities);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
res.status(500).json({ error: { message: err.message } });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
export default router;
|