/* KA Guardian — poste de commande (/commander) */ const $ = (s) => document.querySelector(s); const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); const STATUS_FR = { ok: "ok", degraded: "dégradé", broken: "cassé", stale: "endormi" }; const STATE_FR = { open: "en file", dispatched: "envoi…", fixing: "en cours", watching: "surveillance", cooldown: "attente", resolved: "livré", self_healed: "auto-guéri", abandoned: "terminé" }; const KIND_FR = { effort_new: "nouveau connecteur", effort_enrich: "enrichissement", effort_degrade: "inspection dégradés" }; const HINTS = { effort_new: "L'agent cartographie l'existant, découvre des sources québécoises non couvertes (Serper), choisit la meilleure et construit le connecteur complet — testé en volume réel.", effort_enrich: "L'agent maximise la valeur du connecteur choisi : pagination complète, champs des fiches détail, robustesse, détection des retraits — mesuré avant/après.", effort_degrade: "L'agent inspecte tous les connecteurs dégradés de la plateforme (volume < 50 % de la médiane), trie baisse réelle / légitime / blocage, et répare un par un — un commit par connecteur.", }; let STATE = null; async function load() { const r = await fetch("/api/state"); STATE = await r.json(); const id = STATE.identity, num = STATE.agent.replace("ka", ""); const rs = document.documentElement.style; rs.setProperty("--agent", id.accent); rs.setProperty("--lime", id.accent); if (id.accent_soft) rs.setProperty("--lime-soft", id.accent_soft); if (id.accent2) rs.setProperty("--accent-deep", id.accent2); document.title = `Commander — ${STATE.agent.toUpperCase()} Guardian`; $("#wm-name").textContent = "ka·" + num; $("#foot-name").textContent = "ka·" + num; $("#hero-label").textContent = `${STATE.agent} — poste de commande · ${id.tagline}`; $("#siblings").innerHTML = Object.entries(STATE.siblings).map(([a, v]) => `${a}·G`).join(" "); const svcSel = $("#ef-service"); if (!svcSel.options.length) { svcSel.innerHTML = Object.entries(STATE.services) .map(([k, s]) => ``).join(""); } renderEfforts(); } function renderEfforts() { const efforts = STATE.incidents.filter((i) => (i.status_detected || "").startsWith("effort")); $("#efforts-list").innerHTML = efforts.slice(0, 20).map((i) => { const d = JSON.parse(i.detail || "{}"); return `
${esc(KIND_FR[i.status_detected] || i.status_detected)} — ${esc((STATE.services[i.service] || {}).app || i.service)}
${esc(i.source)}${d.note ? " · « " + esc(d.note.slice(0, 80)) + " »" : ""}${d.max_minutes ? " · ≤" + d.max_minutes + " min" : ""}${d.max_cost_usd ? " · ≤" + d.max_cost_usd + " $" : ""}
${STATE_FR[i.state] || esc(i.state)}
`; }).join("") || `
Aucun effort commandé encore — sois le premier à lui donner du travail.
`; } function syncKind() { const kind = $("#ef-kind").value; $("#ef-source-wrap").hidden = kind !== "effort_enrich"; $("#kind-hint").textContent = HINTS[kind] || ""; if (kind === "effort_enrich") fillSources(); } async function fillSources() { const r = await fetch("/api/connectors/" + $("#ef-service").value); const d = await r.json(); $("#ef-source").innerHTML = d.connectors .map((c) => ``).join("") || ``; } async function submitEffort(e) { e.preventDefault(); const msg = $("#ef-msg"), btn = e.target.querySelector("button"); const token = $("#ef-token").value.trim(); if (!token) { msg.className = "cmd-msg err"; msg.textContent = "jeton d'opérateur requis"; return; } localStorage.setItem("ka_token", token); btn.disabled = true; msg.className = "cmd-msg"; msg.textContent = "lancement…"; try { const r = await fetch("/api/admin/effort", { method: "POST", headers: { "Content-Type": "application/json", "X-KA-Token": token }, body: JSON.stringify({ kind: $("#ef-kind").value, service: $("#ef-service").value, source: $("#ef-source").value || "", note: $("#ef-note").value, max_minutes: parseInt($("#ef-minutes").value, 10), max_cost_usd: parseFloat($("#ef-cost").value), }), }); const d = await r.json(); if (r.ok && d.ok) { msg.className = "cmd-msg ok"; msg.textContent = `effort accepté (${d.incident_id}) — la session démarre d'ici ~5 min. Suis-la sur le flux ⚡`; $("#ef-note").value = ""; load(); } else { msg.className = "cmd-msg err"; msg.textContent = "refusé: " + (d.detail || r.status); } } catch (err) { msg.className = "cmd-msg err"; msg.textContent = "erreur: " + err; } finally { btn.disabled = false; } } $("#ef-kind").addEventListener("change", syncKind); $("#ef-service").addEventListener("change", () => { if ($("#ef-kind").value === "effort_enrich") fillSources(); }); $("#effort-form").addEventListener("submit", submitEffort); $("#ef-token").value = localStorage.getItem("ka_token") || ""; load().then(syncKind); setInterval(load, 30000);