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.
Files changed (86) hide show
  1. package/.env.example +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +402 -2
  4. package/bin/ai-center.js +183 -0
  5. package/client/dist/assets/index-BRl_ORtB.css +1 -0
  6. package/client/dist/assets/index-CLhAmxoi.js +4444 -0
  7. package/client/dist/icon-192.png +0 -0
  8. package/client/dist/icon-512.png +0 -0
  9. package/client/dist/icon.svg +43 -0
  10. package/client/dist/index.html +31 -0
  11. package/client/dist/manifest.webmanifest +30 -0
  12. package/client/dist/sw.js +75 -0
  13. package/package.json +66 -4
  14. package/server/.env.example +9 -0
  15. package/server/dist/server/src/adapters/base.js +4 -0
  16. package/server/dist/server/src/adapters/customApi.js +353 -0
  17. package/server/dist/server/src/adapters/hermes.js +82 -0
  18. package/server/dist/server/src/adapters/preset.js +221 -0
  19. package/server/dist/server/src/adapters/router.js +32 -0
  20. package/server/dist/server/src/config/index.js +9 -0
  21. package/server/dist/server/src/db/index.js +180 -0
  22. package/server/dist/server/src/db/schemaSql.js +522 -0
  23. package/server/dist/server/src/index.js +241 -0
  24. package/server/dist/server/src/middleware/rateLimiter.js +76 -0
  25. package/server/dist/server/src/middleware/securityHeaders.js +21 -0
  26. package/server/dist/server/src/routes/actions.js +79 -0
  27. package/server/dist/server/src/routes/activities.js +35 -0
  28. package/server/dist/server/src/routes/agents.js +354 -0
  29. package/server/dist/server/src/routes/alerts.js +174 -0
  30. package/server/dist/server/src/routes/auth.js +138 -0
  31. package/server/dist/server/src/routes/dashboard.js +41 -0
  32. package/server/dist/server/src/routes/dataAnalyst.js +90 -0
  33. package/server/dist/server/src/routes/documents.js +242 -0
  34. package/server/dist/server/src/routes/domain.js +58 -0
  35. package/server/dist/server/src/routes/evals.js +56 -0
  36. package/server/dist/server/src/routes/mcp.js +160 -0
  37. package/server/dist/server/src/routes/memoryGraph.js +91 -0
  38. package/server/dist/server/src/routes/monitoring.js +41 -0
  39. package/server/dist/server/src/routes/notifications.js +88 -0
  40. package/server/dist/server/src/routes/orchestrator.js +54 -0
  41. package/server/dist/server/src/routes/pipeline.js +261 -0
  42. package/server/dist/server/src/routes/playground.js +91 -0
  43. package/server/dist/server/src/routes/projects.js +183 -0
  44. package/server/dist/server/src/routes/providers.js +403 -0
  45. package/server/dist/server/src/routes/rag.js +118 -0
  46. package/server/dist/server/src/routes/reports.js +123 -0
  47. package/server/dist/server/src/routes/schedules.js +165 -0
  48. package/server/dist/server/src/routes/skills.js +102 -0
  49. package/server/dist/server/src/routes/sre.js +84 -0
  50. package/server/dist/server/src/routes/swarm.js +165 -0
  51. package/server/dist/server/src/routes/system.js +199 -0
  52. package/server/dist/server/src/routes/tasks.js +314 -0
  53. package/server/dist/server/src/routes/warRooms.js +113 -0
  54. package/server/dist/server/src/routes/webResearch.js +60 -0
  55. package/server/dist/server/src/routes/webhooks.js +102 -0
  56. package/server/dist/server/src/routes/workflows.js +168 -0
  57. package/server/dist/server/src/services/actionManager.js +246 -0
  58. package/server/dist/server/src/services/alertingService.js +289 -0
  59. package/server/dist/server/src/services/dataAnalystService.js +463 -0
  60. package/server/dist/server/src/services/documentParserService.js +205 -0
  61. package/server/dist/server/src/services/domainManagerService.js +286 -0
  62. package/server/dist/server/src/services/embedding.js +178 -0
  63. package/server/dist/server/src/services/evalService.js +359 -0
  64. package/server/dist/server/src/services/logger.js +61 -0
  65. package/server/dist/server/src/services/mcpClientService.js +518 -0
  66. package/server/dist/server/src/services/memoryGraphService.js +545 -0
  67. package/server/dist/server/src/services/monitoring.js +90 -0
  68. package/server/dist/server/src/services/orchestrator.js +463 -0
  69. package/server/dist/server/src/services/ragService.js +308 -0
  70. package/server/dist/server/src/services/realtime.js +74 -0
  71. package/server/dist/server/src/services/sandbox.js +673 -0
  72. package/server/dist/server/src/services/schedulerService.js +548 -0
  73. package/server/dist/server/src/services/skillStudioService.js +366 -0
  74. package/server/dist/server/src/services/sreAgentService.js +405 -0
  75. package/server/dist/server/src/services/swarmArtifactService.js +258 -0
  76. package/server/dist/server/src/services/swarmService.js +623 -0
  77. package/server/dist/server/src/services/warRoomService.js +461 -0
  78. package/server/dist/server/src/services/webResearchService.js +295 -0
  79. package/server/dist/server/src/services/workflowEngine.js +611 -0
  80. package/server/dist/server/src/utils/auth.js +33 -0
  81. package/server/dist/server/src/utils/crypto.js +55 -0
  82. package/server/dist/server/src/utils/ssrf.js +112 -0
  83. package/server/dist/shared/constants.js +113 -0
  84. package/server/dist/shared/types.js +5 -0
  85. package/shared/constants.ts +129 -0
  86. 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;