import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import crypto from 'node:crypto'; import { spawn } from 'node:child_process'; import { broadcastHiveEvent } from './hiveService.js'; /** * 🦞 P2PCLAW Evolution Service (Rosetta Stone Expansion) * ========================================================= * This service handles the dynamic generation, provisioning, * and deployment of new AI agents into the swarm. * It reads the master UTILIDADES file to assign free LLM keys * to offspring in a round-robin rotation. */ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const UTILITIES_FILE = path.resolve(__dirname, '../../../../../papers/UTILIDADES_HERRAMIENTAS_APIs.txt'); // ── 1. Parse and Pool API Keys ── const ApiPool = { groq: [], deepseek: [], together: [], qwen: [], cerebras: [], mistral: [] }; function loadApiKeys() { try { if (!fs.existsSync(UTILITIES_FILE)) { console.error('[EVOLUTION] Cannot find UTILIDADES_HERRAMIENTAS_APIs.txt'); return; } const content = fs.readFileSync(UTILITIES_FILE, 'utf8'); // More permissive extraction since the format in the file is varied const groqMatch = content.match(/gsk_[a-zA-Z0-9_-]+/g); if (groqMatch) ApiPool.groq = [...new Set(groqMatch)]; const dsMatch = content.match(/sk-[a-zA-Z0-9_-]+/g); // Can also catch Qwen, but works for DeepSeek pool if (dsMatch) ApiPool.deepseek = [...new Set(dsMatch)]; const togetherMatch = content.match(/key_[a-zA-Z0-9_-]+/g); if (togetherMatch) ApiPool.together = [...new Set(togetherMatch)]; const cerebrasMatch = content.match(/csk-[a-zA-Z0-9_-]+/g); if (cerebrasMatch) ApiPool.cerebras = [...new Set(cerebrasMatch)]; const mistralMatch = content.match(/[A-Za-z0-9]{32}/g); if (mistralMatch) ApiPool.mistral = mistralMatch.slice(0, 2); console.log('[EVOLUTION] 🧬 Rosetta Stone API Pool Loaded:'); console.log(` - Groq: ${ApiPool.groq.length} keys`); console.log(` - DeepSeek: ${ApiPool.deepseek.length} keys`); console.log(` - Together: ${ApiPool.together.length} keys`); console.log(` - Cerebras: ${ApiPool.cerebras.length} keys`); } catch (err) { console.error('[EVOLUTION] Error loading API keys:', err.message); } } // Load pools on startup loadApiKeys(); // Round-robin tracking state const counters = { groq: 0, deepseek: 0, together: 0, cerebras: 0, mistral: 0 }; function getNextKey(provider) { const pool = ApiPool[provider]; if (!pool || pool.length === 0) return null; const key = pool[counters[provider] % pool.length]; counters[provider]++; return key; } // ── 2. The Spawning Logic ── const spawnedAgents = new Map(); /** * Spawns a new descendant agent. * @param {Object} blueprint { name, role, provider, prompt, progenitorId } */ export async function spawnAgent(blueprint) { const { name, role, provider, prompt, progenitorId } = blueprint; if (!ApiPool[provider] || ApiPool[provider].length === 0) { throw new Error(`Cannot spawn. No API keys available for provider: ${provider}`); } const apiKey = getNextKey(provider); const agentId = `${provider.substring(0,2)}-${crypto.randomBytes(4).toString('hex')}`; console.log(`[EVOLUTION] 🧬 Spawning descendant [${agentId}] powered by ${provider.toUpperCase()}`); const env = { ...process.env, AGENT_ID: agentId, AGENT_NAME: name, AGENT_ROLE: role, AGENT_PROMPT: prompt, LLM_PROVIDER: provider, LLM_API_KEY: apiKey, PROGENITOR_ID: progenitorId }; const agentScript = path.resolve(__dirname, '../../agents/rosetta/descendant.js'); // Note: We run detached so the agent survives even if the spawner stops, // representing true autonomous proliferation. const child = spawn('node', [agentScript], { env, detached: true, stdio: 'ignore' // or log to a specific agent log file later }); child.unref(); const descendantRecord = { id: agentId, name, role, provider, progenitor: progenitorId, spawnTime: Date.now() }; spawnedAgents.set(agentId, descendantRecord); // Announce the birth to the hive broadcastHiveEvent('agent_spawned', descendantRecord); return descendantRecord; } export function getSpawnedAgents() { return Array.from(spawnedAgents.values()); }