Files
html/api/wevia-dispatch.php
Opus 3563269463 feat(cockpit): wave 314 unified UI + multi-server dispatcher
- /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
2026-04-24 13:21:59 +02:00

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);