false, 'error' => 'Missing task or servers']); exit; } $task = $input['task']; $servers = $input['servers']; $parallel = !empty($input['parallel']); $aggregate = !empty($input['aggregate']); $WHITELIST = ['s204', 's95', 's151', 'cerebras', 'groq', 'gemini', 'mistral', 'kaggle', 'hf']; $servers = array_values(array_intersect($servers, $WHITELIST)); if (empty($servers)) { echo json_encode(['ok' => false, 'error' => 'No valid servers']); exit; } $start_total = microtime(true); $results = []; // === Build per-server execution closure === function execServer($server, $task) { $start = microtime(true); $r = ['server' => $server, 'ok' => false, 'output' => null, 'error' => null]; try { switch ($server) { case 's204': // Local exec - generate cmd via Cerebras-fast then run $r = execS204($task); break; case 's95': $r = execS95($task); break; case 's151': $r = execS151($task); break; case 'cerebras': case 'groq': case 'gemini': case 'mistral': $r = execLLM($server, $task); break; case 'kaggle': case 'hf': $r = ['ok' => true, 'output' => "($server worker not yet wired - placeholder)\nFuture: trigger via webhook + poll result"]; break; } } catch (Exception $e) { $r['error'] = $e->getMessage(); } $r['server'] = $server; $r['duration_ms'] = round((microtime(true) - $start) * 1000); return $r; } // ========================================================= // SERVER ADAPTERS // ========================================================= function execS204($task) { // Generate bash cmd via Cerebras-fast, then exec $sysPlan = "Tu es WEVIA. Génère UNE seule commande bash pour cette tâche. Réponds UNIQUEMENT la commande, rien d'autre."; $llm = llmCall($sysPlan, $task, 'cerebras-fast', 200); if (!empty($llm['error'])) return ['ok' => false, 'error' => 'LLM err: ' . $llm['error']]; $cmd = trim($llm['content']); $cmd = preg_replace('/^```(?:bash)?\s*|\s*```$/m', '', $cmd); $cmd = trim(explode("\n", $cmd)[0]); if (!isSafeCmd($cmd)) return ['ok' => false, 'error' => 'BLOCKED: ' . $cmd]; $out = shell_exec("timeout 20 bash -c " . escapeshellarg($cmd) . " 2>&1"); return ['ok' => true, 'output' => "$ $cmd\n" . substr((string)$out, 0, 3000)]; } function execS95($task) { // Generate cmd via LLM then send to S95 sentinel $sysPlan = "Tu es WEVIA. Génère UNE seule commande bash pour cette tâche sur le serveur S95 (Hetzner Ubuntu, WEVADS). Réponds UNIQUEMENT la commande."; $llm = llmCall($sysPlan, $task, 'cerebras-fast', 200); if (!empty($llm['error'])) return ['ok' => false, 'error' => 'LLM err: ' . $llm['error']]; $cmd = trim($llm['content']); $cmd = preg_replace('/^```(?:bash)?\s*|\s*```$/m', '', $cmd); $cmd = trim(explode("\n", $cmd)[0]); if (!isSafeCmd($cmd)) return ['ok' => false, 'error' => 'BLOCKED: ' . $cmd]; // Sentinel S95 endpoint $ch = curl_init('https://wevads.weval-consulting.com/api/sentinel-brain.php'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query(['action' => 'exec', 'cmd' => $cmd]), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 25, CURLOPT_SSL_VERIFYPEER => false ]); $resp = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http !== 200) return ['ok' => false, 'error' => "S95 sentinel HTTP $http", 'output' => substr($resp, 0, 200)]; $data = @json_decode($resp, true); $out = is_array($data) ? ($data['output'] ?? $data['result'] ?? json_encode($data)) : $resp; return ['ok' => true, 'output' => "[S95] $ $cmd\n" . substr((string)$out, 0, 3000)]; } function execS151($task) { // S151 - moins de capacités exec, on fait des checks HTTP basiques $checks = [ 'tracking_alive' => 'http://151.80.235.110/', 'open_php' => 'http://151.80.235.110/open.php?test=1' ]; $out = "[S151 health checks]\n"; foreach ($checks as $name => $url) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_NOBODY => false ]); $r = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $time = curl_getinfo($ch, CURLINFO_TOTAL_TIME); curl_close($ch); $out .= " $name: HTTP $code (" . round($time*1000) . "ms)\n"; } $out .= "\nNote: S151 = OVH tracking server. Pas de shell exec direct (sécurité). Pour extension: wire sentinel-style endpoint."; return ['ok' => true, 'output' => $out]; } function execLLM($modelHint, $task) { $modelMap = [ 'cerebras' => 'cerebras-fast', 'groq' => 'groq', 'gemini' => 'gemini', 'mistral' => 'mistral' ]; $model = $modelMap[$modelHint] ?? $modelHint; $sys = "Tu es un expert sysadmin/devops. Réponds en français pro, structuré, concis."; $r = llmCall($sys, $task, $model, 1000); if (!empty($r['error'])) return ['ok' => false, 'error' => $r['error']]; return ['ok' => true, 'output' => "[$modelHint via " . ($r['provider'] ?? '?') . "]\n\n" . $r['content']]; } function llmCall($sys, $usr, $model, $tokens = 800) { $payload = ['model' => $model, 'messages' => [['role' => 'system', 'content' => $sys], ['role' => 'user', 'content' => $usr]], 'max_tokens' => $tokens, 'temperature' => 0.3]; $ch = curl_init('http://127.0.0.1:4000/v1/chat/completions'); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => 25]); $resp = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http !== 200) return ['error' => "HTTP $http"]; $d = @json_decode($resp, true); return ['content' => $d['choices'][0]['message']['content'] ?? '', 'provider' => $d['provider'] ?? null, 'model' => $d['model'] ?? null]; } function isSafeCmd($cmd) { $bl = ['/rm\s+-rf?\s+\/(?!tmp|var\/log|var\/www\/html\/proofs)/i','/dd\s+if=.*of=\/dev\/(sda|nvme|hda)/i','/mkfs\./i','/:(){.*};:/i','/curl.*\|\s*bash/i','/wget.*\|\s*sh/i','/chattr\s+-i\s+\/etc/i','/userdel\s+root/i','/passwd\s+root/i','/shutdown|reboot|halt|poweroff/i','/iptables\s+-F/i','/systemctl\s+(stop|disable)\s+(nginx|php|cron|sshd)/i']; foreach ($bl as $p) if (preg_match($p, $cmd)) return false; return true; } // ========================================================= // EXECUTE - parallel via fork or sequential // ========================================================= if ($parallel && function_exists('pcntl_fork')) { // True parallel via fork (rarely available in fpm) // Fallback: sequential but each call is fast foreach ($servers as $s) { $results[] = execServer($s, $task); } } else { // Sequential (each LLM call is ~400ms - cumulative but acceptable) foreach ($servers as $s) { $results[] = execServer($s, $task); } } // ========================================================= // AGGREGATE via WEVIA Master if requested // ========================================================= $summary = null; if ($aggregate && count($results) > 1) { $synthesis_input = "Tâche initiale: $task\n\nRésultats des " . count($results) . " workers:\n\n"; foreach ($results as $r) { $synthesis_input .= "=== {$r['server']} (" . ($r['ok'] ? 'OK' : 'FAIL') . ", {$r['duration_ms']}ms) ===\n" . substr($r['output'] ?? $r['error'] ?? '', 0, 1500) . "\n\n"; } $synth = llmCall( "Tu es WEVIA Master. Tu reçois les résultats de plusieurs workers. Synthétise en français pro: principaux constats, divergences, recommandations actionables. Court (max 200 mots).", $synthesis_input, 'cerebras-fast', 700 ); $summary = $synth['content'] ?? null; } echo json_encode([ 'ok' => true, 'task' => $task, 'duration_ms' => round((microtime(true) - $start_total) * 1000), 'servers_used' => $servers, 'results' => $results, 'summary' => $summary, 'aggregate_provider' => $aggregate ? ($synth['provider'] ?? null) : null ], JSON_PRETTY_PRINT);