loki-mode 10.8.0 → 10.10.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.
Files changed (178) hide show
  1. package/.github/actions/issue-to-pr/action.yml +2 -2
  2. package/README.md +11 -5
  3. package/SKILL.md +12 -2
  4. package/VERSION +1 -1
  5. package/autonomy/lib/proof-generator.py +1 -0
  6. package/autonomy/loki +55 -40
  7. package/autonomy/receipt_jwt.py +46 -1
  8. package/autonomy/run.sh +1 -1
  9. package/autonomy/telemetry.sh +1 -1
  10. package/autonomy/trigger-server.py +1 -0
  11. package/autonomy/tui.sh +5 -1
  12. package/bin/loki +12 -1
  13. package/bin/loki-postinstall.js +12 -0
  14. package/dashboard/__init__.py +1 -1
  15. package/dashboard/api_phases.py +1 -1
  16. package/dashboard/api_start.py +0 -1
  17. package/dashboard/server.py +22 -138
  18. package/docs/CLI-REFERENCE.md +139 -0
  19. package/docs/COCKPIT-SPEC.md +3 -1
  20. package/docs/DASHBOARD-9.12-EVIDENCE.md +1 -1
  21. package/docs/DASHBOARD-ARCHITECTURE.md +13 -11
  22. package/docs/DASHBOARD_V2_CHECKLIST.md +2 -0
  23. package/docs/INSTALLATION.md +12 -11
  24. package/docs/LOKI-10-BUILD-PROMPT.md +2 -2
  25. package/docs/MERGE-DEDUP-MAP.md +1 -1
  26. package/docs/MERGE-ROUTE-MAP.md +1 -1
  27. package/docs/MERGE3-PLAN.md +2 -2
  28. package/docs/PLANS-INDEX.md +2 -3
  29. package/docs/R3-COST-OBSERVABILITY-DESIGN.md +2 -2
  30. package/docs/R4-TRUST-TRAJECTORY-DESIGN.md +1 -1
  31. package/docs/R5-AUTO-WIKI-DESIGN.md +1 -1
  32. package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +1 -1
  33. package/docs/RARV-C-100X-PLAN.md +3 -3
  34. package/docs/SLO.md +3 -3
  35. package/docs/SONNET5-DEFAULT-PLAN.md +2 -2
  36. package/docs/SYNERGY-TASKS.md +1 -1
  37. package/docs/TOOL-INTEGRATION.md +1 -1
  38. package/docs/TOP-100-BACKLOG.md +1 -1
  39. package/docs/alternative-installations.md +2 -2
  40. package/docs/architecture/ADR-001-runtime-migration.md +1 -1
  41. package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +2 -0
  42. package/docs/audit-logging.md +2 -0
  43. package/docs/authentication.md +2 -0
  44. package/docs/authorization.md +2 -0
  45. package/docs/bug-fixes/agent-04-memory-fixes.md +1 -1
  46. package/docs/certification/02-enterprise-features/lab.md +2 -0
  47. package/docs/certification/02-enterprise-features/lesson.md +2 -0
  48. package/docs/certification/04-production-deployment/lab.md +2 -0
  49. package/docs/certification/04-production-deployment/lesson.md +2 -0
  50. package/docs/control-plane-migration.md +97 -0
  51. package/docs/dashboard-guide.md +6 -7
  52. package/docs/dev/release-checklist.md +10 -12
  53. package/docs/enterprise/integration-cookbook.md +2 -0
  54. package/docs/enterprise/migration.md +1 -1
  55. package/docs/enterprise/performance.md +3 -1
  56. package/docs/enterprise/sdk-guide.md +2 -0
  57. package/docs/enterprise/security.md +2 -0
  58. package/{.github/workflows/loki-issue-to-pr.yml → docs/examples/loki-issue-to-pr.yml.example} +2 -0
  59. package/docs/{ONE-RUN-AUDIT.md → history/one-run-audit.md} +6 -0
  60. package/docs/{BUG-AUDIT-v6.61.0.md → history/v6.61.0-audit.md} +6 -0
  61. package/docs/{V8-AGENT-SDK-PLAN.md → history/v8-agent-sdk-plan.md} +6 -0
  62. package/docs/{CONFIG-FILE-PLAN.md → history/v8-config-plan.md} +6 -0
  63. package/docs/plans/FORGE-AUTONOMOUS-QUEUE.md +2 -2
  64. package/docs/research-2026-07/OBSERVABILITY-TOOL-SPEC.md +1 -1
  65. package/docs/retrospectives/v7.5.15-honesty-audit.md +2 -2
  66. package/docs/test-scenarios/enterprise-scenarios.md +3 -3
  67. package/docs/v10/ARCHITECT-CUTS.md +9 -9
  68. package/docs/v10/BACKLOG.md +7 -7
  69. package/docs/v10/BOARD.md +28 -25
  70. package/docs/v10/CLI-MODERN.md +190 -0
  71. package/docs/v10/CONTROL-PLANE.md +4 -4
  72. package/docs/v10/CP-ENTERPRISE-UI.md +66 -66
  73. package/docs/v10/D51-PHASE-B.md +1 -1
  74. package/docs/v10/DECISIONS.md +3 -3
  75. package/docs/v10/DEPS.md +148 -169
  76. package/docs/v10/FAILURE-CLASSES.md +63 -1
  77. package/docs/v10/LEGACY-REMOVAL.md +335 -26
  78. package/docs/v10/PROGRESS.md +15 -0
  79. package/docs/v10/SWARM.md +2 -2
  80. package/loki-ts/dist/cockpit.js +11 -11
  81. package/loki-ts/dist/loki.js +1610 -1813
  82. package/magic/core/design_tokens.py +1 -2
  83. package/magic/core/generator.py +2 -2
  84. package/magic/tokens/README.md +1 -3
  85. package/mcp/__init__.py +1 -1
  86. package/package.json +4 -8
  87. package/packages/control-plane/dist/server.js +1956 -1019
  88. package/packages/control-plane/src/db/fixture-cleanup.ts +47 -0
  89. package/packages/control-plane/src/server/app.ts +2 -0
  90. package/packages/control-plane/src/server/integrity.ts +48 -9
  91. package/packages/control-plane/src/server/legacy/routes-audit.ts +50 -0
  92. package/packages/control-plane/src/server/legacy/routes-control.ts +26 -0
  93. package/packages/control-plane/src/server/legacy/routes-memory.ts +30 -0
  94. package/packages/control-plane/src/server/legacy/routes.ts +58 -58
  95. package/packages/control-plane/src/server/legacy/shim.ts +10 -4
  96. package/packages/control-plane/src/server/routes/checkpoints.ts +171 -0
  97. package/packages/control-plane/src/server/routes/index.ts +4 -1
  98. package/packages/control-plane/src/server/routes/memory.ts +219 -0
  99. package/packages/control-plane/src/server/routes/session_control.ts +155 -0
  100. package/packages/control-plane/src/server/routes/stats.ts +16 -5
  101. package/packages/control-plane/src/server/runs.ts +16 -4
  102. package/packages/control-plane/ui/dist/assets/index-NAMkcA9N.js +165 -0
  103. package/packages/control-plane/ui/dist/index.html +1 -1
  104. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  105. package/references/magic-modules-patterns.md +2 -2
  106. package/schemas/status-result.schema.json +13 -22
  107. package/skills/factory-operations.md +3 -3
  108. package/skills/magic-modules.md +1 -1
  109. package/skills/release-cadence.md +6 -6
  110. package/tools/audit-docs.py +1 -1
  111. package/tools/index-codebase.py +1 -1
  112. package/web-app/dist/assets/{AdminPage-DDDvUz0N.js → AdminPage-B8RjG3sy.js} +1 -1
  113. package/web-app/dist/assets/{Avatar-Dcdz46i6.js → Avatar-Dttmvqc-.js} +1 -1
  114. package/web-app/dist/assets/{Badge-BB24cCpK.js → Badge-Bvyp8VrD.js} +1 -1
  115. package/web-app/dist/assets/{Button-D0-DmPZL.js → Button-DtY5ZrZw.js} +1 -1
  116. package/web-app/dist/assets/{CockpitPage-CdjpU2JW.js → CockpitPage-B2gbOwRp.js} +1 -1
  117. package/web-app/dist/assets/{ComparePage-DT5U0y0d.js → ComparePage-CHi5nZTU.js} +1 -1
  118. package/web-app/dist/assets/{ErrorBoundary-lEHcl0EO.js → ErrorBoundary-BUuTj-OQ.js} +1 -1
  119. package/web-app/dist/assets/{EvidenceReceiptPanel-GpnWvdCr.js → EvidenceReceiptPanel-C5aOn4ES.js} +1 -1
  120. package/web-app/dist/assets/{GitHubIssuesPanel-C7VOh1B7.js → GitHubIssuesPanel-isVxxHXb.js} +1 -1
  121. package/web-app/dist/assets/{GitHubPRsPanel-G_kOuINv.js → GitHubPRsPanel-DtDpxyQl.js} +1 -1
  122. package/web-app/dist/assets/{HomePage-PqRisOny.js → HomePage-Casf-LUB.js} +2 -2
  123. package/web-app/dist/assets/{LoginPage-DU7pFAkf.js → LoginPage-DvB78691.js} +1 -1
  124. package/web-app/dist/assets/{MagicPage-C5JWIWMk.js → MagicPage-DU6hmbnC.js} +1 -1
  125. package/web-app/dist/assets/{MetricsPage-CbIGS_qo.js → MetricsPage-DcPwzoS8.js} +1 -1
  126. package/web-app/dist/assets/{NotFoundPage-vU1pvdSt.js → NotFoundPage-Bs6WxAZ8.js} +1 -1
  127. package/web-app/dist/assets/{ProjectPage-SsixIPPv.js → ProjectPage-RBIqC-DJ.js} +3 -3
  128. package/web-app/dist/assets/{ProjectsPage-BfQKSR9k.js → ProjectsPage-CogXD7jM.js} +1 -1
  129. package/web-app/dist/assets/{SettingsPage-B8YfXAom.js → SettingsPage-pAyJAfPt.js} +1 -1
  130. package/web-app/dist/assets/{ShowcasePage-Ba407f2-.js → ShowcasePage-D6bT2bDd.js} +1 -1
  131. package/web-app/dist/assets/{SystemSettingsPage-DzNQhhBS.js → SystemSettingsPage-CE8xiJRh.js} +1 -1
  132. package/web-app/dist/assets/{TeamsPage-tqUnpZ15.js → TeamsPage-wMS55TDT.js} +1 -1
  133. package/web-app/dist/assets/{TemplatesPage-ClB5EpI3.js → TemplatesPage-COJNVQqu.js} +1 -1
  134. package/web-app/dist/assets/{TerminalOutput-CBvpYfEu.js → TerminalOutput-Cc2XASxd.js} +1 -1
  135. package/web-app/dist/assets/{activity-C1s0L_X9.js → activity-C6eHFDjL.js} +1 -1
  136. package/web-app/dist/assets/{bell-DMEiXr73.js → bell-BJpVYj4n.js} +1 -1
  137. package/web-app/dist/assets/{bot-BF_xIOxu.js → bot-BwRVVUQL.js} +1 -1
  138. package/web-app/dist/assets/{check-Dv1Ynvrs.js → check-CGDwoKy8.js} +1 -1
  139. package/web-app/dist/assets/{chevron-left-Ddhsod7S.js → chevron-left-CF2Nilj6.js} +1 -1
  140. package/web-app/dist/assets/{circle-alert-OATHUkBZ.js → circle-alert-D0RyRec1.js} +1 -1
  141. package/web-app/dist/assets/{clock-C1Ltva07.js → clock-xeF3gW0W.js} +1 -1
  142. package/web-app/dist/assets/{cloud-B-S4oOy9.js → cloud-D5Sy69ZZ.js} +1 -1
  143. package/web-app/dist/assets/{code-xml-D3Femn_F.js → code-xml-C6-5Ql9I.js} +1 -1
  144. package/web-app/dist/assets/{database-DBSeFgO3.js → database-BSjG1_z0.js} +1 -1
  145. package/web-app/dist/assets/{dollar-sign-sgDgNuGE.js → dollar-sign-FnDR2QUu.js} +1 -1
  146. package/web-app/dist/assets/{file-code-corner-C-MbdZEc.js → file-code-corner-BiOK9vR6.js} +1 -1
  147. package/web-app/dist/assets/{file-plus-CCB6yqXv.js → file-plus-3WALQDEE.js} +1 -1
  148. package/web-app/dist/assets/{globe-DlbrNAj1.js → globe-vUdUrQqp.js} +1 -1
  149. package/web-app/dist/assets/{hammer-B-DWm695.js → hammer-0VwHUEpa.js} +1 -1
  150. package/web-app/dist/assets/{index-Dvny-1SU.js → index-BMKIwteZ.js} +3 -3
  151. package/web-app/dist/assets/{layers-99qsKpOT.js → layers-C6q8kzST.js} +1 -1
  152. package/web-app/dist/assets/{loader-circle-ZvZyZl3J.js → loader-circle-B4Tz724d.js} +1 -1
  153. package/web-app/dist/assets/{lock-DosGjcxP.js → lock-yxvEA65n.js} +1 -1
  154. package/web-app/dist/assets/{package-C-PumesN.js → package-BpKD0Ot0.js} +1 -1
  155. package/web-app/dist/assets/{plus-CTYYskTB.js → plus-Ibqx_Vo9.js} +1 -1
  156. package/web-app/dist/assets/{refresh-cw-qW2ztTFS.js → refresh-cw-C9auyvi-.js} +1 -1
  157. package/web-app/dist/assets/{rotate-ccw-ZIKrf-vO.js → rotate-ccw-2cEiXm6O.js} +1 -1
  158. package/web-app/dist/assets/{scroll-text-BamlXjAT.js → scroll-text-BPAWML-w.js} +1 -1
  159. package/web-app/dist/assets/{server-BljXb79I.js → server-CZh6B1zx.js} +1 -1
  160. package/web-app/dist/assets/{shield-alert-Dr6eT9c_.js → shield-alert-CivolUIx.js} +1 -1
  161. package/web-app/dist/assets/{thumbs-up-v3oBXZgU.js → thumbs-up-BP9covEH.js} +1 -1
  162. package/web-app/dist/assets/{trash-2-BOuIrCpi.js → trash-2-BCDfTcNx.js} +1 -1
  163. package/web-app/dist/assets/{trending-down-Dp7jMCgH.js → trending-down-5LypivXh.js} +1 -1
  164. package/web-app/dist/assets/{trending-up-CDiur-nP.js → trending-up-DNt4SFdk.js} +1 -1
  165. package/web-app/dist/assets/{upload-DxIHXhNE.js → upload-DBmHbY6y.js} +1 -1
  166. package/web-app/dist/assets/{usePolling-D2Uu-YZb.js → usePolling-DOPZcbUL.js} +1 -1
  167. package/web-app/dist/assets/{user-BVWCUAfF.js → user-BQTJJDCG.js} +1 -1
  168. package/web-app/dist/index.html +1 -1
  169. package/dashboard/static/assets/mermaid.min.js +0 -2030
  170. package/dashboard/static/cost.html +0 -344
  171. package/dashboard/static/favicon.svg +0 -5
  172. package/dashboard/static/index.html +0 -15577
  173. package/dashboard/static/proofs.html +0 -194
  174. package/dashboard/static/start.html +0 -131
  175. package/dashboard/static/trust.html +0 -325
  176. package/docs/BRANCH-LIFECYCLE-PLAN.md +0 -354
  177. package/docs/EXECUTION-COCKPIT-PLAN.md +0 -289
  178. package/packages/control-plane/ui/dist/assets/index-Dbhu244h.js +0 -155
