loki-mode 10.8.0 → 10.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +16 -31
- package/autonomy/tui.sh +5 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/api_phases.py +1 -1
- package/dashboard/api_start.py +0 -1
- package/dashboard/server.py +22 -138
- package/docs/COCKPIT-SPEC.md +3 -1
- package/docs/DASHBOARD-9.12-EVIDENCE.md +1 -1
- package/docs/DASHBOARD-ARCHITECTURE.md +13 -11
- package/docs/DASHBOARD_V2_CHECKLIST.md +2 -0
- package/docs/INSTALLATION.md +12 -11
- package/docs/MERGE-DEDUP-MAP.md +1 -1
- package/docs/MERGE-ROUTE-MAP.md +1 -1
- package/docs/MERGE3-PLAN.md +2 -2
- package/docs/R3-COST-OBSERVABILITY-DESIGN.md +2 -2
- package/docs/R4-TRUST-TRAJECTORY-DESIGN.md +1 -1
- package/docs/R5-AUTO-WIKI-DESIGN.md +1 -1
- package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +1 -1
- package/docs/RARV-C-100X-PLAN.md +3 -3
- package/docs/SONNET5-DEFAULT-PLAN.md +2 -2
- package/docs/SYNERGY-TASKS.md +1 -1
- package/docs/TOOL-INTEGRATION.md +1 -1
- package/docs/TOP-100-BACKLOG.md +1 -1
- package/docs/alternative-installations.md +1 -1
- package/docs/architecture/ADR-001-runtime-migration.md +1 -1
- package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +2 -0
- package/docs/audit-logging.md +2 -0
- package/docs/authentication.md +2 -0
- package/docs/authorization.md +2 -0
- package/docs/certification/02-enterprise-features/lab.md +2 -0
- package/docs/certification/02-enterprise-features/lesson.md +2 -0
- package/docs/certification/04-production-deployment/lab.md +2 -0
- package/docs/certification/04-production-deployment/lesson.md +2 -0
- package/docs/control-plane-migration.md +97 -0
- package/docs/dashboard-guide.md +6 -7
- package/docs/dev/release-checklist.md +9 -11
- package/docs/enterprise/integration-cookbook.md +2 -0
- package/docs/enterprise/migration.md +1 -1
- package/docs/enterprise/performance.md +3 -1
- package/docs/enterprise/sdk-guide.md +2 -0
- package/docs/enterprise/security.md +2 -0
- package/docs/plans/FORGE-AUTONOMOUS-QUEUE.md +2 -2
- package/docs/research-2026-07/OBSERVABILITY-TOOL-SPEC.md +1 -1
- package/docs/retrospectives/v7.5.15-honesty-audit.md +2 -2
- package/docs/test-scenarios/enterprise-scenarios.md +2 -2
- package/docs/v10/ARCHITECT-CUTS.md +9 -9
- package/docs/v10/BACKLOG.md +7 -7
- package/docs/v10/BOARD.md +25 -25
- package/docs/v10/CONTROL-PLANE.md +4 -4
- package/docs/v10/CP-ENTERPRISE-UI.md +66 -66
- package/docs/v10/D51-PHASE-B.md +1 -1
- package/docs/v10/DECISIONS.md +3 -3
- package/docs/v10/DEPS.md +10 -10
- package/docs/v10/FAILURE-CLASSES.md +45 -1
- package/docs/v10/LEGACY-REMOVAL.md +2 -2
- package/docs/v10/SWARM.md +2 -2
- package/loki-ts/dist/cockpit.js +11 -11
- package/loki-ts/dist/loki.js +596 -579
- package/magic/core/design_tokens.py +1 -2
- package/magic/core/generator.py +2 -2
- package/magic/tokens/README.md +1 -3
- package/mcp/__init__.py +1 -1
- package/package.json +2 -7
- package/packages/control-plane/dist/server.js +1683 -843
- package/packages/control-plane/src/server/legacy/routes-audit.ts +50 -0
- package/packages/control-plane/src/server/legacy/routes-control.ts +26 -0
- package/packages/control-plane/src/server/legacy/routes-memory.ts +30 -0
- package/packages/control-plane/src/server/legacy/routes.ts +58 -58
- package/packages/control-plane/src/server/legacy/shim.ts +10 -4
- package/packages/control-plane/src/server/routes/checkpoints.ts +171 -0
- package/packages/control-plane/src/server/routes/index.ts +4 -1
- package/packages/control-plane/src/server/routes/memory.ts +219 -0
- package/packages/control-plane/src/server/routes/session_control.ts +155 -0
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/references/magic-modules-patterns.md +2 -2
- package/skills/factory-operations.md +3 -3
- package/skills/magic-modules.md +1 -1
- package/skills/release-cadence.md +1 -1
- package/tools/audit-docs.py +1 -1
- package/tools/index-codebase.py +1 -1
- package/web-app/dist/assets/{AdminPage-DDDvUz0N.js → AdminPage-DqMnfZrH.js} +1 -1
- package/web-app/dist/assets/{Avatar-Dcdz46i6.js → Avatar-BEof29du.js} +1 -1
- package/web-app/dist/assets/{Badge-BB24cCpK.js → Badge-WD53OL5m.js} +1 -1
- package/web-app/dist/assets/{Button-D0-DmPZL.js → Button-Rzk5flhU.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-CdjpU2JW.js → CockpitPage-Bqz3PCMh.js} +1 -1
- package/web-app/dist/assets/{ComparePage-DT5U0y0d.js → ComparePage-Byo2NzAZ.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-lEHcl0EO.js → ErrorBoundary-BuE0kH3W.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-GpnWvdCr.js → EvidenceReceiptPanel-CKFHOPBx.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-C7VOh1B7.js → GitHubIssuesPanel-Df_kTMqI.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-G_kOuINv.js → GitHubPRsPanel-CouvcKrZ.js} +1 -1
- package/web-app/dist/assets/{HomePage-PqRisOny.js → HomePage-dEpLzj0x.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DU7pFAkf.js → LoginPage-6DVjUND5.js} +1 -1
- package/web-app/dist/assets/{MagicPage-C5JWIWMk.js → MagicPage-BDQ9MOfY.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-CbIGS_qo.js → MetricsPage-CXiJ04G_.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-vU1pvdSt.js → NotFoundPage-BaXhZvyj.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-SsixIPPv.js → ProjectPage-BzE4V7Ti.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BfQKSR9k.js → ProjectsPage-ipf6TLMX.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-B8YfXAom.js → SettingsPage-BDxkUKN6.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Ba407f2-.js → ShowcasePage-YQXD72SM.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DzNQhhBS.js → SystemSettingsPage-Dou7zHS0.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-tqUnpZ15.js → TeamsPage-CQarfcYr.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-ClB5EpI3.js → TemplatesPage-CPi1R8Xj.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CBvpYfEu.js → TerminalOutput-WiGhWdYD.js} +1 -1
- package/web-app/dist/assets/{activity-C1s0L_X9.js → activity-DOa7ejQA.js} +1 -1
- package/web-app/dist/assets/{bell-DMEiXr73.js → bell-CX-VAlYN.js} +1 -1
- package/web-app/dist/assets/{bot-BF_xIOxu.js → bot-EDXaZmHc.js} +1 -1
- package/web-app/dist/assets/{check-Dv1Ynvrs.js → check-CnBdKk3L.js} +1 -1
- package/web-app/dist/assets/{chevron-left-Ddhsod7S.js → chevron-left-BJ_ydRQW.js} +1 -1
- package/web-app/dist/assets/{circle-alert-OATHUkBZ.js → circle-alert-tz8ufhc_.js} +1 -1
- package/web-app/dist/assets/{clock-C1Ltva07.js → clock-DFBZPSsH.js} +1 -1
- package/web-app/dist/assets/{cloud-B-S4oOy9.js → cloud-uy3p_5Q7.js} +1 -1
- package/web-app/dist/assets/{code-xml-D3Femn_F.js → code-xml-3CbYD9YL.js} +1 -1
- package/web-app/dist/assets/{database-DBSeFgO3.js → database-YiNBXc9c.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-sgDgNuGE.js → dollar-sign-D0wBa4Cv.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-C-MbdZEc.js → file-code-corner-DDdZVZ5v.js} +1 -1
- package/web-app/dist/assets/{file-plus-CCB6yqXv.js → file-plus-D09LTBqt.js} +1 -1
- package/web-app/dist/assets/{globe-DlbrNAj1.js → globe-BirTPwff.js} +1 -1
- package/web-app/dist/assets/{hammer-B-DWm695.js → hammer-DaE5qgnd.js} +1 -1
- package/web-app/dist/assets/{index-Dvny-1SU.js → index-D8t7KnQf.js} +3 -3
- package/web-app/dist/assets/{layers-99qsKpOT.js → layers-DVVrckRY.js} +1 -1
- package/web-app/dist/assets/{loader-circle-ZvZyZl3J.js → loader-circle-2-nMGmcb.js} +1 -1
- package/web-app/dist/assets/{lock-DosGjcxP.js → lock-D0Xj43pC.js} +1 -1
- package/web-app/dist/assets/{package-C-PumesN.js → package-BB7S3egm.js} +1 -1
- package/web-app/dist/assets/{plus-CTYYskTB.js → plus-BLhoMrn3.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-qW2ztTFS.js → refresh-cw-DMc2uPLw.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-ZIKrf-vO.js → rotate-ccw-DM3IP9x7.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BamlXjAT.js → scroll-text-lH2uW7rB.js} +1 -1
- package/web-app/dist/assets/{server-BljXb79I.js → server-BkerZcdR.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Dr6eT9c_.js → shield-alert-B8ohcUfu.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-v3oBXZgU.js → thumbs-up-CTIxLzPD.js} +1 -1
- package/web-app/dist/assets/{trash-2-BOuIrCpi.js → trash-2-C3zIpsH5.js} +1 -1
- package/web-app/dist/assets/{trending-down-Dp7jMCgH.js → trending-down-BTT7wV4Y.js} +1 -1
- package/web-app/dist/assets/{trending-up-CDiur-nP.js → trending-up-BzHdCDvF.js} +1 -1
- package/web-app/dist/assets/{upload-DxIHXhNE.js → upload-HxR34-A8.js} +1 -1
- package/web-app/dist/assets/{usePolling-D2Uu-YZb.js → usePolling-DDj6Fupy.js} +1 -1
- package/web-app/dist/assets/{user-BVWCUAfF.js → user-qH5y9yNy.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/dashboard/static/assets/mermaid.min.js +0 -2030
- package/dashboard/static/cost.html +0 -344
- package/dashboard/static/favicon.svg +0 -5
- package/dashboard/static/index.html +0 -15577
- package/dashboard/static/proofs.html +0 -194
- package/dashboard/static/start.html +0 -131
- package/dashboard/static/trust.html +0 -325
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<!--
|
|
3
|
-
Loki Mode - Proofs index (zero-build standalone fallback, R1 Slice C).
|
|
4
|
-
|
|
5
|
-
Self-contained: all CSS inlined, no external resources. Fetches /api/proofs
|
|
6
|
-
and links each entry to /api/proofs/<run_id>/html. This page exists so the
|
|
7
|
-
proofs surface works without knowing the dashboard SPA build source dir.
|
|
8
|
-
-->
|
|
9
|
-
<html lang="en">
|
|
10
|
-
<head>
|
|
11
|
-
<meta charset="utf-8">
|
|
12
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
13
|
-
<script>
|
|
14
|
-
// Match the embedding SPA's theme via the ?theme=dark|light param it passes
|
|
15
|
-
// when loading this page in its iframe. Set before paint to avoid a flash;
|
|
16
|
-
// with no/invalid param, OS prefers-color-scheme applies.
|
|
17
|
-
(function () {
|
|
18
|
-
try {
|
|
19
|
-
var t = new URLSearchParams(location.search).get('theme');
|
|
20
|
-
if (t === 'dark' || t === 'light') {
|
|
21
|
-
document.documentElement.setAttribute('data-loki-theme', t);
|
|
22
|
-
}
|
|
23
|
-
} catch (e) { /* no-op: fall back to OS preference */ }
|
|
24
|
-
})();
|
|
25
|
-
</script>
|
|
26
|
-
<title>Loki Mode - Proofs of Run</title>
|
|
27
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
28
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
29
|
-
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
30
|
-
<style>
|
|
31
|
-
/* Match the dashboard SPA design language (cream/light + serif headings),
|
|
32
|
-
so this page reads as one product when embedded in the SPA iframe. The
|
|
33
|
-
local --bg/--panel/--text/--mono/--sans names are kept (the rest of the
|
|
34
|
-
CSS references them) and remapped to the SPA --loki-* palette, with a
|
|
35
|
-
prefers-color-scheme dark variant mirroring the SPA. */
|
|
36
|
-
:root {
|
|
37
|
-
--bg: #FAFAF7; --panel: rgba(255,255,255,0.72); --panel-2: #E8E5DE; --border: rgba(0,0,0,0.08);
|
|
38
|
-
--text: #1A1614; --muted: #4A4640; --faint: #8A857C; --accent: #553DE9;
|
|
39
|
-
--green: #1AAF95; --red: #C04848; --amber: #C4922E;
|
|
40
|
-
--mono: 'JetBrains Mono', ui-monospace, "SF Mono", "Menlo", monospace;
|
|
41
|
-
--sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
42
|
-
--serif: 'DM Serif Display', Georgia, serif;
|
|
43
|
-
}
|
|
44
|
-
/* Dark palette. data-loki-theme (set from the SPA's ?theme= param) wins over
|
|
45
|
-
the OS preference so the iframe follows the SPA's manual Dark toggle. */
|
|
46
|
-
html[data-loki-theme="dark"] {
|
|
47
|
-
--bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
|
|
48
|
-
--text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
|
|
49
|
-
--green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
|
|
50
|
-
}
|
|
51
|
-
@media (prefers-color-scheme: dark) {
|
|
52
|
-
html:not([data-loki-theme]) {
|
|
53
|
-
--bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
|
|
54
|
-
--text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
|
|
55
|
-
--green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
* { box-sizing: border-box; }
|
|
59
|
-
body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--sans); line-height: 1.5; }
|
|
60
|
-
a { color: var(--accent); text-decoration: none; }
|
|
61
|
-
a:hover { text-decoration: underline; }
|
|
62
|
-
.wrap { max-width: 880px; margin: 0 auto; padding: 40px 20px 80px; }
|
|
63
|
-
.head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
|
|
64
|
-
h1 { font-family: var(--serif); font-size: 28px; font-weight: 400; letter-spacing: -0.3px; margin: 0; }
|
|
65
|
-
.head a { font-size: 13px; }
|
|
66
|
-
.sub { color: var(--muted); font-size: 14px; margin: 0 0 26px; }
|
|
67
|
-
.list { display: flex; flex-direction: column; gap: 12px; }
|
|
68
|
-
.row {
|
|
69
|
-
display: block; background: var(--panel); border: 1px solid var(--border);
|
|
70
|
-
border-radius: 12px; padding: 16px 18px; color: var(--text);
|
|
71
|
-
}
|
|
72
|
-
.row:hover { border-color: var(--accent); }
|
|
73
|
-
.row-main { display: block; color: var(--text); }
|
|
74
|
-
.row-main:hover { text-decoration: none; }
|
|
75
|
-
.row .top { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
|
|
76
|
-
.row .rid { font-family: var(--mono); font-size: 14px; font-weight: 600; }
|
|
77
|
-
.row .usd { font-family: var(--mono); font-size: 16px; font-weight: 650; }
|
|
78
|
-
.row .ts { color: var(--faint); font-size: 12px; margin-left: auto; }
|
|
79
|
-
.row .meta { color: var(--muted); font-size: 13px; margin-top: 6px; display: flex; gap: 14px; flex-wrap: wrap; }
|
|
80
|
-
.row .pr { font-size: 13px; margin-top: 8px; }
|
|
81
|
-
.row .pr a { font-family: var(--mono); font-weight: 600; }
|
|
82
|
-
.row .pr .arrow { color: var(--faint); margin: 0 6px; }
|
|
83
|
-
.row .pr .verdict { color: var(--muted); }
|
|
84
|
-
.badge { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--border); }
|
|
85
|
-
.b-approve { color: var(--green); border-color: rgba(52,211,153,0.4); }
|
|
86
|
-
.b-reject { color: var(--red); border-color: rgba(248,113,113,0.4); }
|
|
87
|
-
.b-concern { color: var(--amber); border-color: rgba(251,191,36,0.4); }
|
|
88
|
-
.empty { color: var(--muted); background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 28px; text-align: center; }
|
|
89
|
-
.empty code { font-family: var(--mono); color: var(--text); background: var(--panel-2); padding: 2px 6px; border-radius: 5px; }
|
|
90
|
-
</style>
|
|
91
|
-
</head>
|
|
92
|
-
<body>
|
|
93
|
-
<div class="wrap">
|
|
94
|
-
<div class="head">
|
|
95
|
-
<h1>Proofs of Run</h1>
|
|
96
|
-
<a href="/">Back to dashboard</a>
|
|
97
|
-
</div>
|
|
98
|
-
<p class="sub">Shareable, self-contained proof pages generated at the end of each run. Anyone can open them, no account needed.</p>
|
|
99
|
-
<div id="content"><p class="sub">Loading...</p></div>
|
|
100
|
-
</div>
|
|
101
|
-
<script>
|
|
102
|
-
(function () {
|
|
103
|
-
"use strict";
|
|
104
|
-
function esc(s) {
|
|
105
|
-
s = (s === null || s === undefined) ? "" : String(s);
|
|
106
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
107
|
-
.replace(/"/g, """).replace(/'/g, "'");
|
|
108
|
-
}
|
|
109
|
-
function fmtUsd(n) {
|
|
110
|
-
n = Number(n);
|
|
111
|
-
if (!isFinite(n)) return "";
|
|
112
|
-
var s = n.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
113
|
-
if (s.indexOf(".") === -1) s += ".00";
|
|
114
|
-
else if (s.split(".")[1].length === 1) s += "0";
|
|
115
|
-
return "$" + s;
|
|
116
|
-
}
|
|
117
|
-
// A partly priced run's cost is a lower bound; say so only when a number prints.
|
|
118
|
-
function atLeastUsd(n, partial) {
|
|
119
|
-
var s = fmtUsd(n);
|
|
120
|
-
return (partial === true && s.charAt(0) === "$") ? "at least " + s : s;
|
|
121
|
-
}
|
|
122
|
-
function badgeClass(v) {
|
|
123
|
-
v = String(v || "").toUpperCase();
|
|
124
|
-
if (v.indexOf("APPROVE") === 0 || v === "PASS" || v === "PASSED") return "b-approve";
|
|
125
|
-
if (v.indexOf("REJECT") === 0 || v.indexOf("BLOCK") === 0 || v === "FAIL") return "b-reject";
|
|
126
|
-
if (v.indexOf("CONCERN") === 0) return "b-concern";
|
|
127
|
-
return "";
|
|
128
|
-
}
|
|
129
|
-
// Extract the PR/MR number from a GitHub/GitLab PR URL for the display label.
|
|
130
|
-
// GitHub: .../pull/123 GitLab: .../merge_requests/123. Falls back to the
|
|
131
|
-
// trailing numeric path segment. Returns "" when no number can be found.
|
|
132
|
-
function prNumber(url) {
|
|
133
|
-
var s = String(url || "");
|
|
134
|
-
var m = s.match(/\/(?:pull|merge_requests|pulls)\/(\d+)/);
|
|
135
|
-
if (m) return m[1];
|
|
136
|
-
m = s.match(/\/(\d+)(?:[/?#]|$)/);
|
|
137
|
-
return m ? m[1] : "";
|
|
138
|
-
}
|
|
139
|
-
function render(proofs) {
|
|
140
|
-
var c = document.getElementById("content");
|
|
141
|
-
if (!proofs || proofs.length === 0) {
|
|
142
|
-
c.innerHTML = '<div class="empty">No proofs yet. They are generated automatically at the end of a run' +
|
|
143
|
-
' (set <code>LOKI_PROOF=0</code> to opt out).</div>';
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
var rows = "";
|
|
147
|
-
for (var i = 0; i < proofs.length; i++) {
|
|
148
|
-
var p = proofs[i];
|
|
149
|
-
var rid = esc(p.run_id);
|
|
150
|
-
if (p.error) {
|
|
151
|
-
rows += '<div class="row"><div class="top"><span class="rid">' + rid +
|
|
152
|
-
'</span><span class="ts">Could not read this proof: ' + esc(p.error) + "</span></div></div>";
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
var usd = (p.cost_usd !== null && p.cost_usd !== undefined) ? atLeastUsd(p.cost_usd, p.cost_partial) : "";
|
|
156
|
-
var fc = (p.files_changed !== null && p.files_changed !== undefined) ? (p.files_changed + " files") : "";
|
|
157
|
-
var verdict = p.final_verdict ? '<span class="badge ' + badgeClass(p.final_verdict) + '" title="Recorded council vote, not a verification result">council ' + esc(p.final_verdict) + "</span>" : "";
|
|
158
|
-
var ver = p.loki_version ? ("v" + esc(p.loki_version)) : "";
|
|
159
|
-
var ts = p.generated_at ? esc(p.generated_at) : "";
|
|
160
|
-
var href = p.has_html ? ("/api/proofs/" + encodeURIComponent(p.run_id) + "/html") : ("/api/proofs/" + encodeURIComponent(p.run_id));
|
|
161
|
-
var meta = [usd ? '<span class="usd">' + usd + "</span>" : "", fc, ver].filter(Boolean).join(" ");
|
|
162
|
-
// PR linkage: render "PR #N -> <headline>" only when a PR is attached. The
|
|
163
|
-
// headline is the proof's own deterministic honesty.headline (display only,
|
|
164
|
-
// no client-side verdict logic). No pr_url -> nothing extra is rendered.
|
|
165
|
-
var prBlock = "";
|
|
166
|
-
if (p.pr_url) {
|
|
167
|
-
var num = prNumber(p.pr_url);
|
|
168
|
-
var label = num ? ("PR #" + esc(num)) : "PR";
|
|
169
|
-
var hl = p.headline ? ('<span class="arrow">-></span><span class="verdict">' + esc(p.headline) + "</span>") : "";
|
|
170
|
-
prBlock = '<div class="pr"><a href="' + esc(p.pr_url) +
|
|
171
|
-
'" target="_blank" rel="noopener noreferrer">' + label + "</a>" + hl + "</div>";
|
|
172
|
-
}
|
|
173
|
-
rows += '<div class="row">' +
|
|
174
|
-
'<a class="row-main" href="' + href + '">' +
|
|
175
|
-
'<div class="top"><span class="rid">' + rid + "</span>" + verdict +
|
|
176
|
-
(ts ? '<span class="ts">' + ts + "</span>" : "") + "</div>" +
|
|
177
|
-
(meta ? '<div class="meta">' + meta + "</div>" : "") +
|
|
178
|
-
"</a>" + prBlock +
|
|
179
|
-
"</div>";
|
|
180
|
-
}
|
|
181
|
-
c.innerHTML = '<div class="list">' + rows + "</div>";
|
|
182
|
-
}
|
|
183
|
-
function renderError(msg) {
|
|
184
|
-
document.getElementById("content").innerHTML =
|
|
185
|
-
'<div class="empty">Could not load proofs. ' + esc(msg || "") + "</div>";
|
|
186
|
-
}
|
|
187
|
-
fetch("/api/proofs", { headers: { "Accept": "application/json" } })
|
|
188
|
-
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
|
189
|
-
.then(function (d) { render((d && d.proofs) || []); })
|
|
190
|
-
.catch(function (e) { renderError(e && e.message); });
|
|
191
|
-
})();
|
|
192
|
-
</script>
|
|
193
|
-
</body>
|
|
194
|
-
</html>
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Loki Mode - Get started</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root{--bg:#fafafa;--fg:#1a1a24;--mut:#6b6b7a;--card:#fff;--bd:#dcdce4;--acc:#553DE9;--ok:#1a7f37;--bad:#c62828;--warn:#9a6700}
|
|
9
|
-
@media (prefers-color-scheme:dark){:root{--bg:#14141b;--fg:#ececf3;--mut:#9a9aab;--card:#1d1d27;--bd:#33333f;--acc:#8b78ff;--ok:#4ac26b;--bad:#ff6b6b;--warn:#e3b341}}
|
|
10
|
-
body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.5 system-ui,sans-serif}
|
|
11
|
-
main{max-width:860px;margin:0 auto;padding:24px 16px}
|
|
12
|
-
h1{font-size:22px;margin:0 0 4px}h2{font-size:16px;margin:0 0 8px}
|
|
13
|
-
.card{background:var(--card);border:1px solid var(--bd);border-radius:8px;padding:16px;margin:16px 0}
|
|
14
|
-
.card.off{opacity:.55}
|
|
15
|
-
.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:8px 0}
|
|
16
|
-
input,select{padding:8px;border:1px solid var(--bd);border-radius:6px;background:var(--bg);color:var(--fg);min-width:220px}
|
|
17
|
-
button{padding:8px 14px;border:0;border-radius:6px;background:var(--acc);color:#fff;cursor:pointer}
|
|
18
|
-
button.sec{background:transparent;color:var(--fg);border:1px solid var(--bd)}
|
|
19
|
-
.mut{color:var(--mut)}.ok{color:var(--ok)}.bad{color:var(--bad)}
|
|
20
|
-
table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--bd);vertical-align:top}
|
|
21
|
-
.tag{display:inline-block;padding:0 6px;border:1px solid var(--bd);border-radius:10px;font-size:12px;margin-right:4px}
|
|
22
|
-
.st{font-weight:600}.st.blocked,.st.failed{color:var(--bad)}.st.pr_open,.st.done{color:var(--ok)}.st.running{color:var(--acc)}.st.queued{color:var(--warn)}
|
|
23
|
-
</style>
|
|
24
|
-
</head>
|
|
25
|
-
<body>
|
|
26
|
-
<main>
|
|
27
|
-
<h1>Loki Mode</h1>
|
|
28
|
-
<p class="mut">Connect once, then complete your backlog. Each issue gets its own worktree, branch and PR.</p>
|
|
29
|
-
|
|
30
|
-
<section class="card" id="s1"><h2>1. Provider</h2>
|
|
31
|
-
<div id="s1-state" class="mut">Checking...</div>
|
|
32
|
-
<div class="row"><select id="prov"><option value="claude">claude</option><option value="codex">codex</option></select>
|
|
33
|
-
<input id="key" type="password" autocomplete="off" placeholder="API key (optional if the CLI is logged in)">
|
|
34
|
-
<button id="s1-go">Use provider</button></div><div id="s1-msg"></div></section>
|
|
35
|
-
|
|
36
|
-
<section class="card off" id="s2"><h2>2. GitHub</h2>
|
|
37
|
-
<div id="s2-state" class="mut">Not connected</div>
|
|
38
|
-
<div class="row"><input id="pat" type="password" autocomplete="off" placeholder="Personal access token (repo scope)">
|
|
39
|
-
<button id="s2-go">Connect</button></div>
|
|
40
|
-
<div class="mut">Stored on this machine only (mode 0600). It is never shown again.</div><div id="s2-msg"></div></section>
|
|
41
|
-
|
|
42
|
-
<section class="card off" id="s3"><h2>3. Repository</h2>
|
|
43
|
-
<div class="row"><select id="repos"></select><input id="repo" placeholder="or type owner/repo">
|
|
44
|
-
<button id="s3-go">Choose</button></div><div id="s3-msg"></div></section>
|
|
45
|
-
|
|
46
|
-
<section class="card off" id="s4"><h2>Backlog <span id="bl-repo" class="mut"></span></h2>
|
|
47
|
-
<div class="row"><button id="sel">Complete selected</button><button id="all">Complete all</button>
|
|
48
|
-
<button class="sec" id="refresh">Refresh</button><span class="mut" id="conc"></span></div>
|
|
49
|
-
<table><thead><tr><th></th><th>#</th><th>Issue</th><th>Size</th><th>Status</th></tr></thead><tbody id="rows"></tbody></table>
|
|
50
|
-
<div id="bl-msg"></div></section>
|
|
51
|
-
<section class="card" id="ws"><h2>Workspace runs</h2>
|
|
52
|
-
<div id="ws-body" class="mut">Loading...</div></section>
|
|
53
|
-
</main>
|
|
54
|
-
<script>
|
|
55
|
-
const $=id=>document.getElementById(id);
|
|
56
|
-
const api=async(m,p,b)=>{const r=await fetch(p,{method:m,headers:{'Content-Type':'application/json'},body:b?JSON.stringify(b):undefined});
|
|
57
|
-
let j={};try{j=await r.json()}catch(e){}if(!r.ok)throw new Error(j.detail||('HTTP '+r.status));return j};
|
|
58
|
-
const say=(id,t,bad)=>{$(id).textContent=t;$(id).className=bad?'bad':'ok'};
|
|
59
|
-
const el=(t,txt,cls)=>{const e=document.createElement(t);if(txt!=null)e.textContent=txt;if(cls)e.className=cls;return e};
|
|
60
|
-
let issues=[],status={};
|
|
61
|
-
async function state(){
|
|
62
|
-
const s=await api('GET','/api/onboarding/state');
|
|
63
|
-
$('s1-state').textContent=s.provider.configured?('Using '+s.provider.name):
|
|
64
|
-
(s.provider.detected.length?'Detected: '+s.provider.detected.map(d=>d.name+' '+d.version).join(', '):'No claude or codex CLI found; enter an API key.');
|
|
65
|
-
if(s.provider.detected.length&&!s.provider.configured)$('prov').value=s.provider.detected[0].name;
|
|
66
|
-
$('s2').classList.toggle('off',!s.provider.configured);
|
|
67
|
-
$('s2-state').textContent=s.github.configured?('Connected as '+s.github.login):'Not connected';
|
|
68
|
-
$('s3').classList.toggle('off',!s.github.configured);
|
|
69
|
-
if(s.github.configured&&!$('repos').options.length){try{const r=await api('GET','/api/onboarding/repos');
|
|
70
|
-
r.repos.forEach(x=>$('repos').appendChild(el('option',x)))}catch(e){say('s3-msg',e.message,1)}}
|
|
71
|
-
$('s4').classList.toggle('off',!s.repo);
|
|
72
|
-
if(s.repo){$('bl-repo').textContent=s.repo;if(!issues.length)await loadIssues()}
|
|
73
|
-
}
|
|
74
|
-
async function loadIssues(){try{issues=(await api('GET','/api/backlog/issues')).issues;render()}catch(e){say('bl-msg',e.message,1)}}
|
|
75
|
-
function render(){
|
|
76
|
-
const tb=$('rows');tb.replaceChildren();
|
|
77
|
-
issues.forEach(i=>{const st=status[i.number]||{};const tr=el('tr');
|
|
78
|
-
const cb=el('input');cb.type='checkbox';cb.dataset.n=i.number;cb.style.minWidth='0';tr.appendChild(el('td')).appendChild(cb);
|
|
79
|
-
tr.appendChild(el('td','#'+i.number));
|
|
80
|
-
const td=el('td');const a=el('a',i.title);a.href=i.url||'#';a.target='_blank';a.rel='noopener';td.appendChild(a);
|
|
81
|
-
i.labels.forEach(l=>td.appendChild(el('span',l,'tag')));tr.appendChild(td);
|
|
82
|
-
tr.appendChild(el('td',i.size));
|
|
83
|
-
const sc=el('td');sc.appendChild(el('span',st.status||'-','st '+(st.status||'')));
|
|
84
|
-
if(st.pr_url){sc.appendChild(document.createTextNode(' '));const p=el('a','PR');p.href=st.pr_url;p.target='_blank';p.rel='noopener';sc.appendChild(p)}
|
|
85
|
-
if(st.question)sc.appendChild(el('div','Question: '+st.question,'mut'));
|
|
86
|
-
if(st.resume_hint)sc.appendChild(el('div',st.resume_hint,'mut'));
|
|
87
|
-
if(st.detail)sc.appendChild(el('div',st.detail,'mut'));
|
|
88
|
-
tr.appendChild(sc);tb.appendChild(tr)})}
|
|
89
|
-
async function poll(){try{const s=await api('GET','/api/backlog/status');status={};s.issues.forEach(i=>status[i.number]=i);
|
|
90
|
-
$('conc').textContent='concurrency '+s.concurrency;if(issues.length)render()}catch(e){}}
|
|
91
|
-
async function loadWorkspaceRuns(){
|
|
92
|
-
const b=$('ws-body');
|
|
93
|
-
let r;
|
|
94
|
-
try{r=await api('GET','/api/operator/workspaces/runs')}
|
|
95
|
-
catch(e){b.replaceChildren(el('div','Could not load workspace runs: '+e.message,'bad'));return}
|
|
96
|
-
const runs=Array.isArray(r&&r.runs)?r.runs:null;
|
|
97
|
-
if(!runs){b.replaceChildren(el('div','Could not load workspace runs: unexpected response','bad'));return}
|
|
98
|
-
if(!runs.length){b.replaceChildren(el('div','No runs'+(r.reason?' ('+r.reason+')':''),'mut'));return}
|
|
99
|
-
const t=el('table');
|
|
100
|
-
t.appendChild(el('thead')).appendChild(el('tr')).append(el('th','Run'),el('th','Repo'),el('th','Status'),el('th','Freshness'));
|
|
101
|
-
const tb=el('tbody');
|
|
102
|
-
runs.forEach(run=>{
|
|
103
|
-
const id=(run.workspace||'?')+'/'+(run.run_id||'?');
|
|
104
|
-
if(run.state==='unreadable'){
|
|
105
|
-
const tr=el('tr');tr.appendChild(el('td',id));
|
|
106
|
-
const td=el('td','Unreadable run: '+(run.error||'unknown error'),'bad');td.colSpan=3;tr.appendChild(td);tb.appendChild(tr);return}
|
|
107
|
-
const oc=run.outcomes&&typeof run.outcomes==='object'?run.outcomes:{};
|
|
108
|
-
(run.repos||[]).forEach(rp=>{
|
|
109
|
-
const rs=oc[rp.repo]==null?'':String(oc[rp.repo]);
|
|
110
|
-
const tr=el('tr');tr.appendChild(el('td',id));tr.appendChild(el('td',rp.repo||'?'));
|
|
111
|
-
tr.appendChild(el('td',rs||'-','st '+(rs.startsWith('ok')?'ok':rs?'bad':'')));
|
|
112
|
-
const f=el('td');
|
|
113
|
-
if(rp.stale===true)f.appendChild(el('span','stale','tag bad'));
|
|
114
|
-
else if(rp.stale==null&&rp.reason)f.appendChild(el('span',rp.reason,'mut'));
|
|
115
|
-
tr.appendChild(f);tb.appendChild(tr)});
|
|
116
|
-
const ig=run.status==null?'':String(run.status);
|
|
117
|
-
const tr=el('tr');tr.appendChild(el('td',id));tr.appendChild(el('td','integration','mut'));
|
|
118
|
-
const sc=el('td');sc.appendChild(el('span',ig||'-','st '+(ig||'')));tr.appendChild(sc);tr.appendChild(el('td'));tb.appendChild(tr)});
|
|
119
|
-
t.appendChild(tb);b.replaceChildren(t)}
|
|
120
|
-
async function go(btn,msg,fn){btn.disabled=true;try{await fn();say(msg,'Done')}catch(e){say(msg,e.message,1)}btn.disabled=false;await state()}
|
|
121
|
-
$('s1-go').onclick=e=>go(e.target,'s1-msg',async()=>{await api('POST','/api/onboarding/provider',{provider:$('prov').value,api_key:$('key').value||null});$('key').value=''});
|
|
122
|
-
$('s2-go').onclick=e=>go(e.target,'s2-msg',async()=>{await api('POST','/api/onboarding/github',{token:$('pat').value});$('pat').value=''});
|
|
123
|
-
$('s3-go').onclick=e=>go(e.target,'s3-msg',async()=>{await api('POST','/api/onboarding/repo',{repo:$('repo').value||$('repos').value});issues=[]});
|
|
124
|
-
$('sel').onclick=e=>go(e.target,'bl-msg',async()=>{const n=[...document.querySelectorAll('#rows input:checked')].map(c=>+c.dataset.n);
|
|
125
|
-
if(!n.length)throw new Error('Select at least one issue');await api('POST','/api/backlog/run',{issues:n});await poll()});
|
|
126
|
-
$('all').onclick=e=>go(e.target,'bl-msg',async()=>{await api('POST','/api/backlog/run',{all:true});await poll()});
|
|
127
|
-
$('refresh').onclick=loadIssues;
|
|
128
|
-
state();loadWorkspaceRuns();setInterval(poll,3000);
|
|
129
|
-
</script>
|
|
130
|
-
</body>
|
|
131
|
-
</html>
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<!--
|
|
3
|
-
Loki Mode - Trust trajectory panel (R4, zero-build standalone).
|
|
4
|
-
|
|
5
|
-
Self-contained: all CSS + JS inlined, no external resources. Fetches
|
|
6
|
-
/api/trust/trajectory and renders, per project over runs/time, whether the
|
|
7
|
-
agent is EARNING autonomy on THIS repo: council pass-rate, gate pass-rate,
|
|
8
|
-
iterations-to-completion, and (when recorded) human interventions, each with
|
|
9
|
-
an up/down/flat direction and an inline-SVG sparkline.
|
|
10
|
-
|
|
11
|
-
The story no competitor tells. Honest-data rule: with fewer than 2 runs this
|
|
12
|
-
shows "not enough history yet", never a fabricated trend.
|
|
13
|
-
-->
|
|
14
|
-
<html lang="en">
|
|
15
|
-
<head>
|
|
16
|
-
<meta charset="utf-8">
|
|
17
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
18
|
-
<script>
|
|
19
|
-
// Match the embedding SPA's theme. The SPA passes ?theme=dark|light when it
|
|
20
|
-
// loads this page in its iframe (it toggles via data-loki-theme, which an
|
|
21
|
-
// iframe cannot read). Set the attribute before paint to avoid a flash. With
|
|
22
|
-
// no/invalid param, leave it unset so the OS prefers-color-scheme applies.
|
|
23
|
-
(function () {
|
|
24
|
-
try {
|
|
25
|
-
var t = new URLSearchParams(location.search).get('theme');
|
|
26
|
-
if (t === 'dark' || t === 'light') {
|
|
27
|
-
document.documentElement.setAttribute('data-loki-theme', t);
|
|
28
|
-
}
|
|
29
|
-
} catch (e) { /* no-op: fall back to OS preference */ }
|
|
30
|
-
})();
|
|
31
|
-
</script>
|
|
32
|
-
<title>Loki Mode - Trust Trajectory</title>
|
|
33
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
34
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
35
|
-
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
36
|
-
<style>
|
|
37
|
-
/* Match the dashboard SPA design language (cream/light + serif headings),
|
|
38
|
-
so this page reads as one product when embedded in the SPA iframe. The
|
|
39
|
-
local --bg/--panel/--text/--mono/--sans names are kept (the rest of the
|
|
40
|
-
CSS references them) and remapped to the SPA --loki-* palette, with a
|
|
41
|
-
prefers-color-scheme dark variant mirroring the SPA. */
|
|
42
|
-
:root {
|
|
43
|
-
--bg: #F1F2F6; --panel: rgba(255,255,255,0.72); --panel-2: #E8E5DE; --border: rgba(0,0,0,0.08);
|
|
44
|
-
--text: #1A1614; --muted: #4A4640; --faint: #8A857C; --accent: #553DE9;
|
|
45
|
-
--green: #1AAF95; --red: #C04848; --amber: #C4922E;
|
|
46
|
-
--mono: 'JetBrains Mono', ui-monospace, "SF Mono", "Menlo", monospace;
|
|
47
|
-
--sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
48
|
-
--serif: 'DM Serif Display', Georgia, serif;
|
|
49
|
-
}
|
|
50
|
-
/* Dark palette. Applied when the SPA toggle passed ?theme=dark (sets
|
|
51
|
-
data-loki-theme="dark" on <html>) OR, when no explicit theme was passed,
|
|
52
|
-
when the OS prefers dark. The explicit attribute always wins so the iframe
|
|
53
|
-
follows the SPA's manual Dark toggle, not just the OS setting. */
|
|
54
|
-
html[data-loki-theme="dark"] {
|
|
55
|
-
--bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
|
|
56
|
-
--text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
|
|
57
|
-
--green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
|
|
58
|
-
}
|
|
59
|
-
@media (prefers-color-scheme: dark) {
|
|
60
|
-
html:not([data-loki-theme]) {
|
|
61
|
-
--bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
|
|
62
|
-
--text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
|
|
63
|
-
--green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
* { box-sizing: border-box; }
|
|
67
|
-
body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--sans); line-height: 1.5; }
|
|
68
|
-
a { color: var(--accent); text-decoration: none; }
|
|
69
|
-
a:hover { text-decoration: underline; }
|
|
70
|
-
.wrap { max-width: 960px; margin: 0 auto; padding: 40px 20px 80px; }
|
|
71
|
-
.head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
|
|
72
|
-
h1 { font-family: var(--serif); font-size: 28px; font-weight: 400; letter-spacing: -0.3px; margin: 0; }
|
|
73
|
-
h2 { font-size: 15px; font-weight: 600; color: var(--muted); margin: 30px 0 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
74
|
-
.head a { font-size: 13px; }
|
|
75
|
-
.sub { color: var(--muted); font-size: 14px; margin: 0 0 26px; }
|
|
76
|
-
.cards { display: flex; gap: 14px; flex-wrap: wrap; }
|
|
77
|
-
.card { flex: 1 1 200px; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
|
|
78
|
-
.card .label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
79
|
-
.card .val { font-family: var(--mono); font-size: 26px; font-weight: 650; margin-top: 6px; }
|
|
80
|
-
.card .note { color: var(--faint); font-size: 12px; margin-top: 4px; }
|
|
81
|
-
.axes { display: flex; flex-direction: column; gap: 12px; }
|
|
82
|
-
.axis { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; display: flex; align-items: center; gap: 16px; }
|
|
83
|
-
.axis .meta { flex: 1 1 auto; min-width: 0; }
|
|
84
|
-
.axis .name { font-size: 14px; font-weight: 600; }
|
|
85
|
-
.axis .desc { color: var(--faint); font-size: 12px; margin-top: 2px; }
|
|
86
|
-
.axis .spark { flex: 0 0 200px; }
|
|
87
|
-
.axis .verdict { flex: 0 0 150px; text-align: right; }
|
|
88
|
-
.axis .dir { font-family: var(--mono); font-size: 14px; font-weight: 650; }
|
|
89
|
-
.axis .tag { font-size: 12px; margin-top: 2px; }
|
|
90
|
-
.dir.up { color: var(--green); }
|
|
91
|
-
.dir.down { color: var(--green); }
|
|
92
|
-
.dir.bad { color: var(--red); }
|
|
93
|
-
.dir.flat { color: var(--muted); }
|
|
94
|
-
.tag.good { color: var(--green); }
|
|
95
|
-
.tag.bad { color: var(--red); }
|
|
96
|
-
.tag.flat { color: var(--muted); }
|
|
97
|
-
.tag.na { color: var(--faint); }
|
|
98
|
-
svg { display: block; width: 100%; height: 40px; }
|
|
99
|
-
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
100
|
-
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
|
|
101
|
-
th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; }
|
|
102
|
-
td.num, th.num { text-align: right; font-family: var(--mono); }
|
|
103
|
-
.badge { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--border); }
|
|
104
|
-
.b-approve { color: var(--green); border-color: rgba(52,211,153,0.4); }
|
|
105
|
-
.b-reject { color: var(--red); border-color: rgba(248,113,113,0.4); }
|
|
106
|
-
.empty { color: var(--muted); background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 24px; text-align: center; }
|
|
107
|
-
.empty code { font-family: var(--mono); color: var(--text); background: var(--panel-2); padding: 2px 6px; border-radius: 5px; }
|
|
108
|
-
.headline { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 18px; font-size: 15px; }
|
|
109
|
-
.headline.good { border-color: rgba(52,211,153,0.4); }
|
|
110
|
-
.headline.bad { border-color: rgba(248,113,113,0.4); }
|
|
111
|
-
.mono { font-family: var(--mono); }
|
|
112
|
-
.muted { color: var(--muted); }
|
|
113
|
-
</style>
|
|
114
|
-
</head>
|
|
115
|
-
<body>
|
|
116
|
-
<div class="wrap">
|
|
117
|
-
<div class="head">
|
|
118
|
-
<h1>Trust Trajectory</h1>
|
|
119
|
-
<a href="/">Back to dashboard</a>
|
|
120
|
-
</div>
|
|
121
|
-
<p class="sub">Is the agent earning autonomy on THIS repo? Council pass-rate, gate pass-rate, iterations-to-completion, and human interventions over your run history. Real council and RARV-C data, never a fabricated trend.</p>
|
|
122
|
-
<div id="content"><p class="sub">Loading...</p></div>
|
|
123
|
-
</div>
|
|
124
|
-
<script>
|
|
125
|
-
(function () {
|
|
126
|
-
"use strict";
|
|
127
|
-
function esc(s) {
|
|
128
|
-
s = (s === null || s === undefined) ? "" : String(s);
|
|
129
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
130
|
-
.replace(/"/g, """).replace(/'/g, "'");
|
|
131
|
-
}
|
|
132
|
-
function pct(n) {
|
|
133
|
-
if (n === null || n === undefined) return "n/a";
|
|
134
|
-
n = Number(n);
|
|
135
|
-
if (!isFinite(n)) return "n/a";
|
|
136
|
-
return (n * 100).toFixed(0) + "%";
|
|
137
|
-
}
|
|
138
|
-
function num(n) {
|
|
139
|
-
if (n === null || n === undefined) return "n/a";
|
|
140
|
-
n = Number(n);
|
|
141
|
-
if (!isFinite(n)) return "n/a";
|
|
142
|
-
return String(Math.round(n * 100) / 100);
|
|
143
|
-
}
|
|
144
|
-
function badgeClass(v) {
|
|
145
|
-
v = String(v || "").toUpperCase();
|
|
146
|
-
if (v.indexOf("APPROVE") === 0 || v.indexOf("COMPLETE") === 0 || v === "PASS" || v === "PASSED") return "b-approve";
|
|
147
|
-
if (v.indexOf("REJECT") === 0 || v.indexOf("BLOCK") === 0 || v === "FAIL") return "b-reject";
|
|
148
|
-
return "";
|
|
149
|
-
}
|
|
150
|
-
// Arrow glyphs only (no emoji): ^ up, v down, - flat.
|
|
151
|
-
function arrow(direction) {
|
|
152
|
-
if (direction === "up") return "^";
|
|
153
|
-
if (direction === "down") return "v";
|
|
154
|
-
return "-";
|
|
155
|
-
}
|
|
156
|
-
// Build an inline-SVG sparkline from a numeric series (nulls skipped).
|
|
157
|
-
function sparkline(values, higherIsBetter) {
|
|
158
|
-
var pts = [];
|
|
159
|
-
var idx = [];
|
|
160
|
-
for (var i = 0; i < values.length; i++) {
|
|
161
|
-
var v = values[i];
|
|
162
|
-
if (v === null || v === undefined || !isFinite(Number(v))) continue;
|
|
163
|
-
pts.push(Number(v)); idx.push(i);
|
|
164
|
-
}
|
|
165
|
-
if (pts.length === 0) return '<span class="muted" style="font-size:12px;">no data</span>';
|
|
166
|
-
var W = 200, H = 40, pad = 4;
|
|
167
|
-
var min = Math.min.apply(null, pts), max = Math.max.apply(null, pts);
|
|
168
|
-
var range = (max - min) || 1;
|
|
169
|
-
var coords = [];
|
|
170
|
-
for (var j = 0; j < pts.length; j++) {
|
|
171
|
-
var x = pts.length === 1 ? W / 2 : pad + (j / (pts.length - 1)) * (W - 2 * pad);
|
|
172
|
-
var y = H - pad - ((pts[j] - min) / range) * (H - 2 * pad);
|
|
173
|
-
coords.push(x.toFixed(1) + "," + y.toFixed(1));
|
|
174
|
-
}
|
|
175
|
-
// Color the line by whether the last value is better than the first.
|
|
176
|
-
var stroke = "#9aa1ad";
|
|
177
|
-
if (pts.length >= 2) {
|
|
178
|
-
var rising = pts[pts.length - 1] > pts[0];
|
|
179
|
-
var falling = pts[pts.length - 1] < pts[0];
|
|
180
|
-
if ((rising && higherIsBetter) || (falling && !higherIsBetter)) stroke = "#34d399";
|
|
181
|
-
else if ((rising && !higherIsBetter) || (falling && higherIsBetter)) stroke = "#f87171";
|
|
182
|
-
}
|
|
183
|
-
return '<svg viewBox="0 0 ' + W + ' ' + H + '" preserveAspectRatio="none">' +
|
|
184
|
-
'<polyline fill="none" stroke="' + stroke + '" stroke-width="2" points="' + coords.join(" ") + '"/>' +
|
|
185
|
-
'</svg>';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
var AXIS_DESC = {
|
|
189
|
-
council_pass_rate: "Share of runs the 3-reviewer council approved",
|
|
190
|
-
gate_pass_rate: "Share of quality gates passed per run",
|
|
191
|
-
iterations: "RARV iterations needed to complete a run",
|
|
192
|
-
interventions: "Human interventions needed per run"
|
|
193
|
-
};
|
|
194
|
-
var AXIS_ORDER = ["council_pass_rate", "gate_pass_rate", "iterations", "interventions"];
|
|
195
|
-
|
|
196
|
-
// S-148: named so it can be extracted and unit-tested (see
|
|
197
|
-
// tests/test-trust-headline.js). Every axis insufficient/unavailable ->
|
|
198
|
-
// say so honestly instead of claiming "Stable"; a real flat axis (data
|
|
199
|
-
// present, direction genuinely flat) still reports Stable.
|
|
200
|
-
function trustHeadline(d) {
|
|
201
|
-
var axes = d.axes || {};
|
|
202
|
-
var allInsufficient = AXIS_ORDER.every(function (k) {
|
|
203
|
-
var ax = axes[k];
|
|
204
|
-
return !ax || ax.available === false || ax.insufficient === true;
|
|
205
|
-
});
|
|
206
|
-
if (allInsufficient) return "Not enough history yet.";
|
|
207
|
-
var imp = d.improving_count || 0, reg = d.regressing_count || 0;
|
|
208
|
-
if (imp && !reg) return "Trending more trustworthy: " + imp + " axis improving, none regressing on this repo.";
|
|
209
|
-
if (reg && !imp) return "Trust regressing: " + reg + " axis regressing. Review recent runs.";
|
|
210
|
-
if (imp || reg) return "Mixed: " + imp + " improving, " + reg + " regressing.";
|
|
211
|
-
return "Stable: no significant change across axes yet.";
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function renderAxis(key, ax, series) {
|
|
215
|
-
var label = ax.label || key;
|
|
216
|
-
var desc = AXIS_DESC[key] || "";
|
|
217
|
-
var values = series.map(function (s) { return s[key]; });
|
|
218
|
-
var spark = sparkline(values, !!ax.higher_is_better);
|
|
219
|
-
var verdict;
|
|
220
|
-
if (!ax.available) {
|
|
221
|
-
verdict = '<div class="dir flat">-</div><div class="tag na">no data</div>';
|
|
222
|
-
} else if (ax.insufficient) {
|
|
223
|
-
verdict = '<div class="dir flat">-</div><div class="tag na">need 2+ runs</div>';
|
|
224
|
-
} else {
|
|
225
|
-
var dir = ax.direction || "flat";
|
|
226
|
-
var dirClass = dir === "flat" ? "flat" : (ax.improving ? (dir === "up" ? "up" : "down") : "bad");
|
|
227
|
-
var tagClass = ax.improving === true ? "good" : (ax.improving === false ? "bad" : "flat");
|
|
228
|
-
var tagText = ax.improving === true ? "improving" : (ax.improving === false ? "regressing" : "stable");
|
|
229
|
-
var latestStr = ax.higher_is_better ? pct(ax.latest) : num(ax.latest);
|
|
230
|
-
verdict = '<div class="dir ' + dirClass + '">' + arrow(dir) + ' ' + esc(dir) + '</div>' +
|
|
231
|
-
'<div class="tag ' + tagClass + '">' + tagText + ' (now ' + esc(latestStr) + ')</div>';
|
|
232
|
-
}
|
|
233
|
-
return '<div class="axis">' +
|
|
234
|
-
'<div class="meta"><div class="name">' + esc(label) + '</div>' +
|
|
235
|
-
'<div class="desc">' + esc(desc) + (ax.higher_is_better ? " (higher is better)" : " (lower is better)") + '</div></div>' +
|
|
236
|
-
'<div class="spark">' + spark + '</div>' +
|
|
237
|
-
'<div class="verdict">' + verdict + '</div>' +
|
|
238
|
-
'</div>';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function renderRuns(series) {
|
|
242
|
-
var html = '<h2>Run history</h2>';
|
|
243
|
-
if (!series || series.length === 0) {
|
|
244
|
-
html += '<div class="empty">No completed runs yet. Trust trajectory comes' +
|
|
245
|
-
' from proof-of-run artifacts (<code>.loki/proofs/</code>), written at' +
|
|
246
|
-
' the end of each run.</div>';
|
|
247
|
-
return html;
|
|
248
|
-
}
|
|
249
|
-
var rows = "";
|
|
250
|
-
for (var i = 0; i < series.length; i++) {
|
|
251
|
-
var s = series[i];
|
|
252
|
-
var cp = s.council_pass_rate;
|
|
253
|
-
var verdict = (cp === 1) ? '<span class="badge b-approve">PASS</span>'
|
|
254
|
-
: (cp === 0 ? '<span class="badge b-reject">FAIL</span>' : '<span class="muted">-</span>');
|
|
255
|
-
rows += '<tr><td class="mono">' + esc(s.run_id) + '</td>' +
|
|
256
|
-
'<td class="muted">' + esc(s.generated_at || "") + '</td>' +
|
|
257
|
-
'<td>' + verdict + '</td>' +
|
|
258
|
-
'<td class="num">' + (s.gate_pass_rate === null || s.gate_pass_rate === undefined ? "-" : pct(s.gate_pass_rate)) + '</td>' +
|
|
259
|
-
'<td class="num">' + (s.iterations === null || s.iterations === undefined ? "-" : esc(s.iterations)) + '</td>' +
|
|
260
|
-
'<td class="num">' + (s.interventions === null || s.interventions === undefined ? "-" : esc(s.interventions)) + '</td></tr>';
|
|
261
|
-
}
|
|
262
|
-
html += '<table><thead><tr>' +
|
|
263
|
-
'<th>Run</th><th>When</th><th>Council</th><th class="num">Gates</th>' +
|
|
264
|
-
'<th class="num">Iters</th><th class="num">Interv.</th>' +
|
|
265
|
-
'</tr></thead><tbody>' + rows + '</tbody></table>';
|
|
266
|
-
return html;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function render(d) {
|
|
270
|
-
var c = document.getElementById("content");
|
|
271
|
-
if (d && d.available === false) {
|
|
272
|
-
c.innerHTML = '<div class="empty">Trust trajectory is unavailable in this' +
|
|
273
|
-
' install. ' + esc((d.notes && d.notes[0]) || "") + '</div>';
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
var series = d.series || [];
|
|
277
|
-
if (d.insufficient) {
|
|
278
|
-
var msg = '<div class="headline"><strong>Not enough history yet.</strong><br>' +
|
|
279
|
-
'Trust trajectory needs 2 or more recorded runs to show a direction. ' +
|
|
280
|
-
esc(d.runs_count || 0) + ' run(s) recorded so far. Run <code>loki start</code> again' +
|
|
281
|
-
' and the trend appears here, derived from real council and gate results.</div>';
|
|
282
|
-
c.innerHTML = msg + renderRuns(series);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
var axes = d.axes || {};
|
|
286
|
-
var imp = d.improving_count || 0, reg = d.regressing_count || 0;
|
|
287
|
-
var hClass = (imp && !reg) ? "good" : (reg && !imp ? "bad" : "");
|
|
288
|
-
var headline = trustHeadline(d);
|
|
289
|
-
|
|
290
|
-
var cards = '<div class="cards">' +
|
|
291
|
-
'<div class="card"><div class="label">Runs analyzed</div>' +
|
|
292
|
-
'<div class="val">' + esc(d.runs_count || 0) + '</div>' +
|
|
293
|
-
'<div class="note">from .loki/proofs/</div></div>' +
|
|
294
|
-
'<div class="card"><div class="label">Improving axes</div>' +
|
|
295
|
-
'<div class="val" style="color:var(--green);">' + esc(imp) + '</div>' +
|
|
296
|
-
'<div class="note">good direction</div></div>' +
|
|
297
|
-
'<div class="card"><div class="label">Regressing axes</div>' +
|
|
298
|
-
'<div class="val" style="color:' + (reg ? 'var(--red)' : 'var(--muted)') + ';">' + esc(reg) + '</div>' +
|
|
299
|
-
'<div class="note">needs attention</div></div>' +
|
|
300
|
-
'</div>';
|
|
301
|
-
|
|
302
|
-
var axesHtml = '<h2>Earned-autonomy signals</h2><div class="axes">';
|
|
303
|
-
var order = AXIS_ORDER;
|
|
304
|
-
for (var k = 0; k < order.length; k++) {
|
|
305
|
-
if (axes[order[k]]) axesHtml += renderAxis(order[k], axes[order[k]], series);
|
|
306
|
-
}
|
|
307
|
-
axesHtml += '</div>';
|
|
308
|
-
|
|
309
|
-
c.innerHTML = cards +
|
|
310
|
-
'<div class="headline ' + hClass + '" style="margin-top:14px;">' + esc(headline) + '</div>' +
|
|
311
|
-
axesHtml +
|
|
312
|
-
renderRuns(series);
|
|
313
|
-
}
|
|
314
|
-
function renderError(msg) {
|
|
315
|
-
document.getElementById("content").innerHTML =
|
|
316
|
-
'<div class="empty">Could not load trust data. ' + esc(msg || "") + "</div>";
|
|
317
|
-
}
|
|
318
|
-
fetch("/api/trust/trajectory", { headers: { "Accept": "application/json" } })
|
|
319
|
-
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
|
320
|
-
.then(function (d) { render(d || {}); })
|
|
321
|
-
.catch(function (e) { renderError(e && e.message); });
|
|
322
|
-
})();
|
|
323
|
-
</script>
|
|
324
|
-
</body>
|
|
325
|
-
</html>
|