'Missing task']); exit; } $task = $input['task']; $max_steps = (int)($input['max_steps'] ?? 5); $dry_run = !empty($input['dry_run']); $consensus = !empty($input['consensus']); $consensus_models = $input['consensus_models'] ?? ['cerebras-think', 'groq', 'gemini', 'mistral', 'cloudflare-ai']; sse('start', ['task' => $task, 'max_steps' => $max_steps, 'dry_run' => $dry_run, 'consensus' => $consensus]); // === SAFETY GUARDRAILS === function isSafeCmd($cmd) { $blocklist = [ '/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 ($blocklist as $pattern) if (preg_match($pattern, $cmd)) return false; return true; } function llmCall($system, $user, $model = 'cerebras-think', $max_tokens = 1500, $timeout = 25) { $payload = [ 'model' => $model, 'messages' => [ ['role' => 'system', 'content' => $system], ['role' => 'user', 'content' => $user] ], 'max_tokens' => $max_tokens, 'temperature' => 0.2 ]; $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 => $timeout ]); $resp = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = @json_decode($resp, true); if ($http !== 200) return ['error' => "LLM HTTP $http", 'raw' => substr($resp, 0, 200)]; return [ 'content' => $data['choices'][0]['message']['content'] ?? '', 'model' => $data['model'] ?? null, 'provider' => $data['provider'] ?? null ]; } function execCmd($cmd, $timeout = 25) { if (!isSafeCmd($cmd)) { return ['ok' => false, 'error' => 'BLOCKED by safety guardrails', 'cmd' => $cmd]; } $start = microtime(true); $output = shell_exec("timeout $timeout bash -c " . escapeshellarg($cmd) . " 2>&1"); $duration_ms = round((microtime(true) - $start) * 1000); return [ 'ok' => true, 'cmd' => $cmd, 'output' => substr((string)$output, 0, 4000), 'duration_ms' => $duration_ms ]; } function parsePlanJson($text) { $text = preg_replace('/^```(?:json)?\s*/m', '', $text); $text = preg_replace('/\s*```$/m', '', trim($text)); $data = @json_decode($text, true); if (!$data || !isset($data['commands'])) { $first = strpos($text, '{'); $last = strrpos($text, '}'); if ($first !== false && $last !== false && $last > $first) { $data = @json_decode(substr($text, $first, $last - $first + 1), true); } } if (!$data || !isset($data['commands'])) { $unesc = stripslashes($text); $first = strpos($unesc, '{'); $last = strrpos($unesc, '}'); if ($first !== false && $last !== false) { $data = @json_decode(substr($unesc, $first, $last - $first + 1), true); } } return $data; } // ========================================================= // STEP 1 — INITIAL PLAN by WEVIA Master (Cerebras-think) // ========================================================= sse('thinking', ['step' => 'plan', 'msg' => 'WEVIA Master génère plan initial...']); $systemPlan = "Tu es WEVIA Master, agent autonome S204 (weval-consulting.com). Capacités: bash, php, python3, curl, git, sudo NOPASSWD www-data. Réponds UNIQUEMENT en JSON strict: {\"plan\":[\"étape 1\",...],\"commands\":[\"cmd bash 1\",...],\"risk\":\"low|medium|high\",\"rationale\":\"pourquoi cette approche\"} Commands courtes, lisibles, exécutables. Pas de rm -rf /, format, fork bombs, curl|bash. Si dangereux: planifie quand meme mais avec risk=high + explique les impacts possibles. WEVIA = PATRON (doctrine 210) - elle execute avec precautions, ne refuse plus."; $plan = llmCall($systemPlan, "TÂCHE: $task\n\nPropose plan JSON.", 'cerebras-think', 1500); if (!empty($plan['error'])) { sse('error', ['msg' => 'Plan LLM error: ' . $plan['error']]); exit; } $planData = parsePlanJson($plan['content']); if (!$planData) { sse('error', ['msg' => 'Plan parse failed', 'raw' => substr($plan['content'], 0, 400)]); exit; } sse('plan', [ 'plan' => $planData['plan'] ?? [], 'commands' => $planData['commands'] ?? [], 'risk' => $planData['risk'] ?? 'unknown', 'rationale' => $planData['rationale'] ?? null, 'model' => $plan['model'], 'provider' => $plan['provider'] ]); // ========================================================= // doctrine_178_autoconsensus: force consensus si plan risky $dangerousKeywords = ["restart", "reboot", "systemctl", "service ", "apt ", "upgrade", "kill ", "rm -rf", "DROP ", "TRUNCATE", "mysql ", "nginx "]; $planText = strtolower(json_encode($planData)); $riskyDetected = []; foreach ($dangerousKeywords as $kw) { if (strpos($planText, strtolower($kw)) !== false) $riskyDetected[] = $kw; } if (!empty($riskyDetected) && !$consensus) { $consensus = true; sse("doctrine_178", ["msg" => "Keywords dangereux detectes - consensus force", "keywords" => $riskyDetected]); } // STEP 1.5 — CONSENSUS MODE: ask N IA for opinion // ========================================================= $consensusResult = null; if ($consensus) { sse('thinking', ['step' => 'consensus', 'msg' => 'Demande avis aux ' . count($consensus_models) . ' IA souveraines...']); $opinionSystem = "Tu es un expert sysadmin/devops consultant. Tu dois donner un avis sur un plan d'exécution proposé par un autre agent. Réponds UNIQUEMENT en JSON strict: {\"vote\":\"approve|reject|modify\",\"confidence\":0-10,\"concerns\":[\"souci 1\",\"souci 2\"],\"suggested_changes\":[\"modif cmd 1\",...] ou [],\"rationale\":\"explication courte\"}"; $opinionUser = "TÂCHE INITIALE:\n$task\n\nPLAN PROPOSÉ par WEVIA Master:\n" . json_encode($planData, JSON_PRETTY_PRINT) . "\n\nDonne ton avis structuré JSON."; $opinions = []; $approve = 0; $reject = 0; $modify = 0; $allConcerns = []; $allSuggestions = []; foreach ($consensus_models as $modelId) { sse('consensus_query', ['model' => $modelId, 'msg' => "Interroge $modelId..."]); $opn = llmCall($opinionSystem, $opinionUser, $modelId, 800, 15); if (!empty($opn['error'])) { $opinions[] = [ 'model' => $modelId, 'provider' => null, 'vote' => 'error', 'error' => $opn['error'] ]; sse('consensus_response', end($opinions)); continue; } $parsed = parsePlanJson($opn['content']); if (!$parsed || !isset($parsed['vote'])) { $opinions[] = [ 'model' => $modelId, 'provider' => $opn['provider'], 'vote' => 'unparseable', 'raw' => substr($opn['content'], 0, 200) ]; sse('consensus_response', end($opinions)); continue; } $vote = strtolower(trim($parsed['vote'])); if ($vote === 'approve') $approve++; elseif ($vote === 'reject') $reject++; elseif ($vote === 'modify') $modify++; $entry = [ 'model' => $modelId, 'provider' => $opn['provider'], 'vote' => $vote, 'confidence' => $parsed['confidence'] ?? null, 'concerns' => $parsed['concerns'] ?? [], 'suggested_changes' => $parsed['suggested_changes'] ?? [], 'rationale' => substr($parsed['rationale'] ?? '', 0, 300) ]; $opinions[] = $entry; foreach ($entry['concerns'] as $c) $allConcerns[] = $c; foreach ($entry['suggested_changes'] as $s) $allSuggestions[] = $s; sse('consensus_response', $entry); } // Vote tally $totalVotes = $approve + $reject + $modify; $decision = 'unknown'; if ($totalVotes === 0) { $decision = 'no_quorum'; } elseif ($reject > $approve && $reject > $modify) { $decision = 'rejected_by_majority'; } elseif ($modify >= $approve && $modify > 0) { $decision = 'modify_recommended'; } elseif ($approve > 0 && $approve >= $reject) { $decision = 'approved'; } else { $decision = 'no_consensus'; } $consensusResult = [ 'opinions' => $opinions, 'tally' => ['approve' => $approve, 'reject' => $reject, 'modify' => $modify, 'total' => $totalVotes], 'decision' => $decision, 'concerns_aggregated' => array_slice(array_unique($allConcerns), 0, 8), 'suggestions_aggregated' => array_slice(array_unique($allSuggestions), 0, 8) ]; sse('consensus_decision', $consensusResult); // === Honor decision === if ($decision === 'rejected_by_majority' || $decision === 'no_quorum') { sse('aborted', [ 'reason' => $decision, 'concerns' => $consensusResult['concerns_aggregated'], 'suggestions' => $consensusResult['suggestions_aggregated'] ]); sse('done', ['task' => $task, 'aborted' => true, 'reason' => $decision]); exit; } // === If MODIFY recommended, ask WEVIA Master to revise plan === if ($decision === 'modify_recommended' && !empty($consensusResult['suggestions_aggregated'])) { sse('thinking', ['step' => 'revise', 'msg' => 'Consensus = modify. WEVIA révise le plan avec les suggestions...']); $reviseUser = "TÂCHE: $task\n\nPLAN ORIGINAL:\n" . json_encode($planData) . "\n\nRETOURS DES IA CONSULTANTES:\nConcerns: " . json_encode($consensusResult['concerns_aggregated']) . "\nSuggestions: " . json_encode($consensusResult['suggestions_aggregated']) . "\n\nRévise le plan en intégrant les suggestions. Réponds en JSON strict {plan,commands,risk,rationale}."; $revised = llmCall($systemPlan, $reviseUser, 'cerebras-think', 1500); $revisedData = parsePlanJson($revised['content'] ?? ''); if ($revisedData && !empty($revisedData['commands'])) { $planData = $revisedData; sse('plan_revised', [ 'plan' => $planData['plan'] ?? [], 'commands' => $planData['commands'] ?? [], 'risk' => $planData['risk'] ?? 'unknown', 'integrated_suggestions' => count($consensusResult['suggestions_aggregated']) ]); } } } // ========================================================= // STEP 2 — EXECUTE // ========================================================= if ($dry_run) { sse('done', ['msg' => 'DRY RUN - no execution', 'plan' => $planData, 'consensus' => $consensusResult]); exit; } $results = []; $commands = array_slice($planData['commands'] ?? [], 0, $max_steps); foreach ($commands as $idx => $cmd) { sse('exec_start', ['step' => $idx + 1, 'cmd' => $cmd]); $result = execCmd($cmd, 25); $results[] = $result; sse('exec_result', [ 'step' => $idx + 1, 'cmd' => $cmd, 'ok' => $result['ok'], 'output' => $result['output'] ?? null, 'error' => $result['error'] ?? null, 'duration_ms' => $result['duration_ms'] ?? null ]); if (!$result['ok'] || (strpos($result['output'] ?? '', 'error') !== false && strpos($result['output'] ?? '', 'No such') !== false)) { sse('recovery_thinking', ['msg' => 'Erreur, WEVIA propose fix...']); $recoverPrompt = "Cmd échouée:\n$cmd\nOutput:\n" . substr($result['output'] ?? $result['error'], 0, 500) . "\n\nPropose UNE cmd bash de fix. UNIQUEMENT la commande."; $recover = llmCall("Tu es WEVIA. Réponds UNIQUEMENT avec une cmd bash valide.", $recoverPrompt, 'cerebras-fast', 200); $fixCmd = trim($recover['content'] ?? ''); $fixCmd = preg_replace('/^```(?:bash)?\s*|\s*```$/m', '', $fixCmd); $fixCmd = trim(explode("\n", $fixCmd)[0]); if ($fixCmd && strlen($fixCmd) < 500) { sse('recovery_exec', ['fix_cmd' => $fixCmd]); $fixResult = execCmd($fixCmd, 15); sse('recovery_result', $fixResult); $results[] = $fixResult; } } } // ========================================================= // STEP 3 — SUMMARIZE // ========================================================= sse('thinking', ['step' => 'summarize', 'msg' => 'WEVIA résume...']); $resultsText = ''; foreach ($results as $i => $r) { $resultsText .= "Step " . ($i+1) . " cmd: " . substr($r['cmd'] ?? '', 0, 200) . "\nOutput: " . substr($r['output'] ?? $r['error'] ?? '', 0, 600) . "\n\n"; } $summarizeContext = "Tâche: $task\n\nRésultats:\n$resultsText"; if ($consensusResult) { $summarizeContext .= "\nConsensus: " . $consensusResult['decision'] . " (approve=" . $consensusResult['tally']['approve'] . " reject=" . $consensusResult['tally']['reject'] . " modify=" . $consensusResult['tally']['modify'] . ")"; } $summarize = llmCall( "Tu es WEVIA Master. Résume en français pro, concis. Indique succès/échec + 1-2 next actions.", $summarizeContext, 'cerebras-fast', 600 ); sse('summary', [ 'content' => $summarize['content'] ?? '(no summary)', 'model' => $summarize['model'], 'provider' => $summarize['provider'] ]); sse('done', [ 'task' => $task, 'steps_executed' => count($results), 'success_count' => count(array_filter($results, fn($r) => $r['ok'] ?? false)), 'consensus_used' => $consensus, 'consensus_decision' => $consensusResult['decision'] ?? null ]);