@@ -1,344 +0,0 @@
1
- <!DOCTYPE html>
2
- <!--
3
- Loki Mode - Cost + observability panel (R3, zero-build standalone).
4
-
5
- Self-contained: all CSS + JS inlined, no external resources. Fetches
6
- /api/cost/timeline and renders project total, a budget gauge that warns at
7
- 80% (before the hard cap at 100%), per-run cost history, model-routing
8
- breakdown, and an inline-SVG cumulative-cost line for the current run.
9
-
10
- Anti-surprise-cost wedge: cost is shown transparently. Uncollected cost is
11
- shown as "not recorded", never a fabricated $0.00.
12
- -->
13
- <html lang="en">
14
- <head>
15
- <meta charset="utf-8">
16
- <meta name="viewport" content="width=device-width, initial-scale=1">
17
- <script>
18
- // Match the embedding SPA's theme via the ?theme=dark|light param it passes
19
- // when loading this page in its iframe. Set before paint to avoid a flash;
20
- // with no/invalid param, OS prefers-color-scheme applies.
21
- (function () {
22
- try {
23
- var t = new URLSearchParams(location.search).get('theme');
24
- if (t === 'dark' || t === 'light') {
25
- document.documentElement.setAttribute('data-loki-theme', t);
26
- }
27
- } catch (e) { /* no-op: fall back to OS preference */ }
28
- })();
29
- </script>
30
- <title>Loki Mode - Cost and Observability</title>
31
- <link rel="preconnect" href="https://fonts.googleapis.com">
32
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
33
- <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">
34
- <style>
35
- /* Match the dashboard SPA design language (cream/light + serif headings),
36
- so this page reads as one product when embedded in the SPA iframe. The
37
- local --bg/--panel/--text/--mono/--sans names are kept (the rest of the
38
- CSS references them) and remapped to the SPA --loki-* palette, with a
39
- prefers-color-scheme dark variant mirroring the SPA. */
40
- :root {
41
- --bg: #FAFAF7; --panel: rgba(255,255,255,0.72); --panel-2: #E8E5DE; --border: rgba(0,0,0,0.08);
42
- --text: #1A1614; --muted: #4A4640; --faint: #8A857C; --accent: #553DE9;
43
- --green: #1AAF95; --red: #C04848; --amber: #C4922E;
44
- --mono: 'JetBrains Mono', ui-monospace, "SF Mono", "Menlo", monospace;
45
- --sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
46
- --serif: 'DM Serif Display', Georgia, serif;
47
- }
48
- /* Dark palette. data-loki-theme (set from the SPA's ?theme= param) wins over
49
- the OS preference so the iframe follows the SPA's manual Dark toggle. */
50
- html[data-loki-theme="dark"] {
51
- --bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
52
- --text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
53
- --green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
54
- }
55
- @media (prefers-color-scheme: dark) {
56
- html:not([data-loki-theme]) {
57
- --bg: #0F0B1A; --panel: rgba(30,21,51,0.72); --panel-2: #1E1533; --border: rgba(255,255,255,0.08);
58
- --text: #F0ECF8; --muted: #B8B0C8; --faint: #7B6FA0; --accent: #7B6BF0;
59
- --green: #2ED8B6; --red: #E07070; --amber: #E8B84A;
60
- }
61
- }
62
- * { box-sizing: border-box; }
63
- body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--sans); line-height: 1.5; }
64
- a { color: var(--accent); text-decoration: none; }
65
- a:hover { text-decoration: underline; }
66
- .wrap { max-width: 960px; margin: 0 auto; padding: 40px 20px 80px; }
67
- .head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
68
- h1 { font-family: var(--serif); font-size: 28px; font-weight: 400; letter-spacing: -0.3px; margin: 0; }
69
- h2 { font-size: 15px; font-weight: 600; color: var(--muted); margin: 30px 0 12px; text-transform: uppercase; letter-spacing: 0.5px; }
70
- .head a { font-size: 13px; }
71
- .sub { color: var(--muted); font-size: 14px; margin: 0 0 26px; }
72
- .cards { display: flex; gap: 14px; flex-wrap: wrap; }
73
- .card { flex: 1 1 200px; background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
74
- .card .label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
75
- .card .val { font-family: var(--mono); font-size: 26px; font-weight: 650; margin-top: 6px; }
76
- .card .note { color: var(--faint); font-size: 12px; margin-top: 4px; }
77
- .gauge { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 18px; margin-top: 14px; }
78
- .gauge .top { display: flex; justify-content: space-between; align-items: baseline; }
79
- .gauge .pct { font-family: var(--mono); font-weight: 650; font-size: 18px; }
80
- .bar { position: relative; height: 14px; background: var(--panel-2); border-radius: 8px; margin: 12px 0 6px; overflow: hidden; }
81
- .bar .fill { height: 100%; border-radius: 8px; transition: width .3s; }
82
- .bar .warnline { position: absolute; top: -3px; bottom: -3px; width: 2px; background: var(--amber); left: 80%; }
83
- .fill.ok { background: var(--green); }
84
- .fill.warn { background: var(--amber); }
85
- .fill.exceeded { background: var(--red); }
86
- .status { font-size: 13px; margin-top: 8px; }
87
- .status.ok { color: var(--green); }
88
- .status.warn { color: var(--amber); }
89
- .status.exceeded { color: var(--red); }
90
- .status.none { color: var(--muted); }
91
- .cap-note { color: var(--faint); font-size: 12px; margin-top: 6px; }
92
- table { width: 100%; border-collapse: collapse; font-size: 13px; }
93
- th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
94
- th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; }
95
- td.num, th.num { text-align: right; font-family: var(--mono); }
96
- .badge { font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--border); }
97
- .b-approve { color: var(--green); border-color: rgba(52,211,153,0.4); }
98
- .b-reject { color: var(--red); border-color: rgba(248,113,113,0.4); }
99
- .b-concern { color: var(--amber); border-color: rgba(251,191,36,0.4); }
100
- .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 18px; }
101
- .empty { color: var(--muted); background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 24px; text-align: center; }
102
- .empty code { font-family: var(--mono); color: var(--text); background: var(--panel-2); padding: 2px 6px; border-radius: 5px; }
103
- .mono { font-family: var(--mono); }
104
- .muted { color: var(--muted); }
105
- svg { display: block; width: 100%; height: 140px; }
106
- .modelrow { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
107
- .modelrow .name { width: 130px; font-size: 13px; }
108
- .modelrow .mbar { flex: 1; height: 10px; background: var(--panel-2); border-radius: 6px; overflow: hidden; }
109
- .modelrow .mbar .mfill { height: 100%; background: var(--accent); border-radius: 6px; }
110
- .modelrow .mval { width: 90px; text-align: right; font-family: var(--mono); font-size: 13px; }
111
- </style>
112
- </head>
113
- <body>
114
- <div class="wrap">
115
- <div class="head">
116
- <h1>Cost and Observability</h1>
117
- <a href="/">Back to dashboard</a>
118
- </div>
119
- <p class="sub">Transparent cost: per-run and per-project spend, model routing, token burn, and budget caps that warn before they stop. No surprise bills.</p>
120
- <div id="content"><p class="sub">Loading...</p></div>
121
- </div>
122
- <script>
123
- (function () {
124
- "use strict";
125
- function esc(s) {
126
- s = (s === null || s === undefined) ? "" : String(s);
127
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
128
- .replace(/"/g, "&quot;").replace(/'/g, "&#39;");
129
- }
130
- function fmtUsd(n) {
131
- if (n === null || n === undefined) return "not recorded";
132
- n = Number(n);
133
- if (!isFinite(n)) return "not recorded";
134
- var s = n.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
135
- if (s.indexOf(".") === -1) s += ".00";
136
- else if (s.split(".")[1].length === 1) s += "0";
137
- return "$" + s;
138
- }
139
- // A total over runs where only some recorded a cost is a lower bound.
140
- function projectTotal(d) {
141
- return fmtUsd(d.project_total_usd) + (d.project_total_partial && d.project_total_usd != null ? " (partial)" : "");
142
- }
143
- // Same rule for a sum over iterations where only some recorded a cost.
144
- function partialUsd(n, partial) {
145
- return fmtUsd(n) + (partial && n !== null && n !== undefined ? " (partial)" : "");
146
- }
147
- function orNotRecorded(v) {
148
- return (v === null || v === undefined) ? "not recorded" : esc(v);
149
- }
150
- // A partly priced run's cost is a lower bound; say so only when a number prints.
151
- function atLeastUsd(n, partial) {
152
- var s = fmtUsd(n);
153
- return (partial === true && s.charAt(0) === "$") ? "at least " + s : s;
154
- }
155
- function badgeClass(v) {
156
- v = String(v || "").toUpperCase();
157
- if (v.indexOf("APPROVE") === 0 || v === "PASS" || v === "PASSED") return "b-approve";
158
- if (v.indexOf("REJECT") === 0 || v.indexOf("BLOCK") === 0 || v === "FAIL") return "b-reject";
159
- if (v.indexOf("CONCERN") === 0) return "b-concern";
160
- return "";
161
- }
162
- function statusText(st, partial) {
163
- if (st === "ok" && partial) return "Within budget so far, but some iterations recorded no cost, so true spend may be higher.";
164
- if (st === "exceeded") return "Budget cap reached. The run is paused to prevent a surprise bill.";
165
- if (st === "warn") return "Approaching budget cap (80% or more used). Warning only, the run continues.";
166
- if (st === "ok") return "Within budget.";
167
- // A cap is set but no spend was measured: unknown, never "within budget".
168
- if (st === "unknown") return "Spend not recorded yet, so budget use is unknown.";
169
- return "No budget cap set. Set LOKI_BUDGET_LIMIT to cap spend.";
170
- }
171
-
172
- function renderBudget(b) {
173
- if (!b) return "";
174
- var html = '<h2>Budget</h2>';
175
- if (b.limit === null || b.limit === undefined) {
176
- html += '<div class="panel"><div class="status none">' + statusText("none") + '</div>' +
177
- '<div class="cap-note">When a cap is set, Loki warns at 80% and hard-stops at 100%.</div></div>';
178
- return html;
179
- }
180
- // Unknown use is not 0% used: no fill and no percentage are drawn for it.
181
- var pct = (b.percent_used === null || b.percent_used === undefined) ? NaN : Number(b.percent_used);
182
- var known = isFinite(pct);
183
- var fillPct = known ? Math.max(0, Math.min(100, pct)) : 0;
184
- var st = esc(b.status || "unknown");
185
- var part = b.partial === true;
186
- html += '<div class="gauge">' +
187
- '<div class="top"><span class="muted">' + partialUsd(b.used, part) + ' of ' + fmtUsd(b.limit) + ' used</span>' +
188
- '<span class="pct">' + (known ? pct.toFixed(1) + '%' + (part ? ' (partial)' : '') : 'not recorded') + '</span></div>' +
189
- '<div class="bar"><div class="fill ' + st + '" style="width:' + fillPct + '%"></div>' +
190
- '<div class="warnline" title="80% warn threshold"></div></div>' +
191
- '<div class="status ' + st + '">' + statusText(b.status, part) + '</div>' +
192
- '<div class="cap-note">Remaining: ' + (part && b.remaining != null ? 'at most ' : '') + fmtUsd(b.remaining) +
193
- '. Warns at ' + esc(b.warn_threshold_percent) + '%, hard-stops at 100%.</div>' +
194
- '</div>';
195
- return html;
196
- }
197
-
198
- function renderModelBreakdown(runs) {
199
- // Aggregate per-run cost by model for routing visibility.
200
- var byModel = {};
201
- var total = 0;
202
- for (var i = 0; i < runs.length; i++) {
203
- var m = runs[i].model || "unknown";
204
- var c = runs[i].cost_usd;
205
- if (c === null || c === undefined) continue;
206
- c = Number(c);
207
- if (!isFinite(c)) continue;
208
- byModel[m] = (byModel[m] || 0) + c;
209
- total += c;
210
- }
211
- var keys = Object.keys(byModel);
212
- if (keys.length === 0) return "";
213
- keys.sort(function (a, b) { return byModel[b] - byModel[a]; });
214
- var html = '<h2>Model routing (by spend)</h2><div class="panel">';
215
- for (var k = 0; k < keys.length; k++) {
216
- var name = keys[k];
217
- var val = byModel[name];
218
- var w = total > 0 ? (val / total * 100) : 0;
219
- html += '<div class="modelrow"><span class="name mono">' + esc(name) + '</span>' +
220
- '<span class="mbar"><span class="mfill" style="width:' + w.toFixed(1) + '%"></span></span>' +
221
- '<span class="mval">' + fmtUsd(val) + '</span></div>';
222
- }
223
- html += '</div>';
224
- return html;
225
- }
226
-
227
- function renderCurrentRun(cr) {
228
- var html = '<h2>Current run (cost over iterations)</h2>';
229
- if (!cr || !cr.cost_recorded || !cr.iterations || cr.iterations.length === 0) {
230
- html += '<div class="empty">No iteration cost recorded for the current run yet.' +
231
- ' Cost appears once a run produces efficiency records.</div>';
232
- return html;
233
- }
234
- var its = cr.iterations;
235
- // Inline SVG cumulative line. An iteration with no recorded cost is SKIPPED,
236
- // never plotted at the floor as a measured zero; x keeps its index so the
237
- // gap stays visible.
238
- var W = 900, H = 140, pad = 8;
239
- var measured = [];
240
- var maxCum = 0;
241
- for (var i = 0; i < its.length; i++) {
242
- var raw = its[i].cumulative_usd;
243
- var cv = (raw === null || raw === undefined) ? NaN : Number(raw);
244
- if (!isFinite(cv)) continue;
245
- measured.push([i, cv]);
246
- if (cv > maxCum) maxCum = cv;
247
- }
248
- if (maxCum <= 0) maxCum = 1;
249
- var pts = [];
250
- for (var j = 0; j < measured.length; j++) {
251
- var idx = measured[j][0];
252
- var x = its.length === 1 ? W / 2 : pad + (idx / (its.length - 1)) * (W - 2 * pad);
253
- var y = H - pad - (measured[j][1] / maxCum) * (H - 2 * pad);
254
- pts.push(x.toFixed(1) + "," + y.toFixed(1));
255
- }
256
- var poly = pts.length === 0
257
- ? '<div class="empty">No iteration in this run recorded a cost, so there is nothing to plot.</div>'
258
- : '<svg viewBox="0 0 ' + W + ' ' + H + '" preserveAspectRatio="none">' +
259
- '<polyline fill="none" stroke="#6f7bf7" stroke-width="2" points="' + pts.join(" ") + '"/>' +
260
- '</svg>';
261
- html += '<div class="panel"><div class="muted" style="font-size:13px;margin-bottom:6px;">' +
262
- 'Cumulative spend this run: <span class="mono">' + partialUsd(cr.total_usd, cr.partial) + '</span></div>' +
263
- poly + '</div>';
264
- // Per-iteration table.
265
- var rows = "";
266
- // Once an iteration went unmeasured, every later running total skips it.
267
- var gap = false;
268
- for (var r = 0; r < its.length; r++) {
269
- var it = its[r];
270
- if (it.cost_usd === null || it.cost_usd === undefined) gap = true;
271
- rows += '<tr><td class="num mono">' + esc(it.iteration) + '</td>' +
272
- '<td class="mono">' + orNotRecorded(it.model) + '</td>' +
273
- '<td>' + esc(it.phase) + '</td>' +
274
- '<td class="num">' + orNotRecorded(it.input_tokens) + '</td>' +
275
- '<td class="num">' + orNotRecorded(it.output_tokens) + '</td>' +
276
- '<td class="num mono">' + fmtUsd(it.cost_usd) + '</td>' +
277
- '<td class="num mono">' + partialUsd(it.cumulative_usd, gap) + '</td></tr>';
278
- }
279
- html += '<table style="margin-top:14px;"><thead><tr>' +
280
- '<th class="num">Iter</th><th>Model</th><th>Phase</th>' +
281
- '<th class="num">Input tok</th><th class="num">Output tok</th>' +
282
- '<th class="num">Cost</th><th class="num">Cumulative</th>' +
283
- '</tr></thead><tbody>' + rows + '</tbody></table>';
284
- return html;
285
- }
286
-
287
- function renderRuns(runs) {
288
- var html = '<h2>Run history (per-run cost)</h2>';
289
- if (!runs || runs.length === 0) {
290
- html += '<div class="empty">No completed runs yet. Per-run cost history' +
291
- ' comes from proof-of-run artifacts (<code>.loki/proofs/</code>).</div>';
292
- return html;
293
- }
294
- var rows = "";
295
- for (var i = 0; i < runs.length; i++) {
296
- var p = runs[i];
297
- var verdict = p.final_verdict ?
298
- '<span class="badge ' + badgeClass(p.final_verdict) + '">' + esc(p.final_verdict) + '</span>' : '';
299
- rows += '<tr><td class="mono">' + esc(p.run_id) + '</td>' +
300
- '<td class="muted">' + esc(p.generated_at || "") + '</td>' +
301
- '<td class="mono">' + esc(p.model || "") + '</td>' +
302
- '<td class="num">' + (p.files_changed === null || p.files_changed === undefined ? "" : esc(p.files_changed)) + '</td>' +
303
- '<td>' + verdict + '</td>' +
304
- '<td class="num mono">' + atLeastUsd(p.cost_usd, p.cost_partial) + '</td></tr>';
305
- }
306
- html += '<table><thead><tr>' +
307
- '<th>Run</th><th>When</th><th>Model</th><th class="num">Files</th>' +
308
- '<th>Council vote</th><th class="num">Cost</th>' +
309
- '</tr></thead><tbody>' + rows + '</tbody></table>';
310
- return html;
311
- }
312
-
313
- function render(d) {
314
- var c = document.getElementById("content");
315
- var b = d.budget || {};
316
- var cards = '<div class="cards">' +
317
- '<div class="card"><div class="label">Project total</div>' +
318
- '<div class="val">' + projectTotal(d) + '</div>' +
319
- '<div class="note">' + esc(d.runs_count) + ' run(s) recorded</div></div>' +
320
- '<div class="card"><div class="label">Current run</div>' +
321
- '<div class="val">' + (d.current_run ? partialUsd(d.current_run.total_usd, d.current_run.partial) : fmtUsd(null)) + '</div>' +
322
- '<div class="note">' + (d.current_run && d.current_run.iterations ? d.current_run.iterations.length : 0) + ' iteration(s)</div></div>' +
323
- '<div class="card"><div class="label">Budget status</div>' +
324
- '<div class="val ' + esc(b.status || "none") + '" style="font-size:20px;text-transform:capitalize;">' + esc(b.status || "none") + (b.partial ? ' (partial)' : '') + '</div>' +
325
- '<div class="note">' + (b.limit ? partialUsd(b.used, b.partial) + ' / ' + fmtUsd(b.limit) : "no cap set") + '</div></div>' +
326
- '</div>';
327
- c.innerHTML = cards +
328
- renderBudget(d.budget) +
329
- renderCurrentRun(d.current_run) +
330
- renderModelBreakdown(d.runs || []) +
331
- renderRuns(d.runs);
332
- }
333
- function renderError(msg) {
334
- document.getElementById("content").innerHTML =
335
- '<div class="empty">Could not load cost data. ' + esc(msg || "") + "</div>";
336
- }
337
- fetch("/api/cost/timeline", { headers: { "Accept": "application/json" } })
338
- .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
339
- .then(function (d) { render(d || {}); })
340
- .catch(function (e) { renderError(e && e.message); });
341
- })();
342
- </script>
343
- </body>
344
- </html>
@@ -1,5 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
2
- <path d="M16 6C8 6 2 16 2 16s6 10 14 10 14-10 14-10S24 6 16 6z" fill="none" stroke="#7c3aed" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
- <circle cx="16" cy="16" r="5" fill="#7c3aed"/>
4
- <circle cx="16" cy="16" r="2" fill="#fff"/>
5
- </svg>