Files
html/api/blade-agent.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);