UNPKG

veas

Version:

Veas CLI - Command-line interface for Veas platform

265 lines 9.56 kB
import { createHash, randomBytes } from 'node:crypto'; import { arch, cpus, hostname, platform, totalmem } from 'node:os'; import { createClient } from '@supabase/supabase-js'; import { logger } from '../utils/logger.js'; export class AgentRegistry { supabase; config; destinationId = null; apiKeyHash = null; heartbeatInterval = null; isRegistered = false; constructor(config) { this.config = config; if (!config.supabaseUrl || !config.supabaseAnonKey) { throw new Error('Supabase URL and anon key are required for agent registry'); } this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, { auth: { persistSession: false, autoRefreshToken: false, }, }); } generateMachineId() { const data = [ hostname(), platform(), arch(), cpus()[0]?.model || 'unknown', process.env.USER || process.env.USERNAME || 'unknown', ].join(':'); return createHash('sha256').update(data).digest('hex').substring(0, 16); } generateApiKey() { return randomBytes(32).toString('hex'); } hashApiKey(apiKey) { return createHash('sha256').update(apiKey).digest('hex'); } getCapabilities() { return { platform: platform(), arch: arch(), cpus: cpus().length, totalMemoryMb: Math.floor(totalmem() / 1024 / 1024), nodeVersion: process.version, cliVersion: '1.0.10', }; } getSupportedTools() { return [ 'list_my_projects', 'get_project', 'create_issue', 'update_issue', 'list_my_issues', 'get_issue', 'create_article', 'update_article', 'list_articles', 'get_article', ]; } async register() { logger.info('Registering agent with platform...'); try { const apiKey = this.config.apiKey || this.generateApiKey(); this.apiKeyHash = this.hashApiKey(apiKey); const machineId = this.generateMachineId(); const capabilities = this.getCapabilities(); const supportedTools = this.getSupportedTools(); const { data: existing } = await this.supabase .schema('agents') .from('agent_destinations') .select('id') .eq('organization_id', this.config.organizationId) .eq('name', this.config.name) .single(); if (existing) { const { error } = await this.supabase .schema('agents') .from('agent_destinations') .update({ hostname: hostname(), machine_id: machineId, cli_version: capabilities.cliVersion, capabilities, supported_tools: supportedTools, api_key_hash: this.apiKeyHash, status: 'online', last_heartbeat_at: new Date().toISOString(), max_concurrent_tasks: this.config.maxConcurrentTasks || 1, is_active: true, updated_at: new Date().toISOString(), }) .eq('id', existing.id) .select() .single(); if (error) { throw error; } this.destinationId = existing.id; logger.info(`Agent updated: ${existing.id}`); } else { const { data, error } = await this.supabase .schema('agents') .from('agent_destinations') .insert({ organization_id: this.config.organizationId, owner_id: '00000000-0000-0000-0000-000000000001', name: this.config.name, hostname: hostname(), machine_id: machineId, cli_version: capabilities.cliVersion, capabilities, supported_tools: supportedTools, api_key_hash: this.apiKeyHash, status: 'online', max_concurrent_tasks: this.config.maxConcurrentTasks || 1, allowed_task_types: ['workflow', 'single', 'batch'], metadata: this.config.capabilities || {}, tags: [], is_active: true, }) .select() .single(); if (error) { throw error; } this.destinationId = data.id; logger.info(`Agent registered: ${data.id}`); if (!this.config.apiKey) { logger.info(`Generated API key: ${apiKey}`); logger.info('Save this API key securely. It will not be shown again.'); } } this.isRegistered = true; this.startHeartbeat(); return this.destinationId; } catch (error) { logger.error('Failed to register agent:', error); throw error; } } async unregister() { if (!this.isRegistered || !this.destinationId) { return; } logger.info('Unregistering agent...'); try { await this.supabase .schema('agents') .from('agent_destinations') .update({ status: 'offline', updated_at: new Date().toISOString(), }) .eq('id', this.destinationId); this.stopHeartbeat(); this.isRegistered = false; logger.info('Agent unregistered'); } catch (error) { logger.error('Failed to unregister agent:', error); } } startHeartbeat() { const intervalMs = this.config.heartbeatIntervalMs || 30000; this.heartbeatInterval = setInterval(async () => { await this.sendHeartbeat(); }, intervalMs); this.sendHeartbeat(); } stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } async sendHeartbeat() { if (!this.destinationId) return; try { const memUsage = process.memoryUsage(); const heartbeat = { destinationId: this.destinationId, cpuUsagePercent: 0, memoryUsageMb: Math.floor(memUsage.heapUsed / 1024 / 1024), diskUsagePercent: 0, activeTasks: 0, queuedTasks: 0, status: 'online', }; const { error: heartbeatError } = await this.supabase.from('destination_heartbeats').insert({ destination_id: this.destinationId, cpu_usage_percent: heartbeat.cpuUsagePercent, memory_usage_mb: heartbeat.memoryUsageMb, disk_usage_percent: heartbeat.diskUsagePercent, active_tasks: heartbeat.activeTasks, queued_tasks: heartbeat.queuedTasks, status: heartbeat.status, }); if (heartbeatError) { logger.error('Failed to send heartbeat:', heartbeatError); } else { logger.debug('Heartbeat sent'); } const { error: updateError } = await this.supabase .schema('agents') .from('agent_destinations') .update({ last_heartbeat_at: new Date().toISOString(), status: 'online', }) .eq('id', this.destinationId); if (updateError) { logger.error('Failed to update destination heartbeat:', updateError); } } catch (error) { logger.error('Error sending heartbeat:', error); } } async updateStatus(status, errorMessage) { if (!this.destinationId) return; try { const updates = { status, updated_at: new Date().toISOString(), }; if (errorMessage) { updates.metadata = { ...this.config.capabilities, lastError: errorMessage, lastErrorAt: new Date().toISOString(), }; } const { error } = await this.supabase .schema('agents') .from('agent_destinations') .update(updates) .eq('id', this.destinationId); if (error) { logger.error('Failed to update agent status:', error); } else { logger.info(`Agent status updated to: ${status}`); } } catch (error) { logger.error('Error updating agent status:', error); } } getDestinationId() { return this.destinationId; } isAgentRegistered() { return this.isRegistered; } } //# sourceMappingURL=agent-registry.js.map