feat: overhaul Duriin autonomy console

This commit is contained in:
ImBenji
2026-08-04 21:05:47 +01:00
parent 0724f5dc36
commit 5037e1e192
10 changed files with 793 additions and 374 deletions
+67
View File
@@ -34,6 +34,72 @@ function escapeHtml(s) {
}
function formatNumber(value, compact = false) {
const number = Number(value || 0);
return new Intl.NumberFormat("en-GB", compact ? { notation: "compact", maximumFractionDigits: 1 } : {}).format(number);
}
function formatPercent(value, digits = 1) {
if (value == null || !Number.isFinite(Number(value))) return "—";
return `${(Number(value) * 100).toFixed(digits)}%`;
}
function formatRelative(value) {
if (!value) return "—";
const timestamp = new Date(value.endsWith && value.endsWith("Z") ? value : `${value}Z`).getTime();
if (!Number.isFinite(timestamp)) return value;
const seconds = Math.round((timestamp - Date.now()) / 1000);
const units = [[86400, "day"], [3600, "hour"], [60, "minute"]];
const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
for (const [size, name] of units) {
if (Math.abs(seconds) >= size) return formatter.format(Math.round(seconds / size), name);
}
return "just now";
}
function installChrome() {
const header = document.querySelector("header.app-header");
if (!header) return;
const title = header.querySelector("h1");
if (title) title.innerHTML = `<span class="brand-mark">D</span><span class="brand-copy">Duriin<span>Autonomous intelligence</span></span>`;
const nav = header.querySelector(".tabs");
if (nav && !nav.querySelector('[href="/admin/autonomy"]')) {
nav.insertAdjacentHTML("afterbegin", '<a href="/admin/autonomy">Autonomy</a>');
}
if (nav) {
const path = location.pathname;
nav.querySelectorAll("a").forEach(link => {
const href = link.getAttribute("href");
const active = href === "/admin/autonomy"
? path === href
: href === "/admin/ingest"
? path.startsWith("/admin/ingest")
: href === "/admin/intelligence"
? path.startsWith("/admin/intelligence")
: path === href;
link.classList.toggle("active", active);
});
}
const status = document.createElement("div");
status.className = "rail-status";
status.innerHTML = `<div class="rail-status-label"><span class="rail-status-dot"></span><span id="rail-mode">Connecting</span></div><div class="rail-status-meta" id="rail-meta">Runtime status</div>`;
header.appendChild(status);
api("/admin/api/autonomy/overview").then(data => {
const mode = String(data.mode || "offline").toUpperCase();
document.getElementById("rail-mode").textContent = data.enabled ? `${mode} runtime` : "Runtime unavailable";
document.getElementById("rail-meta").textContent = data.broker?.configured ? `${data.broker.name} · connected` : "Broker not configured";
document.querySelector(".rail-status-dot")?.classList.toggle("live", data.enabled);
}).catch(() => {
document.getElementById("rail-mode").textContent = "Runtime unavailable";
});
}
// ── url query-param helpers ────────────────────────────────────────────────
//
// filters and sort state live in the url so reloads and shared links keep
@@ -106,6 +172,7 @@ function wireOverlays() {
document.addEventListener("DOMContentLoaded", () => {
installChrome();
wireOverlays();
loadGlobalStats();
});
+99
View File
@@ -0,0 +1,99 @@
(function () {
const byId = id => document.getElementById(id);
const count = (rows, key, value) => Number((rows || []).find(row => row[key] === value)?.count || 0);
const sum = (rows, predicate) => (rows || []).filter(predicate).reduce((total, row) => total + Number(row.count || 0), 0);
function renderHypotheses(rows) {
const host = byId("hypothesis-list");
if (!rows?.length) {
host.innerHTML = '<div class="empty-state">No autonomy hypotheses yet. The coordinator is working through the evidence queue.</div>';
return;
}
host.innerHTML = rows.slice(0, 7).map(row => {
const action = row.action || (row.status === "resolved" ? "MEASURED" : "OPEN");
const direction = row.direction === "positive" ? "positive" : "negative";
return `<article class="hypothesis-row">
<div><div class="hypothesis-symbol">${escapeHtml(row.instrument)}</div><div class="direction-tag ${direction}">${escapeHtml(row.direction)}</div></div>
<div><div class="hypothesis-type">${escapeHtml(String(row.event_type || "event").replaceAll("_", " "))}</div><div class="hypothesis-channel">${escapeHtml(row.causal_channel || "Evidence-backed market hypothesis")}</div></div>
<div class="hypothesis-meta">${row.evidence_count || 0} evidence source${row.evidence_count === 1 ? "" : "s"}<br>${row.horizon_days} trading-day horizon<br>${formatRelative(row.created_at)}</div>
<span class="decision-chip ${String(action).toLowerCase()}">${escapeHtml(action)}</span>
</article>`;
}).join("");
}
function renderLedger(rows) {
const host = byId("decision-ledger");
const decisions = (rows || []).filter(row => row.action);
if (!decisions.length) return;
host.innerHTML = decisions.map(row => `<tr>
<td class="mono">${escapeHtml(row.instrument)}</td>
<td><span class="decision-chip ${String(row.action).toLowerCase()}">${escapeHtml(row.action)}</span></td>
<td class="${row.direction === "positive" ? "positive" : "negative"}">${escapeHtml(row.direction)}</td>
<td class="mono">${formatPercent(row.calibrated_probability)}</td>
<td>${row.horizon_days}d</td>
<td class="muted">${formatRelative(row.created_at)}</td>
</tr>`).join("");
}
function render(data) {
if (!data.enabled) throw new Error(data.reason || "Autonomy is unavailable");
const mode = String(data.mode || "shadow").toUpperCase();
const open = count(data.predictionCounts, "status", "open");
const resolved = count(data.predictionCounts, "status", "resolved");
const outcomes = Number(data.outcomes?.total || 0);
const correct = Number(data.outcomes?.correct || 0);
const accuracy = outcomes ? correct / outcomes : null;
const pendingHistorical = count((data.jobs || []).filter(row => row.lane === "historical"), "status", "pending");
const completedJobs = sum(data.jobs, row => row.status === "complete");
const proposals = sum(data.proposalCounts, row => row.status === "accepted");
const decisions = sum(data.decisionCounts, () => true);
byId("execution-mode").textContent = mode;
byId("ledger-mode").textContent = mode;
byId("runtime-execution").textContent = mode;
byId("broker-state").textContent = data.broker?.configured ? `${data.broker.name} connected` : "Broker credentials unavailable";
byId("runtime-state").textContent = `${mode} runtime active`;
byId("hero-title").textContent = mode === "PAPER" ? "Duriin is trading in simulation." : "Duriin is learning before it acts.";
byId("hero-description").textContent = mode === "PAPER"
? "Every order is backed by measured evidence, empirical calibration and deterministic risk policy. No model has direct execution authority."
: "It is turning evidence into hypotheses, waiting for outcomes, and calibrating its judgment without placing broker orders.";
byId("metric-open").textContent = formatNumber(open);
byId("metric-resolved").textContent = `${formatNumber(resolved)} resolved`;
byId("metric-accuracy").textContent = formatPercent(accuracy);
byId("metric-sample").textContent = outcomes ? `${formatNumber(outcomes)} measured outcomes` : "Waiting for outcomes";
byId("metric-alpha").textContent = formatPercent(data.outcomes?.average_excess_return, 2);
byId("metric-universe").textContent = formatNumber(data.allowlistedInstruments, true);
byId("pipe-observe").textContent = formatNumber(completedJobs, true);
byId("pipe-propose").textContent = formatNumber(proposals, true);
byId("pipe-measure").textContent = formatNumber(outcomes, true);
byId("pipe-calibrate").textContent = formatNumber(data.calibration?.length || 0);
byId("pipe-act").textContent = formatNumber(decisions);
byId("freshness").textContent = `Updated ${formatRelative(data.generatedAt)}`;
byId("orbit-value").textContent = formatPercent(accuracy, 0);
byId("accuracy-orbit").style.setProperty("--accuracy", `${(accuracy || 0) * 360}deg`);
byId("perf-resolved").textContent = formatNumber(outcomes);
byId("perf-correct").textContent = formatNumber(correct);
byId("perf-cohorts").textContent = formatNumber(data.calibration?.length || 0);
if (outcomes) byId("performance-note").textContent = `Measured on ${outcomes} matured predictions. Results remain descriptive until the sample is large enough for stable calibration.`;
byId("runtime-queue").textContent = `${formatNumber(pendingHistorical, true)} pending`;
byId("runtime-coordinator").textContent = pendingHistorical ? "Processing" : "Watching";
if (data.account) {
byId("account-equity").textContent = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(data.account.equity || 0);
byId("account-meta").textContent = `${data.account.broker} · sampled ${formatRelative(data.account.captured_at)}`;
}
renderHypotheses(data.latestPredictions);
renderLedger(data.latestPredictions);
}
api("/admin/api/autonomy/overview").then(render).catch(error => {
byId("hero-title").textContent = "Duriin cannot read its autonomy state.";
byId("hero-description").textContent = error.message;
byId("hypothesis-list").innerHTML = '<div class="empty-state">Runtime data is unavailable.</div>';
toast("Autonomy overview unavailable", true);
});
})();