veas
Version:
Veas CLI - Command-line interface for Veas platform
265 lines • 9.56 kB
JavaScript
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