const navRoot = document.getElementById("nav-root"); const appRoot = document.getElementById("app"); const toastTemplate = document.getElementById("toast-template"); const API = { scenarios: "/api/v1/scenarios", scenario: (key) => `/api/v1/scenarios/${encodeURIComponent(key)}`, reset: "/api/v1/env/reset", step: (envId) => `/api/v1/env/${encodeURIComponent(envId)}/step`, destroy: (envId) => `/api/v1/env/${encodeURIComponent(envId)}`, }; const state = { path: normalizePath(window.location.pathname), scenarios: {}, order: [], builderScenario: null, playground: { envId: null, scenarioKey: null, score: 0, step: 0, maxSteps: 50, taskName: "", description: "", cwd: "~", done: false, }, }; const AUTO_PLAYBOOK = { llm: { log_analysis: ["grep 500 /var/log/app.log"], permission_repair: ["chmod 0755 /home/user/scripts/cleanup.sh"], process_recovery: ["ps", "systemctl restart postgres", "systemctl status postgres"], cascading_db_failure: ["ps", "systemctl restart postgres", "df -h", "rm /var/log/nginx/error.log", "systemctl restart app"], full_incident: ["ps", "df -h", "systemctl restart postgres", "rm /var/log/nginx/error.log", "grep auth /var/log/auth.log"], }, rl: { log_analysis: ["cat /var/log/app.log", "grep 500 /var/log/app.log"], process_recovery: ["systemctl restart postgres", "systemctl status postgres"], cascading_db_failure: ["systemctl restart postgres", "rm /var/log/nginx/error.log", "systemctl restart app"], }, }; function normalizePath(pathname) { const normalized = pathname.replace(/\/+$/, ""); return normalized.length ? normalized : "/"; } function routeKey(path) { if (path === "/builder") return "builder"; if (path === "/playground") return "playground"; if (path === "/arena") return "arena"; return "hub"; } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function splitLines(text) { return String(text || "") .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean); } function getQueryScenario() { const params = new URLSearchParams(window.location.search); return params.get("scenario") || ""; } function showToast(message) { if (!toastTemplate) return; const toast = toastTemplate.content.firstElementChild.cloneNode(true); toast.textContent = message; document.body.appendChild(toast); window.setTimeout(() => toast.remove(), 2200); } async function fetchJSON(url, options = {}) { const config = { ...options }; config.headers = { ...(options.headers || {}), }; if (config.body && !config.headers["Content-Type"]) { config.headers["Content-Type"] = "application/json"; } const response = await fetch(url, config); if (!response.ok) { let details = `${response.status} ${response.statusText}`; try { const payload = await response.json(); if (payload?.detail) { details = payload.detail; } } catch { // Fallback to status text. } throw new Error(details); } return response.json(); } function difficultyMeta(diff) { const value = (diff || "").toLowerCase(); if (value === "easy") { return { label: "SAFE", textClass: "text-chaos-green", bgClass: "bg-chaos-green/10", borderClass: "border-chaos-green/20", specsClass: "text-chaos-green", iconColorClass: "text-chaos-green", }; } if (value === "medium") { return { label: "MEDIUM", textClass: "text-chaos-cyan", bgClass: "bg-chaos-cyan/10", borderClass: "border-chaos-cyan/20", specsClass: "text-chaos-cyan", iconColorClass: "text-chaos-cyan", }; } if (value === "hard") { return { label: "CRITICAL", textClass: "text-chaos-red", bgClass: "bg-chaos-red/10", borderClass: "border-chaos-red/20", specsClass: "text-chaos-red", iconColorClass: "text-chaos-red", }; } return { label: "EXPERT", textClass: "text-chaos-red", bgClass: "bg-chaos-red/10", borderClass: "border-chaos-red/50", specsClass: "text-chaos-red", iconColorClass: "text-chaos-red", }; } function scenarioEntries() { return state.order.map((key) => [key, state.scenarios[key]]).filter(([, value]) => Boolean(value)); } function iconMarkup(key, colorClass) { if (key === "process_recovery" || key === "cascading_db_failure") { return ``; } if (key === "disk_space_crisis") { return ``; } if (key === "security_incident") { return ``; } if (key === "memory_leak") { return ``; } if (key === "network_troubleshooting") { return ``; } return ``; } function renderNav() { const active = routeKey(state.path); const navLink = (href, key, label) => { const activeClass = key === active ? "text-chaos-green border-b-2 border-chaos-green py-5 -mb-[22px]" : "text-chaos-muted"; return `${label}`; }; navRoot.innerHTML = ` `; } function buildManifest(detail) { const lines = [ `scenario: "${detail.name}"`, `key: "${detail.key}"`, `difficulty: ${detail.difficulty}`, `max_steps: ${detail.max_steps}`, "", "faults:", ]; detail.faults.forEach((fault, index) => { lines.push(` - id: "fault-${index}"`); lines.push(` name: "${fault.name}"`); lines.push(` type: "${fault.apply_fn}"`); lines.push(` args: ${JSON.stringify(fault.params)}`); }); lines.push("", "cascades:"); detail.cascades.forEach((cascade) => { lines.push(` - condition: "${cascade.condition_fn}"`); lines.push(` when: ${JSON.stringify(cascade.condition_params)}`); lines.push(` trigger: "${cascade.effect.apply_fn}"`); lines.push(` opts: ${JSON.stringify(cascade.effect.params)}`); }); lines.push("", "objectives:"); detail.objectives.forEach((objective) => { lines.push(` - check: "${objective.check_fn}"`); lines.push(` description: "${objective.description}"`); lines.push(` points: ${objective.points}`); }); return lines.join("\n"); } function metrics(detail) { const faults = detail.faults.length; const cascades = detail.cascades.length; const objectives = detail.objectives.length; const blastRadius = Math.min(99, faults * 3 + cascades * 2 + objectives * 1.2); const complexityBase = detail.difficulty === "expert" ? 0.9 : detail.difficulty === "hard" ? 0.76 : detail.difficulty === "medium" ? 0.58 : 0.42; const complexity = Math.min(0.99, complexityBase + faults * 0.01 + cascades * 0.01); return { faults, cascades, objectives, blastRadius, complexity }; } function renderHub() { const cards = scenarioEntries().map(([key, scenario]) => { const meta = difficultyMeta(scenario.difficulty); return `
${iconMarkup(key, meta.iconColorClass)}
${meta.label}

${escapeHtml(scenario.name)}

${escapeHtml(scenario.description || "")}

${escapeHtml(String(scenario.objectives_count || 0))} OBJS MAX ${escapeHtml(String(scenario.max_steps || 50))} STEPS
`; }).join(""); const featuredKey = state.scenarios.full_incident ? "full_incident" : state.order[0]; const featured = state.scenarios[featuredKey] || null; const featuredName = featured ? featured.name : "Full Incident Response"; const featuredDifficulty = featured ? difficultyMeta(featured.difficulty).specsClass : "text-chaos-red"; const featuredSteps = featured ? String(featured.max_steps || 100) : "100"; const featuredObjectives = featured ? String(featured.objectives_count || 6) : "6"; appRoot.innerHTML = `

ChaosHub

Central nervous system for system resilience. Browse production-hardened tools and scenarios to stress test your architecture.

${cards}
FEATURED SCENARIO

${escapeHtml(featuredName)}

The ultimate challenge. A cascading failure involving database crash, disk overfilling, cron breakdown, and a concurrent security incident. Triage, prioritize, and restore all systems to 100% functionality.

Technical Specs

ComplexityExpert
Max Steps${escapeHtml(featuredSteps)}
Grading Logic${escapeHtml(featuredObjectives)} Objectives
`; const integrations = document.getElementById("integrations-tab"); integrations?.addEventListener("click", () => { showToast("Integrations view is not available yet."); }); } function builderScenarioOptionButtons(selectedKey) { return scenarioEntries().map(([key, meta]) => { return ``; }).join(""); } async function renderBuilder() { const queryScenario = getQueryScenario(); if (!state.builderScenario) { if (state.scenarios[queryScenario]) { state.builderScenario = queryScenario; } else if (state.scenarios.cascading_db_failure) { state.builderScenario = "cascading_db_failure"; } else { state.builderScenario = state.order[0]; } } const key = state.builderScenario; const detail = await fetchJSON(API.scenario(key)); const live = metrics(detail); const faultsMarkup = detail.faults.map((fault) => { return `
${iconMarkup("process_recovery", "w-4 h-4")}
${escapeHtml(fault.name)}
${escapeHtml(fault.apply_fn.replaceAll("_", " "))}
`; }).join(""); const cascadeMarkup = detail.cascades.map((cascade) => { const effectLabel = cascade.effect?.name || cascade.effect?.apply_fn || ""; return `
${escapeHtml(cascade.condition_fn.replaceAll("_", " "))}
→ ${escapeHtml(effectLabel)}
`; }).join(""); const objectiveMarkup = detail.objectives.map((objective) => { return `
${escapeHtml(objective.description)}
${Math.round((objective.points || 0) * 100)}%
`; }).join(""); const formatNodeValue = (paramKey, paramValue) => { if (paramKey === "count") { const numeric = Number(paramValue); if (Number.isFinite(numeric) && numeric >= 1000) { return `${(numeric / 1000).toFixed(1)}K`; } } return String(paramValue); }; const actionDescription = (actionName) => { if (actionName === "fill_disk") return "Log flood fills /var/log partition"; if (actionName === "crash_service" || actionName === "app_degraded") return "App can't write logs, starts failing"; return actionName.replaceAll("_", " "); }; const faultNodes = detail.faults.map((fault, index) => { const id = `${fault.apply_fn.slice(0, 3).toUpperCase()}-${String(index).padStart(2, "0")}`; const paramRows = Object.entries(fault.params || {}).map(([paramKey, paramValue]) => { return `
${escapeHtml(paramKey)}${escapeHtml(formatNodeValue(paramKey, paramValue))}
`; }).join(""); return `
${escapeHtml(fault.name.toUpperCase())}ID: ${escapeHtml(id)}
${iconMarkup("process_recovery", "w-4 h-4")}
${escapeHtml(fault.description || fault.name)}
${paramRows || `
type-
`}
`; }).join(""); const cascadeNodes = detail.cascades.map((cascade, index) => { const cond = cascade.condition_fn.replaceAll("_", " "); const actionKey = String(cascade.effect.apply_fn || ""); const actionText = cascade.effect.description || actionDescription(actionKey); const firstParam = Object.entries(cascade.condition_params || {}).map(([k, v]) => `${k}: ${v}`).join(", "); const actionMeta = Object.entries(cascade.effect.params || {}).map(([k, v]) => `
${escapeHtml(`${k}:`)}${escapeHtml(formatNodeValue(k, v))}
`).join(""); return `
TRIGGER CONDITION#${index + 1}
IF ${escapeHtml(cond)}
(${escapeHtml(firstParam)})
ACTION: ${escapeHtml(actionKey.toUpperCase())}
${escapeHtml(actionText)}
${actionMeta}
`; }).join(""); const objectiveNodes = detail.objectives.map((objective, index) => { return `
OBJ-${String(index + 1).padStart(2, "0")}${Math.round((objective.points || 0) * 100)}%
${escapeHtml(objective.description)}
${escapeHtml(objective.check_fn.replaceAll("_", " "))}
`; }).join(""); const scenarioButtonLabel = state.scenarios[key]?.name || detail.name; const diff = difficultyMeta(detail.difficulty); appRoot.innerHTML = `
Select Scenario
Injected Faults ${detail.faults.length}
${faultsMarkup}
Cascade Rules ${detail.cascades.length}
${cascadeMarkup}
Objectives ${detail.objectives.length}
${objectiveMarkup}
Fault Nodes${detail.faults.length} Active
Cascade Logic${detail.cascades.length} Triggers
Validation Targets${detail.objectives.length} Criteria
${faultNodes}
${cascadeNodes}
${objectiveNodes}
Active Scenario

${escapeHtml(detail.name)}

${diff.label} MAX: ${detail.max_steps} STEPS

${escapeHtml(detail.description)}

Live Projections

Fault Nodes${live.faults}
Cascade Depth${live.cascades}
Objectives${live.objectives}
Blast Radius${live.blastRadius.toFixed(1)}%
Complexity${live.complexity.toFixed(3)}

Hints

    ${detail.hints.map((hint) => `
  • ›${escapeHtml(hint)}
  • `).join("")}

Config Manifest

${escapeHtml(buildManifest(detail))}
`; const trigger = document.getElementById("builder-scenario-trigger"); const menu = document.getElementById("builder-scenario-menu"); trigger?.addEventListener("click", () => { menu?.classList.toggle("hidden"); }); menu?.querySelectorAll("button[data-scenario-key]").forEach((button) => { button.addEventListener("click", async () => { const selected = button.getAttribute("data-scenario-key"); if (!selected) return; state.builderScenario = selected; await renderBuilder(); }); }); [["builder-btn-execute", "Experiment queued."], ["builder-btn-llm", "LLM test started."], ["builder-btn-rl", "RL test started."], ["builder-btn-export", "YAML bundle exported."]].forEach(([id, msg]) => { document.getElementById(id)?.addEventListener("click", () => showToast(msg)); }); } function getRingDash(score) { const pct = Math.max(0, Math.min(99, Math.round(score * 100))); return `${pct}, 100`; } function appendTerminalOutput(line) { const stream = document.getElementById("terminal-stream"); const output = document.getElementById("terminal-output-area"); if (!stream || !output) return; const row = document.createElement("div"); row.className = "mb-2 whitespace-pre-wrap flex flex-col"; row.innerHTML = `
${escapeHtml(line)}
`; stream.appendChild(row); output.scrollTop = output.scrollHeight; } function appendTerminalSystem(line) { const stream = document.getElementById("terminal-stream"); const output = document.getElementById("terminal-output-area"); if (!stream || !output) return; const row = document.createElement("div"); row.className = "mb-2 whitespace-pre-wrap flex flex-col"; row.innerHTML = `
${escapeHtml(line)}
`; stream.appendChild(row); output.scrollTop = output.scrollHeight; } function renderPlaygroundFrame() { const envLabel = "ENV_0"; const scorePct = Math.max(0, Math.min(99, Math.round(state.playground.score * 100))); appRoot.innerHTML = `

${escapeHtml(state.playground.taskName || "Log Analysis")}

ID:
${escapeHtml(envLabel)}

${escapeHtml(state.playground.description || "")}

${scorePct}%
Completion Level
${scorePct} PTS
Step Progress${state.playground.step} / ${state.playground.maxSteps}
Objectives Meta

Complete actions in the terminal and verify fixes to achieve 100% completion in this sandbox.

root@chaoslab-env_0 ~ (ssh)
Auto-Solve:
ChaosLab Live Terminal [Version 2.0.42-STABLE]
(c) 2026 ChaosLab System. All rights reserved.
root@chaoslab:~$
OFFLINE
ENCRYPTION: AES-256-GCM

AI Assistant

OFFLINE
Ask the LLM for help with the current task. Get suggestions for the next command to run.
`; } async function destroyPlaygroundEnv() { if (!state.playground.envId) return; try { await fetchJSON(API.destroy(state.playground.envId), { method: "DELETE" }); } catch { // Ignore cleanup errors. } } function updatePlaygroundWidgets() { const scorePct = Math.max(0, Math.min(99, Math.round(state.playground.score * 100))); const envLabel = "ENV_0"; const progressArc = document.getElementById("playground-progress-arc"); if (progressArc) progressArc.setAttribute("stroke-dasharray", getRingDash(state.playground.score)); const progressText = document.getElementById("playground-progress-text"); if (progressText) progressText.textContent = `${scorePct}%`; const score = document.getElementById("playground-score-points"); if (score) score.textContent = String(scorePct); const step = document.getElementById("playground-step-progress"); if (step) step.textContent = `${state.playground.step} / ${state.playground.maxSteps}`; const name = document.getElementById("playground-task-name"); if (name) name.textContent = state.playground.taskName; const description = document.getElementById("playground-task-desc"); if (description) description.textContent = state.playground.description; const shell = document.getElementById("playground-shell-label"); if (shell) shell.textContent = `root@chaoslab-${envLabel.toLowerCase()} ~ (ssh)`; const prompt = document.getElementById("playground-prompt"); if (prompt) prompt.textContent = `root@chaoslab:${state.playground.cwd}$`; } async function initializePlayground(scenarioKey) { await destroyPlaygroundEnv(); const payload = await fetchJSON(API.reset, { method: "POST", body: JSON.stringify({ scenario: scenarioKey }), }); state.playground.envId = payload.env_id; state.playground.scenarioKey = scenarioKey; state.playground.score = 0; state.playground.step = 0; state.playground.maxSteps = Number(payload.info?.max_steps || 50); state.playground.done = false; state.playground.cwd = "~"; state.playground.taskName = payload.info?.task_name || (state.scenarios[scenarioKey]?.name || "Scenario"); state.playground.description = state.scenarios[scenarioKey]?.description || ""; appendTerminalSystem(":: Successfully initialized sandbox [env_0]"); appendTerminalSystem(`:: Task: ${state.playground.taskName}`); updatePlaygroundWidgets(); } async function runPlaygroundCommand(command) { if (!command || !state.playground.envId || state.playground.done) return; appendTerminalOutput(`root@chaoslab:${state.playground.cwd}$ ${command}`); const payload = await fetchJSON(API.step(state.playground.envId), { method: "POST", body: JSON.stringify({ action: command }), }); const output = payload.info?.command_output || ""; if (output.trim()) { output.split(/\r?\n/).forEach((line) => { if (line.trim()) appendTerminalOutput(line); }); } state.playground.score = Number(payload.info?.task_score || 0); state.playground.step = Number(payload.info?.step || state.playground.step + 1); state.playground.maxSteps = Number(payload.info?.max_steps || state.playground.maxSteps); state.playground.cwd = payload.observation?.current_directory || state.playground.cwd; state.playground.done = Boolean(payload.done); if (state.playground.done) { appendTerminalSystem(":: Sandbox run ended."); } updatePlaygroundWidgets(); } async function renderPlayground() { renderPlaygroundFrame(); const queryScenario = getQueryScenario(); const key = state.scenarios[queryScenario] ? queryScenario : state.order[0]; await initializePlayground(key); const input = document.getElementById("cli-input"); input?.addEventListener("keydown", async (event) => { if (event.key !== "Enter") return; event.preventDefault(); const command = input.value.trim(); if (!command) return; input.value = ""; try { await runPlaygroundCommand(command); } catch (error) { appendTerminalOutput(`error: ${error.message}`); } }); document.getElementById("playground-auto-rl")?.addEventListener("click", async () => { const commands = AUTO_PLAYBOOK.rl[state.playground.scenarioKey] || AUTO_PLAYBOOK.llm[state.playground.scenarioKey] || []; for (const command of commands) { if (state.playground.done) break; try { await runPlaygroundCommand(command); } catch { break; } } }); } function resolveArenaCommands(mode, scriptText, scenarioKey) { const normalized = (mode || "script").toLowerCase(); if (normalized === "script") { const commands = splitLines(scriptText); if (commands.length) return commands; } if (AUTO_PLAYBOOK[normalized]?.[scenarioKey]?.length) { return AUTO_PLAYBOOK[normalized][scenarioKey]; } if (AUTO_PLAYBOOK.llm[scenarioKey]?.length) { return AUTO_PLAYBOOK.llm[scenarioKey]; } return ["ps", "df -h", "free -m"]; } async function runArenaAgent(label, mode, scriptText, scenarioKey) { const commands = resolveArenaCommands(mode, scriptText, scenarioKey); const resetPayload = await fetchJSON(API.reset, { method: "POST", body: JSON.stringify({ scenario: scenarioKey }), }); const envId = resetPayload.env_id; let score = 0; let step = 0; let done = false; try { for (const command of commands) { const payload = await fetchJSON(API.step(envId), { method: "POST", body: JSON.stringify({ action: command }), }); score = Number(payload.info?.task_score || score); step = Number(payload.info?.step || step + 1); done = Boolean(payload.done); if (done) break; } } finally { try { await fetchJSON(API.destroy(envId), { method: "DELETE" }); } catch { // Ignore cleanup error. } } return { label, mode, score, step, done }; } function renderArenaResult(resultA, resultB) { const winner = resultA.score > resultB.score ? resultA.label : resultB.score > resultA.score ? resultB.label : resultA.step < resultB.step ? resultA.label : resultB.step < resultA.step ? resultB.label : "DRAW"; return `

${winner === "DRAW" ? "Result: Draw" : `Winner: ${winner}`}

Agent Mode Score Steps Status
${escapeHtml(resultA.label)} ${escapeHtml(resultA.mode)} ${Math.round(resultA.score * 100)}% ${resultA.step} ${resultA.done ? "DONE" : "INCOMPLETE"}
${escapeHtml(resultB.label)} ${escapeHtml(resultB.mode)} ${Math.round(resultB.score * 100)}% ${resultB.step} ${resultB.done ? "DONE" : "INCOMPLETE"}
`; } function renderArena() { const options = scenarioEntries().map(([key, meta]) => { return ``; }).join(""); appRoot.innerHTML = `
root@chaoslab-terminal / ARENA / AGENT_DUEL_V3.4

Battle Scenario

Compare user or autonomous scripts against high-stakes environments. Define identical testing parameters and observe how different instruction sets handle cascading service disruptions.

Active Environment Overlay
READY
A

AGENT_ALPHA

B

AGENT_BETA

RL AUTONOMOUS MODE

Model: Heuristic Expert

`; document.getElementById("arena-run")?.addEventListener("click", async () => { const scenarioKey = document.getElementById("arena-scenario")?.value || state.order[0]; const modeA = document.getElementById("arena-mode-a")?.value || "script"; const modeB = document.getElementById("arena-mode-b")?.value || "rl"; const scriptA = document.getElementById("arena-script-a")?.value || ""; const scriptB = document.getElementById("arena-script-b")?.value || ""; const runButton = document.getElementById("arena-run"); const resultNode = document.getElementById("arena-result"); if (runButton) runButton.setAttribute("disabled", "disabled"); if (resultNode) { resultNode.innerHTML = `
Running race...
`; } try { const [resultA, resultB] = await Promise.all([ runArenaAgent("AGENT_ALPHA", modeA, scriptA, scenarioKey), runArenaAgent("AGENT_BETA", modeB, scriptB, scenarioKey), ]); if (resultNode) { resultNode.innerHTML = renderArenaResult(resultA, resultB); } } catch (error) { if (resultNode) { resultNode.innerHTML = `
Race failed: ${escapeHtml(error.message)}
`; } } finally { if (runButton) runButton.removeAttribute("disabled"); } }); } function renderError(message) { appRoot.innerHTML = `

Frontend failed to initialize

${escapeHtml(message)}

`; } async function hydrateScenarios() { const payload = await fetchJSON(API.scenarios); state.scenarios = payload.scenarios || {}; state.order = Object.keys(state.scenarios); } async function mount() { try { await hydrateScenarios(); renderNav(); if (routeKey(state.path) === "builder") { await renderBuilder(); return; } if (routeKey(state.path) === "playground") { await renderPlayground(); return; } if (routeKey(state.path) === "arena") { renderArena(); return; } renderHub(); } catch (error) { renderNav(); renderError(error.message || "Unknown error"); } } window.addEventListener("pagehide", () => { destroyPlaygroundEnv(); }); mount();