- /wevia-cockpit.html: 6 tabs lazy-loading iframes (Plan-Exec, Multi-Chat, Multi-Server Dispatch, VNC, WTP, Registry) - Header health bar live: S204 load + CDP 8/8 + WTP 100pct + S95 + S151 + Sovereign 18 providers - /api/wevia-dispatch.php: 9 workers adapters (s204, s95 via sentinel, s151 healthchecks, cerebras/groq/gemini/mistral via sovereign-api 4000, kaggle/hf placeholder) - Aggregate option: WEVIA Master synthese resultats divergents - Decharge CPU S204 en dispatchant LLM vers Cerebras/Groq/Gemini GPU - TEST PASS: dispatch s204+cerebras compte fichiers .php avec synthese aggregee - CF purge Doctrine 314: WEVIA orchestre multi-server, workers en parallele, S204 reste libre
212 lines
8.3 KiB
PHP
212 lines
8.3 KiB
PHP
<?php
|
|
// API: /api/wevia-dispatch.php
|
|
// Wave 314 - Multi-Server Multi-Agent Dispatcher
|
|
// Décharge S204 CPU en distribuant tâches sur S95, S151, GPU providers
|
|
|
|
header('Content-Type: application/json');
|
|
header('Cache-Control: no-store');
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
if (!$input || empty($input['task']) || empty($input['servers'])) {
|
|
echo json_encode(['ok' => 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);
|