Files
html/api/wevia-daily-standup.php
2026-04-16 02:28:32 +02:00

198 lines
11 KiB
PHP

<?php
/**
* WEVIA DAILY STANDUP v1.0 — Agent Meetings System
*
* SCHEDULE:
* - 2x/jour SQUAD meetings (08h + 14h) — agents par domaine
* - 1x/semaine WEEKLY (lundi 09h) — Agent Chef + tous les agents
*
* PROCESS:
* 1. Agent Chef collecte le status de chaque agent
* 2. Chaque agent rapporte: done, blockers, next
* 3. Cerebras 235B synthétise + détecte conflits
* 4. Auto-résolution conflits entre agents
* 5. Action items assignés + Mattermost report
*/
header("Content-Type: application/json; charset=utf-8");
$action = $_GET["action"] ?? "daily";
$secrets = [];
foreach (file("/etc/weval/secrets.env", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $l) {
if (strpos($l, "=") !== false) { list($k, $v) = explode("=", $l, 2); $secrets[trim($k)] = trim($v, " \t\"'"); }
}
function llm_call($prompt) {
global $secrets;
foreach (["CEREBRAS_KEY_2", "CEREBRAS_KEY2", "CEREBRAS_API_KEY"] as $kn) {
$key = $secrets[$kn] ?? "";
if (strlen($key) < 10) continue;
$ch = curl_init("https://api.cerebras.ai/v1/chat/completions");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json", "Authorization: Bearer $key"],
CURLOPT_POSTFIELDS => json_encode(["model" => "qwen-3-235b-a22b-instruct-2507",
"messages" => [["role" => "system", "content" => "Tu es l'Agent Chef WEVIA. Tu animes les daily standups entre agents IA. Sois concis, identifie conflits et propose résolutions."],
["role" => "user", "content" => $prompt]], "max_tokens" => 800])]);
$r = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($code >= 200 && $code < 300) {
$d = json_decode($r, true);
return $d["choices"][0]["message"]["content"] ?? null;
}
}
// Fallback Ollama
$ch = curl_init("http://127.0.0.1:11434/v1/chat/completions");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["model" => "weval-brain-v3",
"messages" => [["role" => "user", "content" => $prompt]], "max_tokens" => 500])]);
$r = curl_exec($ch); curl_close($ch);
$d = json_decode($r, true);
return $d["choices"][0]["message"]["content"] ?? "LLM unavailable";
}
function collect_agent_status($agent_name) {
$statuses = [
"cortex" => function() {
$r = json_decode(@file_get_contents("/var/www/html/api/cortex-report.json") ?: "{}", true);
return ["done" => "13 checks, services " . ($r["checks"]["services"] ?? "?"),
"blockers" => ($r["checks"]["cerebras"] ?? "") === "unavailable" ? "Cerebras 403" : "none",
"next" => "Continue monitoring */4h"];
},
"gap_detector" => function() {
$r = json_decode(@file_get_contents("/var/www/html/api/gap-detector.json") ?: "{}", true);
return ["done" => "Score " . ($r["score"] ?? "?") . "% (" . ($r["wired"] ?? "?") . "/" . ($r["opt_tools_total"] ?? "?") . " wired)",
"blockers" => ($r["not_wired_count"] ?? 0) > 0 ? ($r["not_wired_count"] ?? 0) . " tools not wired" : "none",
"next" => "Wire remaining tools"];
},
"l99" => function() {
$r = json_decode(@file_get_contents("/var/www/html/api/l99-results.json") ?: "{}", true);
$dark = json_decode(@file_get_contents("/var/www/html/api/l99-dark-results.json") ?: "{}", true);
return ["done" => "Functional " . ($r["passed"] ?? "?") . "/" . ($r["total"] ?? "?") . ", Dark " . ($dark["pass"] ?? "?") . "/" . (($dark["pass"]??0)+($dark["fail"]??0)),
"blockers" => "gitleaks+trivy version format mismatch",
"next" => "Fix dark test parsing"];
},
"nonreg" => function() {
$r = json_decode(@file_get_contents("https://weval-consulting.com/api/nonreg-api.php?cat=all") ?: "{}", true);
return ["done" => ($r["pass"] ?? "?") . "/" . ($r["total"] ?? "?") . " (" . ($r["score"] ?? "?") . "%)",
"blockers" => "none", "next" => "Maintain 100%"];
},
"evolution" => function() {
$r = json_decode(@file_get_contents("/var/www/html/api/agent-evolution-report.json") ?: "{}", true);
return ["done" => "System: " . ($r["system"]["routes"] ?? "?") . " routes, " . ($r["system"]["skills"] ?? "?") . " skills",
"blockers" => "Agent PHP syntax to fix",
"next" => "Generate 5 evolution proposals"];
},
"scanner" => function() {
return ["done" => "12 sections wiki scanned", "blockers" => "none", "next" => "Continue */2h"];
},
"security" => function() {
return ["done" => "0 leaks, 92 expositions redacted, CrowdSec active",
"blockers" => "none", "next" => "Continuous monitoring"];
},
"mirofish" => function() {
$up = @file_get_contents("http://127.0.0.1:5001/") !== false;
return ["done" => $up ? "MiroFish UP :5001" : "MiroFish DOWN",
"blockers" => $up ? "none" : "Service down", "next" => "Collaborative tools"];
},
];
if (isset($statuses[$agent_name])) return ($statuses[$agent_name])();
return ["done" => "unknown", "blockers" => "unknown", "next" => "unknown"];
}
function notify_mattermost($text) {
$ch = curl_init("http://localhost:8065/hooks/pt54hzthf3b6pe6rgp1ionipnh");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(["text" => $text]),
CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]);
curl_exec($ch); curl_close($ch);
}
switch ($action) {
case "daily":
$squads = [
"infra" => ["cortex", "gap_detector", "nonreg", "security"],
"innovation" => ["evolution", "scanner", "l99", "mirofish"],
];
$squad = $_GET["squad"] ?? "all";
$report = ["type" => "DAILY STANDUP", "timestamp" => date("Y-m-d H:i"), "squad" => $squad, "agents" => [], "conflicts" => [], "actions" => []];
$agents_to_check = ($squad === "all") ? array_merge(...array_values($squads)) : ($squads[$squad] ?? []);
// Collect status from each agent
foreach ($agents_to_check as $agent) {
$status = collect_agent_status($agent);
$report["agents"][$agent] = $status;
}
// Ask LLM to detect conflicts and propose resolutions
$agent_summary = json_encode($report["agents"], JSON_UNESCAPED_UNICODE);
$analysis = llm_call("Voici le status de chaque agent WEVIA:\n$agent_summary\n\nIdentifie:\n1. CONFLITS entre agents (ex: un agent qui bloque un autre)\n2. PROBLEMES communs\n3. ACTIONS PRIORITAIRES pour aujourd'hui\n4. CONCILIATION si besoin\nRéponds en JSON: {conflicts:[], common_issues:[], priority_actions:[], conciliation:[]}");
$report["ai_analysis"] = $analysis;
// Save report
$date = date("Y-m-d");
$time = date("H-i");
@mkdir("/var/www/html/api/meetings", 0755, true);
file_put_contents("/var/www/html/api/meetings/daily-{$date}-{$time}.json", json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
file_put_contents("/var/www/html/api/meetings/latest-daily.json", json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
// Mattermost notification
$agent_lines = [];
foreach ($report["agents"] as $name => $s) {
$emoji = $s["blockers"] === "none" ? "" : "⚠️";
$agent_lines[] = "$emoji **$name**: {$s['done']} | Blockers: {$s['blockers']}";
}
$mm = "📋 **DAILY STANDUP** — " . date("d/m H:i") . " ($squad)\n" . implode("\n", $agent_lines);
if ($analysis) $mm .= "\n\n🤖 **Agent Chef Analysis:**\n" . substr($analysis, 0, 500);
notify_mattermost($mm);
echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
break;
case "weekly":
// Weekly = daily ALL + evolution proposals + architecture review
$report = ["type" => "WEEKLY", "timestamp" => date("Y-m-d H:i"), "agents" => []];
foreach (["cortex", "gap_detector", "nonreg", "security", "evolution", "scanner", "l99", "mirofish"] as $agent) {
$report["agents"][$agent] = collect_agent_status($agent);
}
// Add system metrics
$report["system"] = [
"routes" => intval(trim(shell_exec("grep -c '// Route' /var/www/html/api/weval-ia-fast.php 2>/dev/null"))),
"skills" => intval(trim(shell_exec("ls -d /opt/deer-flow/skills/weval/*/ 2>/dev/null | wc -l"))),
"wiki" => intval(trim(shell_exec("ls /opt/weval-l99/wiki/*.json 2>/dev/null | wc -l"))),
"crons" => intval(trim(shell_exec("ls /etc/cron.d/weval-* 2>/dev/null | wc -l"))),
"docker" => intval(trim(shell_exec("docker ps | tail -n+2 | wc -l"))),
"disk" => trim(shell_exec("df -h / | tail -1 | awk '{print \$5}'")),
];
// Ask LLM for weekly review
$summary = json_encode(["agents" => $report["agents"], "system" => $report["system"]], JSON_UNESCAPED_UNICODE);
$report["ai_review"] = llm_call("Weekly review WEVIA:\n$summary\n\nDonne: 1) Bilan semaine 2) Top 3 réussites 3) Top 3 problèmes 4) Plan semaine prochaine. Format markdown.");
@mkdir("/var/www/html/api/meetings", 0755, true);
file_put_contents("/var/www/html/api/meetings/weekly-" . date("Y-m-d") . ".json", json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
file_put_contents("/var/www/html/api/meetings/latest-weekly.json", json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
notify_mattermost("📊 **WEEKLY REVIEW** — " . date("d/m") . "\nRoutes: " . $report["system"]["routes"] . " | Skills: " . $report["system"]["skills"] . " | Wiki: " . $report["system"]["wiki"] . "\n\n" . substr($report["ai_review"] ?? "", 0, 800));
echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
break;
case "history":
$files = glob("/var/www/html/api/meetings/*.json");
$history = [];
foreach (array_slice(array_reverse($files), 0, 20) as $f) {
$d = json_decode(file_get_contents($f), true);
$history[] = ["file" => basename($f), "type" => $d["type"] ?? "?", "timestamp" => $d["timestamp"] ?? "?", "agents" => count($d["agents"] ?? [])];
}
echo json_encode(["meetings" => $history, "total" => count($files)], JSON_PRETTY_PRINT);
break;
case "status":
$latest = json_decode(@file_get_contents("/var/www/html/api/meetings/latest-daily.json") ?: "{}", true);
echo json_encode($latest ?: ["status" => "no meeting yet"]);
break;
default:
echo json_encode(["module" => "DAILY STANDUP v1.0", "actions" => ["daily", "weekly", "history", "status"],
"params" => ["daily?squad=infra", "daily?squad=innovation", "daily?squad=all"],
"schedule" => ["squad_infra" => "08:00+14:00", "squad_innovation" => "08:30+14:30", "weekly" => "Monday 09:00"]]);
}