267 lines
11 KiB
PHP
267 lines
11 KiB
PHP
<?php
|
|
$secrets=[];foreach(file("/etc/weval/secrets.env",2|4) as $l){if(strpos($l,"=")!==false){list($k,$v)=explode("=",$l,2);$secrets[trim($k)]=trim($v," \t\"'");}}
|
|
/**
|
|
* BLADE AI AGENT LOOP — Pattern #18 (Claude Code Core)
|
|
*
|
|
* Transforms Blade AI from "execute 1 command" to:
|
|
* Plan → Execute → Read output → Decide next → Loop until done
|
|
*
|
|
* This is what makes Claude Code powerful:
|
|
* 1. AGENT LOOP: keeps going until task is complete
|
|
* 2. OUTPUT PARSING: reads command results, extracts info
|
|
* 3. ERROR RECOVERY: if error, tries different approach
|
|
* 4. CHAIN OF THOUGHT: AI decides next step based on results
|
|
* 5. MULTI-FILE: can read, edit, create multiple files in sequence
|
|
* 6. CONTEXT CARRY: remembers all previous steps in the chain
|
|
*
|
|
* Endpoint: /api/blade-agent.php?k=BLADE2026&goal=NATURAL_LANGUAGE_GOAL
|
|
*/
|
|
|
|
header("Content-Type: application/json");
|
|
|
|
// Auth
|
|
$key = $_REQUEST["k"] ?? "";
|
|
if ($key !== "BLADE2026") { echo json_encode(["error" => "auth"]); exit; }
|
|
|
|
$goal = $_REQUEST["goal"] ?? "";
|
|
$max_steps = intval($_REQUEST["max_steps"] ?? 15);
|
|
$mode = $_REQUEST["mode"] ?? "auto"; // auto, plan_only, execute
|
|
|
|
if (!$goal) { echo json_encode(["error" => "no goal"]); exit; }
|
|
|
|
// === AI PROVIDERS ===
|
|
function callAI($system, $messages, $timeout = 15) {
|
|
global $secrets; /* OPUS3-BLADE-SCOPE-FIX */
|
|
// OPUS3-SOVEREIGN-FIRST: route via local cascade (auto-retry multi-provider)
|
|
array_unshift($providers, ["url" => "http://127.0.0.1:4000/v1/chat/completions", "key" => "none", "model" => "auto"]);
|
|
$providers = [
|
|
["url" => "https://api.cerebras.ai/v1/chat/completions", "key" => ($secrets["CEREBRAS_API_KEY"] ?? "csk-4wrrhkpr568ry9xx49k9mcynwdx483nx53dd62yh5xedfckh"), "model" => "qwen-3-235b-a22b-instruct-2507" /* OPUS3-BLADE-PROVIDERS-FIX */],
|
|
["url" => "https://api.groq.com/openai/v1/chat/completions", "key" => ($secrets["GROQ_KEY"]??""), "model" => "qwen-3-235b-a22b-instruct-2507-versatile"],
|
|
["url" => "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions", "key" => "sk-34db1ad3152443cd86563d1bfc576c30", "model" => "qwen-plus"],
|
|
];
|
|
|
|
$allMessages = array_merge([["role" => "system", "content" => $system]], $messages);
|
|
|
|
foreach ($providers as $p) {
|
|
$ch = curl_init($p["url"]);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_TIMEOUT => $timeout,
|
|
CURLOPT_HTTPHEADER => ["Content-Type: application/json", "Authorization: Bearer " . $p["key"]],
|
|
CURLOPT_POSTFIELDS => json_encode([
|
|
"model" => $p["model"],
|
|
"messages" => $allMessages,
|
|
"max_tokens" => 2048,
|
|
"temperature" => 0.3
|
|
])
|
|
]);
|
|
$r = curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($code === 200) {
|
|
$d = json_decode($r, true);
|
|
return $d["choices"][0]["message"]["content"] ?? "";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// === EXECUTE COMMAND ===
|
|
function execCommand($cmd, $type = "powershell") {
|
|
$BLADE_API = "https://weval-consulting.com/api/blade-api.php?k=BLADE2026";
|
|
|
|
// Push task and wait for result
|
|
$taskId = "agent_" . uniqid();
|
|
$pushUrl = "$BLADE_API&action=push&type=$type&priority=10&label=" . urlencode("Agent: " . substr($cmd, 0, 50));
|
|
$pushUrl .= "&cmd=" . urlencode($cmd);
|
|
|
|
$ch = curl_init($pushUrl);
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
|
|
$r = json_decode(curl_exec($ch), true);
|
|
curl_close($ch);
|
|
|
|
$pushedId = $r["task"]["id"] ?? "";
|
|
if (!$pushedId) return ["error" => "push_failed", "detail" => $r];
|
|
|
|
// Poll for result (max 60s)
|
|
for ($i = 0; $i < 12; $i++) {
|
|
sleep(5);
|
|
$ch = curl_init("$BLADE_API&action=list");
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]);
|
|
$list = json_decode(curl_exec($ch), true);
|
|
curl_close($ch);
|
|
|
|
foreach ($list["tasks"] ?? [] as $t) {
|
|
if ($t["id"] === $pushedId) {
|
|
if ($t["status"] === "done") {
|
|
return ["ok" => true, "result" => $t["result"] ?? "", "status" => "done"];
|
|
}
|
|
if ($t["status"] === "failed") {
|
|
return ["ok" => false, "error" => $t["error"] ?? "", "status" => "failed"];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ["ok" => false, "error" => "timeout_60s", "status" => "timeout"];
|
|
}
|
|
|
|
// === EXECUTE ON SERVER (via CX/SSH) ===
|
|
function execServer($server, $cmd) {
|
|
if ($server === "s204") {
|
|
$sentinel = "sudo ssh -p 49222 -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i /root/.ssh/wevads_key root@10.1.0.2 " . $cmd;
|
|
$url = "http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=" . urlencode($sentinel);
|
|
} elseif ($server === "s95") {
|
|
$url = "http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=" . urlencode($cmd);
|
|
} elseif ($server === "s151") {
|
|
$sentinel = "sudo ssh -o StrictHostKeyChecking=no ubuntu@151.80.235.110 " . $cmd;
|
|
$url = "http://10.1.0.3:5890/api/sentinel-brain.php?action=exec&cmd=" . urlencode($sentinel);
|
|
} else {
|
|
return ["error" => "unknown server"];
|
|
}
|
|
|
|
// Route through CX
|
|
$cxCmd = "curl -s \"$url\"";
|
|
$encoded = base64_encode($cxCmd);
|
|
$ch = curl_init("https://weval-consulting.com/api/cx");
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_TIMEOUT => 30,
|
|
CURLOPT_POSTFIELDS => "k=WEVADS2026&c=$encoded"
|
|
]);
|
|
$r = json_decode(curl_exec($ch), true);
|
|
curl_close($ch);
|
|
return ["ok" => true, "result" => $r["output"] ?? ""];
|
|
}
|
|
|
|
// === AGENT SYSTEM PROMPT ===
|
|
$AGENT_SYSTEM = <<<SYS
|
|
Tu es l'Agent Autonome BLADE AI de WEVAL Consulting.
|
|
Tu reçois un OBJECTIF et tu dois l'accomplir étape par étape.
|
|
|
|
CAPACITÉS:
|
|
1. exec_blade(cmd): Exécute PowerShell sur le Razer (Windows)
|
|
2. exec_server(server, cmd): Exécute sur s204|s95|s151
|
|
3. read_result(): Lit le résultat de la dernière commande
|
|
4. done(summary): Marque l'objectif comme accompli
|
|
|
|
FORMAT DE RÉPONSE — JSON strict:
|
|
{"action":"exec_blade","cmd":"commande powershell"}
|
|
{"action":"exec_server","server":"s204","cmd":"commande linux"}
|
|
{"action":"done","summary":"Ce qui a été fait"}
|
|
{"action":"think","thought":"Réflexion sur la prochaine étape"}
|
|
|
|
RÈGLES AGENT LOOP:
|
|
1. Analyse l'objectif → décompose en étapes
|
|
2. Exécute UNE commande à la fois
|
|
3. Lis le résultat avant de décider la suite
|
|
4. Si erreur: change d'approche (pattern #3 Circuit Breaker)
|
|
5. Si 2 échecs sur même méthode: STOP + change approche (2-strikes rule)
|
|
6. Maximum {$max_steps} étapes
|
|
7. Quand objectif atteint → action "done"
|
|
|
|
SERVEURS:
|
|
- s204 (204.168.152.13): PRIMARY — Site, WEVIA brain, Docker services, nginx
|
|
- s95 (95.216.167.89): WEVADS — Arsenal, Sentinel, PostgreSQL
|
|
- s151 (151.80.235.110): Ollama, Tracking
|
|
- Blade (Razer local): Windows, Python 3.14, Playwright, Chrome
|
|
|
|
PATHS IMPORTANTS:
|
|
- Brain: /var/www/weval/wevia-ia/weval-chatbot-api.php
|
|
- Blade brain: /var/www/html/api/blade-brain.php
|
|
- GOLD: /opt/wevads/vault/
|
|
- Training: /var/www/weval/wevia-ia/finetune-data/
|
|
SYS;
|
|
|
|
// === AGENT LOOP ===
|
|
$messages = [["role" => "user", "content" => "OBJECTIF: $goal"]];
|
|
$steps = [];
|
|
$done = false;
|
|
|
|
for ($step = 0; $step < $max_steps && !$done; $step++) {
|
|
// Ask AI for next action
|
|
$response = callAI($AGENT_SYSTEM, $messages);
|
|
if (!$response) {
|
|
$steps[] = ["step" => $step + 1, "error" => "AI provider failed"];
|
|
break;
|
|
}
|
|
|
|
// Parse JSON action — robust extraction
|
|
$action = null;
|
|
// Try to find JSON in response
|
|
$start = strpos($response, '{');
|
|
if ($start !== false) {
|
|
$depth = 0; $end = $start;
|
|
for ($j = $start; $j < strlen($response); $j++) {
|
|
if ($response[$j] === '{') $depth++;
|
|
if ($response[$j] === '}') { $depth--; if ($depth === 0) { $end = $j; break; } }
|
|
}
|
|
$jsonStr = substr($response, $start, $end - $start + 1);
|
|
// Fix common AI escaping issues
|
|
$jsonStr = str_replace(["\\'", '\\"', '\\n'], ["'", '"', "\n"], $jsonStr);
|
|
$action = json_decode($jsonStr, true);
|
|
if (!$action) {
|
|
// Try cleaning more aggressively
|
|
$jsonStr = preg_replace('/\\{2,}/', '\\', $jsonStr);
|
|
$action = json_decode($jsonStr, true);
|
|
}
|
|
}
|
|
|
|
if (!$action || !isset($action["action"])) {
|
|
// AI returned text instead of JSON — add to context and retry
|
|
$messages[] = ["role" => "assistant", "content" => $response];
|
|
$messages[] = ["role" => "user", "content" => "Réponds en JSON strict: {\"action\":\"...\",\"cmd\":\"...\"}"];
|
|
$steps[] = ["step" => $step + 1, "type" => "retry", "raw" => substr($response, 0, 200)];
|
|
continue;
|
|
}
|
|
|
|
$stepResult = ["step" => $step + 1, "action" => $action["action"]];
|
|
|
|
switch ($action["action"]) {
|
|
case "exec_blade":
|
|
$result = execCommand($action["cmd"] ?? "", $action["type"] ?? "powershell");
|
|
$stepResult["cmd"] = $action["cmd"];
|
|
$stepResult["result"] = $result;
|
|
$messages[] = ["role" => "assistant", "content" => $response];
|
|
$messages[] = ["role" => "user", "content" => "Résultat: " . json_encode($result) . "\nQuelle est la prochaine étape?"];
|
|
break;
|
|
|
|
case "exec_server":
|
|
$result = execServer($action["server"] ?? "s204", $action["cmd"] ?? "");
|
|
$stepResult["server"] = $action["server"];
|
|
$stepResult["cmd"] = $action["cmd"];
|
|
$stepResult["result"] = $result;
|
|
$messages[] = ["role" => "assistant", "content" => $response];
|
|
$messages[] = ["role" => "user", "content" => "Résultat: " . json_encode($result) . "\nQuelle est la prochaine étape?"];
|
|
break;
|
|
|
|
case "think":
|
|
$stepResult["thought"] = $action["thought"] ?? "";
|
|
$messages[] = ["role" => "assistant", "content" => $response];
|
|
$messages[] = ["role" => "user", "content" => "OK, exécute la prochaine action."];
|
|
break;
|
|
|
|
case "done":
|
|
$stepResult["summary"] = $action["summary"] ?? "";
|
|
$done = true;
|
|
break;
|
|
|
|
default:
|
|
$stepResult["error"] = "unknown action: " . $action["action"];
|
|
$messages[] = ["role" => "assistant", "content" => $response];
|
|
$messages[] = ["role" => "user", "content" => "Action inconnue. Utilise exec_blade, exec_server, think, ou done."];
|
|
}
|
|
|
|
$steps[] = $stepResult;
|
|
}
|
|
|
|
// Output
|
|
echo json_encode([
|
|
"ok" => $done,
|
|
"goal" => $goal,
|
|
"steps" => count($steps),
|
|
"max_steps" => $max_steps,
|
|
"log" => $steps,
|
|
"completed" => $done
|
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|