agenthub/src/app.ts
Paperclip FoundingEngineer 83dbf7eb19 feat(directory): Implement GET /companies/:id/agents/directory endpoint
Implement enriched agent directory endpoint for onboarding and
discovery. Returns all agents with activity status, social channels,
and profile information.

Features:
- All required fields: id, name, urlKey, role, description, specialties
- Activity tracking: lastActivityAt from audit_events
- Status calculation: active (<5min), idle (<60min), offline (>60min)
- Social channels: top 3 by post count per agent
- Profile URL: /BARAAA/agents/:urlKey
- Pagination: ?limit=N (default 50, max 100)
- Role filter: ?role=admin|agent
- Auth: x-agent-id header required (401 if missing)

Implementation:
- src/routes/directory.ts: endpoint logic
- src/app.ts: register directory routes

Fixes BARAAA-91

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-02 22:16:07 +00:00

81 lines
2.7 KiB
TypeScript

import Fastify, { type FastifyInstance } from 'fastify';
import type { AppConfig } from './config.js';
import { pool } from './db/pool.js';
import { registerSecurityPlugins } from './lib/security.js';
import { registerInstrumentation } from './lib/instrumentation.js';
import { registerAgentRoutes } from './routes/agents.js';
import { registerTokenRoutes } from './routes/tokens.js';
import { registerSessionRoutes } from './routes/sessions.js';
import { registerRoomRoutes } from './routes/rooms.js';
import { registerSocialRoutes } from './routes/social.js';
import { registerDirectoryRoutes } from './routes/directory.js';
import { setupSocketIO } from './socket/index.js';
import { register as metricsRegister } from './lib/metrics.js';
import { startMetricsCollector } from './services/metrics-collector.js';
export interface BuildAppOptions {
config: AppConfig;
}
export async function buildApp({ config }: BuildAppOptions): Promise<FastifyInstance> {
const app = Fastify({
logger: { level: config.LOG_LEVEL },
disableRequestLogging: config.NODE_ENV === 'test',
});
// Register security plugins first
await registerSecurityPlugins(app, config);
// Register instrumentation for metrics
await registerInstrumentation(app);
app.get('/healthz', async () => {
return { status: 'ok', uptime: process.uptime() };
});
app.get('/readyz', async (_req, reply) => {
const start = Date.now();
try {
// Check DB connectivity
await pool.query('SELECT 1');
const elapsed = Date.now() - start;
return { status: 'ready', checks: { db: 'ok' }, responseTime: elapsed };
} catch (err) {
reply.status(503);
return {
status: 'not_ready',
checks: { db: 'failed' },
error: err instanceof Error ? err.message : 'unknown',
};
}
});
app.get('/metrics', async (_req, reply) => {
reply.header('Content-Type', metricsRegister.contentType);
return metricsRegister.metrics();
});
// Register API routes
await registerAgentRoutes(app, pool);
await registerTokenRoutes(app, pool);
await registerSessionRoutes(app, pool, config);
await registerRoomRoutes(app, pool);
await registerSocialRoutes(app, pool);
await registerDirectoryRoutes(app, pool);
// Setup socket.io after app is ready (if feature enabled)
await app.ready();
if (config.FEATURE_MESSAGING_ENABLED) {
setupSocketIO(app.server, pool, config);
app.log.info('✅ Socket.IO messaging enabled');
} else {
app.log.warn('⚠️ Socket.IO messaging disabled (FEATURE_MESSAGING_ENABLED=false)');
}
// Start metrics collector (updates room metrics every 30s)
startMetricsCollector(pool);
app.log.info('✅ Metrics collector started');
return app;
}