UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

329 lines 18.1 kB
/** * RVFA Appliance Builder -- Constructs self-contained .rvf appliance files. * * Creates a single binary containing kernel, runtime, Ruflo CLI, models/keys, * AgentDB data, and the verification suite. See ADR-058. */ import { createHash, scryptSync, randomBytes, createCipheriv, createDecipheriv, } from 'node:crypto'; import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { execSync } from 'node:child_process'; import { RvfaWriter, } from './rvfa-format.js'; // ── Encryption Constants ───────────────────────────────────── const SCRYPT_KEY_LEN = 32; const SCRYPT_SALT_LEN = 32; const SCRYPT_OPTS = { N: 16384, r: 8, p: 1 }; const AES_IV_LEN = 16; const AES_TAG_LEN = 16; const AES_ALG = 'aes-256-gcm'; // ── Catalog ────────────────────────────────────────────────── const RUFLO_COMMANDS = 'init agent swarm memory mcp task session config status start workflow hooks hive-mind daemon neural security performance providers plugins deployment embeddings claims migrate process doctor completions'.split(' '); const AGENT_TYPES = 'coder reviewer tester planner researcher security-architect security-auditor memory-specialist performance-engineer hierarchical-coordinator mesh-coordinator adaptive-coordinator collective-intelligence-coordinator swarm-memory-manager byzantine-coordinator raft-manager gossip-coordinator consensus-builder crdt-synchronizer quorum-manager security-manager perf-analyzer performance-benchmarker task-orchestrator memory-coordinator smart-agent github-modes pr-manager code-review-swarm issue-tracker release-manager workflow-automation project-board-sync repo-architect multi-repo-swarm sparc-coord sparc-coder specification pseudocode architecture refinement backend-dev mobile-dev ml-developer cicd-engineer api-docs system-architect code-analyzer base-template-generator tdd-london-swarm production-validator'.split(' '); const HOOK_TYPES = 'pre-edit post-edit pre-command post-command pre-task post-task session-start session-end session-restore notify route explain pretrain build-agents transfer teammate-idle task-completed'.split(' '); const WORKER_TYPES = 'ultralearn optimize consolidate predict audit map preload deepdive document refactor benchmark testgaps'.split(' '); const OFFLINE_MODELS = [ { name: 'phi-3-mini-q4', format: 'gguf', sizeHint: '2.3GB', params: '3.8B' }, { name: 'qwen2.5-coder-3b-q4', format: 'gguf', sizeHint: '1.7GB', params: '3B' }, ]; // ── API Key Encryption / Decryption ────────────────────────── /** Encrypt API keys from a .env file. Output: salt(32)+iv(16)+tag(16)+ciphertext */ export function encryptApiKeys(envPath, passphrase) { const keys = parseEnvFile(readFileSync(envPath, 'utf-8')); const plaintext = Buffer.from(JSON.stringify(keys), 'utf-8'); const salt = randomBytes(SCRYPT_SALT_LEN); const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS); const iv = randomBytes(AES_IV_LEN); const cipher = createCipheriv(AES_ALG, key, iv); const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]); return Buffer.concat([salt, iv, cipher.getAuthTag(), encrypted]); } /** Decrypt API keys previously encrypted with encryptApiKeys. */ export function decryptApiKeys(buf, passphrase) { const minLen = SCRYPT_SALT_LEN + AES_IV_LEN + AES_TAG_LEN + 1; if (buf.length < minLen) { throw new Error(`Encrypted buffer too short: need >= ${minLen}B, got ${buf.length}B`); } let off = 0; const salt = buf.subarray(off, off += SCRYPT_SALT_LEN); const iv = buf.subarray(off, off += AES_IV_LEN); const tag = buf.subarray(off, off += AES_TAG_LEN); const ciphertext = buf.subarray(off); const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS); const decipher = createDecipheriv(AES_ALG, key, iv); decipher.setAuthTag(tag); return JSON.parse(Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf-8')); } export class RvfaBuilder { opts; constructor(options) { this.opts = { arch: options.arch || 'x86_64', profile: options.profile, output: resolve(options.output), rufloVersion: options.rufloVersion || detectRufloVersion(), models: options.models ?? defaultModelsForProfile(options.profile), apiKeys: options.apiKeys ?? '', verbose: options.verbose ?? false, }; } async build() { const t0 = performance.now(); this.log(`Building RVFA appliance (profile=${this.opts.profile}, arch=${this.opts.arch})`); const outDir = dirname(this.opts.output); if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true }); const stages = [ { id: 'kernel', raw: this.buildKernelSection(), label: 'Kernel (Alpine rootfs)' }, { id: 'runtime', raw: this.buildRuntimeSection(), label: 'Runtime (Node.js + Claude Code)' }, { id: 'ruflo', raw: this.buildRufloSection(), label: 'Ruflo CLI' }, { id: 'models', raw: this.buildModelsSection(), label: `Models (${this.opts.profile})` }, { id: 'data', raw: this.buildDataSection(), label: 'Data (AgentDB)' }, { id: 'verify', raw: this.buildVerifySection(), label: 'Verify (test suite)' }, ]; const writer = new RvfaWriter(this.buildHeaderPartial()); const summary = []; for (const s of stages) { const st = performance.now(); this.log(` Stage: ${s.label}...`); writer.addSection(s.id, s.raw, { type: s.id }); this.log(` ${fmtBytes(s.raw.length)} raw (${elapsed(st)})`); summary.push({ id: s.id, size: 0, originalSize: s.raw.length }); } const binary = writer.build(); // Patch compressed sizes from the built header try { const hLen = binary.readUInt32LE(8); const hdr = JSON.parse(binary.subarray(12, 12 + hLen).toString('utf-8')); for (const sec of hdr.sections) { const e = summary.find((x) => x.id === sec.id); if (e) e.size = sec.size; } } catch { /* non-fatal */ } writeFileSync(this.opts.output, binary); const duration = performance.now() - t0; this.log(''); this.log('RVFA appliance built successfully.'); this.log(` Output: ${this.opts.output} Size: ${fmtBytes(binary.length)} Duration: ${elapsed(t0)}`); for (const s of summary) { const r = s.originalSize > 0 && s.size > 0 ? ` (${((1 - s.size / s.originalSize) * 100).toFixed(1)}% reduction)` : ''; this.log(` ${s.id}: ${fmtBytes(s.originalSize)} -> ${fmtBytes(s.size)}${r}`); } return { outputPath: this.opts.output, size: binary.length, sections: summary, duration, profile: this.opts.profile }; } // ── Section Builders ───────────────────────────────────── buildKernelSection() { return jsonBuf({ type: 'kernel', distribution: 'alpine', version: '3.23', arch: this.opts.arch, packages: ['busybox', 'dumb-init', 'musl'], init: '/sbin/init -> ruflo-init (PID 1)', features: ['minimal rootfs', 'read-only root filesystem', 'tmpfs for /tmp and /run', 'seccomp profile applied'], sizeTarget: '~5MB compressed', note: 'Manifest-only: actual rootfs fetched during full build pipeline', }); } buildRuntimeSection() { let nodeVersion = 'v22.0.0'; try { nodeVersion = execSync('node --version', { encoding: 'utf-8' }).trim(); } catch { /* keep default */ } return jsonBuf({ type: 'runtime', node: { version: nodeVersion, target: 'v22', variant: `linux-${this.opts.arch}-musl`, stripped: true, excludes: ['npm', 'corepack', 'debug-symbols'] }, claudeCode: { name: 'claude-code-cli', entrypoint: '/usr/local/bin/claude' }, paths: { node: '/usr/local/bin/node', claude: '/usr/local/bin/claude' }, }); } buildRufloSection() { let packageMeta = null; try { const raw = execSync('npm pack ruflo@latest --dry-run --json 2>/dev/null', { encoding: 'utf-8', timeout: 15_000 }); const parsed = JSON.parse(raw); if (Array.isArray(parsed) && parsed.length > 0) packageMeta = parsed[0]; } catch { /* manifest-only fallback */ } return jsonBuf({ type: 'ruflo', version: this.opts.rufloVersion, package: packageMeta ?? { name: 'ruflo', version: this.opts.rufloVersion }, commands: RUFLO_COMMANDS, commandCount: RUFLO_COMMANDS.length, agents: AGENT_TYPES, agentCount: AGENT_TYPES.length, hooks: { count: HOOK_TYPES.length, types: HOOK_TYPES }, workers: { count: WORKER_TYPES.length, types: WORKER_TYPES }, mcpTools: 215, }); } buildModelsSection() { const p = this.opts.profile; const resolveModels = (names) => names.map((n) => OFFLINE_MODELS.find((m) => m.name === n) ?? { name: n, format: 'gguf', sizeHint: 'unknown', params: 'unknown' }); if (p === 'cloud') { const content = { type: 'models', profile: 'cloud', provider: 'api-vault', models: [] }; if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) { const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase()); content.vault = { format: AES_ALG, kdf: 'scrypt', kdfParams: SCRYPT_OPTS, encrypted: enc.toString('base64') }; this.log(' API keys encrypted into vault'); } else { content.vault = null; content.note = 'No API keys provided; set --api-keys to include vault'; } return jsonBuf(content); } if (p === 'hybrid') { const content = { type: 'models', profile: 'hybrid', provider: 'hybrid', engine: 'ruvllm+api-vault', localModels: resolveModels(this.opts.models), routing: { tier1: { handler: 'agent-booster', latency: '<1ms' }, tier2: { handler: 'local-model', latency: '~200ms' }, tier3: { handler: 'cloud-api', latency: '2-5s' }, complexityThreshold: 0.3 }, }; if (this.opts.apiKeys && existsSync(this.opts.apiKeys)) { const enc = encryptApiKeys(this.opts.apiKeys, generateBuildPassphrase()); content.vault = { format: AES_ALG, kdf: 'scrypt', encrypted: enc.toString('base64') }; this.log(' API keys encrypted into vault'); } return jsonBuf(content); } // offline const names = this.opts.models.length > 0 ? this.opts.models : ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4']; return jsonBuf({ type: 'models', profile: 'offline', provider: 'ruvllm', engine: 'ruvllm', models: resolveModels(names), routing: { tier1: { handler: 'agent-booster-wasm', latency: '<1ms' }, tier2: { handler: 'phi-3-mini-q4', latency: '~200ms' }, tier3: { handler: 'qwen2.5-coder-3b-q4', latency: '~2s' }, fallbackToCloud: false }, kvCache: { backend: 'rvf', persistence: true }, note: 'Manifest-only: GGUF weights fetched during full build pipeline', }); } buildDataSection() { // Empty RVF database header (matches rvf-backend.ts RVF\0 magic) const rvfMagic = Buffer.from([0x52, 0x56, 0x46, 0x00]); const hdrJson = Buffer.from(JSON.stringify({ magic: 'RVF\0', version: 1, dimensions: 1536, metric: 'cosine', quantization: 'fp32', entryCount: 0, createdAt: Date.now(), updatedAt: Date.now(), }), 'utf-8'); const hdrLen = Buffer.alloc(4); hdrLen.writeUInt32LE(hdrJson.length, 0); const rvfDb = Buffer.concat([rvfMagic, hdrLen, hdrJson]); const manifest = jsonBuf({ type: 'data', components: { agentDb: { format: 'rvf', magicBytes: 'RVF\\0', databaseSize: rvfDb.length }, hnswIndex: { type: 'hnsw-index', dimensions: 1536, metric: 'cosine', m: 16, efConstruction: 200, maxElements: 100_000, vectorCount: 0 }, sonaPatterns: { type: 'sona-patterns', version: 1, architecture: 'self-optimizing-neural', adaptationTime: '<0.05ms', patterns: [], expertCount: 8, moeConfig: { topK: 2, capacityFactor: 1.25, loadBalancingLoss: 0.01 } }, pluginRegistry: { source: 'ipfs', snapshotted: true, pluginCount: 20 }, }, }); return Buffer.concat([rvfDb, Buffer.from('\n---DATA-MANIFEST---\n'), manifest]); } buildVerifySection() { const scriptPath = resolve(dirname(new URL(import.meta.url).pathname), '../../../../scripts/verify-appliance.sh'); let script; if (existsSync(scriptPath)) { script = readFileSync(scriptPath); this.log(` Bundled verify-appliance.sh (${fmtBytes(script.length)})`); } else { script = Buffer.from([ '#!/bin/sh', 'set -e', 'RUFLO_CMD="${RUFLO_CMD:-ruflo}"', 'echo "Running basic verification..."', '$RUFLO_CMD --version && echo " OK: CLI" || echo " FAIL: CLI"', '$RUFLO_CMD doctor && echo " OK: Doctor" || echo " FAIL: Doctor"', 'echo "Verification complete."', ].join('\n'), 'utf-8'); this.log(' Using stub verify script (verify-appliance.sh not found)'); } const manifest = jsonBuf({ type: 'verify-manifest', categories: 35, criticalChecks: 95, script: 'verify-appliance.sh', scriptSha256: createHash('sha256').update(script).digest('hex'), quickMode: { categories: [1, 2, 3, 4, 5, 25] }, }); return Buffer.concat([script, Buffer.from('\n---VERIFY-MANIFEST---\n'), manifest]); } // ── Header ─────────────────────────────────────────────── buildHeaderPartial() { const providerMap = { cloud: 'api-vault', hybrid: 'hybrid', offline: 'ruvllm' }; const caps = ['cli-26-commands', 'agents-60-plus', 'hooks-17', 'workers-12', 'mcp-215-tools', 'agentdb-rvf', 'hnsw-search', 'sona-patterns', 'security-scanning', 'performance-profiling', 'hive-mind-consensus', 'plugin-registry']; if (this.opts.profile !== 'cloud') caps.push('local-inference-ruvllm'); if (this.opts.profile !== 'offline') caps.push('cloud-api-vault'); const boot = { entrypoint: '/opt/ruflo/bin/cli.js', args: ['--profile', this.opts.profile], env: { NODE_ENV: 'production', CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid', CLAUDE_FLOW_LOG_LEVEL: 'info' }, isolation: this.opts.profile === 'cloud' ? 'container' : 'native', }; const models = { provider: providerMap[this.opts.profile], engine: this.opts.profile === 'cloud' ? undefined : 'ruvllm-0.1.0', models: this.opts.models.length > 0 ? this.opts.models : undefined, vaultEncryption: this.opts.profile !== 'offline' ? 'aes-256-gcm' : undefined, }; return { magic: 'RVFA', version: 1, name: 'ruflo-appliance', appVersion: this.opts.rufloVersion, arch: this.opts.arch, platform: 'linux', profile: this.opts.profile, created: new Date().toISOString(), boot, models, capabilities: caps, }; } log(msg) { if (this.opts.verbose) console.log(`[RvfaBuilder] ${msg}`); } } // ── Helpers ────────────────────────────────────────────────── function jsonBuf(obj) { return Buffer.from(JSON.stringify(obj, null, 2), 'utf-8'); } function parseEnvFile(content) { const result = {}; for (const line of content.split('\n')) { const t = line.trim(); if (!t || t.startsWith('#')) continue; const eq = t.indexOf('='); if (eq === -1) continue; const k = t.substring(0, eq).trim(); let v = t.substring(eq + 1).trim(); if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1); if (k) result[k] = v; } return result; } function detectRufloVersion() { try { const p = resolve(dirname(new URL(import.meta.url).pathname), '../../package.json'); if (existsSync(p)) return JSON.parse(readFileSync(p, 'utf-8')).version ?? '3.5.0'; } catch { /* ignore */ } return '3.5.0'; } function defaultModelsForProfile(profile) { if (profile === 'offline') return ['phi-3-mini-q4', 'qwen2.5-coder-3b-q4']; if (profile === 'hybrid') return ['phi-3-mini-q4']; return []; } function generateBuildPassphrase() { return randomBytes(32).toString('hex'); } function fmtBytes(b) { if (b < 1024) return `${b}B`; if (b < 1048576) return `${(b / 1024).toFixed(1)}KB`; if (b < 1073741824) return `${(b / 1048576).toFixed(1)}MB`; return `${(b / 1073741824).toFixed(2)}GB`; } function elapsed(t) { const ms = performance.now() - t; return ms < 1000 ? `${ms.toFixed(0)}ms` : `${(ms / 1000).toFixed(2)}s`; } //# sourceMappingURL=rvfa-builder.js.map