pi-mega-compact 0.7.8 → 0.7.9
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 +11 -12
- package/dist/extensions/dashboard-server/helpers.js +37 -0
- package/dist/extensions/dashboard-server/html/all-repos-tab.js +26 -0
- package/dist/extensions/dashboard-server/html/body-open.js +23 -0
- package/dist/extensions/dashboard-server/html/current-repo-tab.js +130 -0
- package/dist/extensions/dashboard-server/html/head-open.js +16 -0
- package/dist/extensions/dashboard-server/html/high-score-tab.js +25 -0
- package/dist/extensions/dashboard-server/html/repo-detail-modal.js +26 -0
- package/dist/extensions/dashboard-server/html/script.js +259 -0
- package/dist/extensions/dashboard-server/html/styles.js +103 -0
- package/dist/extensions/dashboard-server/html/summary-tab.js +19 -0
- package/dist/extensions/dashboard-server/html-template.js +41 -0
- package/dist/extensions/dashboard-server/html.js +756 -0
- package/dist/extensions/dashboard-server/index-reader.js +133 -0
- package/dist/extensions/dashboard-server/server.js +370 -0
- package/dist/extensions/dashboard-server/snapshot.js +43 -0
- package/dist/extensions/dashboard-server/state.js +30 -0
- package/dist/extensions/dashboard-server/types.js +5 -0
- package/dist/extensions/dashboard-server.js +7 -1315
- package/dist/extensions/mega-commands.js +162 -134
- package/dist/extensions/mega-compact.test.js +90 -21
- package/dist/extensions/mega-conflict-cmds.js +5 -1
- package/dist/extensions/mega-dashboard-cmds.js +29 -22
- package/dist/extensions/mega-db-cmds.js +11 -2
- package/dist/extensions/mega-events/agent-handlers.js +173 -0
- package/dist/extensions/mega-events/compact-handlers.js +133 -0
- package/dist/extensions/mega-events/context-handler.js +249 -0
- package/dist/extensions/mega-events/register.js +21 -0
- package/dist/extensions/mega-events/session-handlers.js +142 -0
- package/dist/extensions/mega-events.js +15 -699
- package/dist/extensions/mega-pipeline/compact.js +324 -0
- package/dist/extensions/mega-pipeline/memory-review.js +38 -0
- package/dist/extensions/mega-pipeline/recall.js +147 -0
- package/dist/extensions/mega-pipeline.js +9 -480
- package/dist/extensions/mega-runtime/helpers.js +40 -0
- package/dist/extensions/mega-runtime/query.js +29 -0
- package/dist/extensions/mega-runtime/state.js +711 -0
- package/dist/extensions/mega-runtime/widget.js +197 -0
- package/dist/extensions/mega-runtime.js +15 -947
- package/dist/src/store/sqlite/checkpoints.js +145 -0
- package/dist/src/store/sqlite/connection.js +35 -0
- package/dist/src/store/sqlite/dedup-mirror.js +64 -0
- package/dist/src/store/sqlite/foundation.js +38 -0
- package/dist/src/store/sqlite/global-index.js +224 -0
- package/dist/src/store/sqlite/index-store.js +167 -0
- package/dist/src/store/sqlite/maintenance.js +235 -0
- package/dist/src/store/sqlite/memories.js +164 -0
- package/dist/src/store/sqlite/memory.js +54 -0
- package/dist/src/store/sqlite/meta.js +82 -0
- package/dist/src/store/sqlite/minhash-lsh.js +47 -0
- package/dist/src/store/sqlite/model-snapshots.js +47 -0
- package/dist/src/store/sqlite/raptor.js +57 -0
- package/dist/src/store/sqlite/raw-transcript.js +134 -0
- package/dist/src/store/sqlite/schema.js +250 -0
- package/dist/src/store/sqlite/session-state.js +28 -0
- package/dist/src/store/sqlite/sessions.js +39 -0
- package/dist/src/store/sqlite/stats.js +66 -0
- package/dist/src/store/sqlite/transaction.js +19 -0
- package/dist/src/store/sqlite/utils.js +120 -0
- package/dist/src/store/sqlite.js +20 -1607
- package/dist/src/vectorStore/add.js +260 -0
- package/dist/src/vectorStore/dedup.js +52 -0
- package/dist/src/vectorStore/index.js +10 -0
- package/dist/src/vectorStore/queries.js +83 -0
- package/dist/src/vectorStore/search.js +95 -0
- package/dist/src/vectorStore/session.js +19 -0
- package/dist/src/vectorStore/store.js +105 -0
- package/dist/src/vectorStore/types.js +6 -0
- package/dist/src/vectorStore/utils.js +23 -0
- package/extensions/dashboard-server/html.ts +758 -0
- package/extensions/dashboard-server/index-reader.ts +130 -0
- package/extensions/dashboard-server/server.ts +358 -0
- package/extensions/dashboard-server/snapshot.ts +44 -0
- package/extensions/dashboard-server/state.ts +33 -0
- package/extensions/dashboard-server/types.ts +134 -0
- package/extensions/dashboard-server.ts +7 -1431
- package/extensions/mega-commands.ts +33 -10
- package/extensions/mega-compact.test.ts +198 -43
- package/extensions/mega-conflict-cmds.ts +6 -2
- package/extensions/mega-dashboard-cmds.ts +30 -23
- package/extensions/mega-db-cmds.ts +11 -3
- package/extensions/mega-events/agent-handlers.ts +214 -0
- package/extensions/mega-events/compact-handlers.ts +164 -0
- package/extensions/mega-events/context-handler.ts +290 -0
- package/extensions/mega-events/register.ts +37 -0
- package/extensions/mega-events/session-handlers.ts +165 -0
- package/extensions/mega-events.ts +15 -780
- package/extensions/mega-pipeline/compact.ts +366 -0
- package/extensions/mega-pipeline/memory-review.ts +46 -0
- package/extensions/mega-pipeline/recall.ts +165 -0
- package/extensions/mega-pipeline.ts +9 -537
- package/extensions/mega-runtime/helpers.ts +68 -0
- package/extensions/mega-runtime/query.ts +29 -0
- package/extensions/mega-runtime/state.ts +797 -0
- package/extensions/mega-runtime/widget.ts +258 -0
- package/extensions/mega-runtime.ts +15 -1093
- package/package.json +4 -3
- package/src/store/sqlite/checkpoints.ts +204 -0
- package/src/store/sqlite/dedup-mirror.ts +114 -0
- package/src/store/sqlite/foundation.ts +63 -0
- package/src/store/sqlite/global-index.ts +305 -0
- package/src/store/sqlite/maintenance.ts +294 -0
- package/src/store/sqlite/memories.ts +217 -0
- package/src/store/sqlite/meta.ts +108 -0
- package/src/store/sqlite/model-snapshots.ts +83 -0
- package/src/store/sqlite/raptor.ts +107 -0
- package/src/store/sqlite/raw-transcript.ts +221 -0
- package/src/store/sqlite/schema.ts +258 -0
- package/src/store/sqlite/session-state.ts +38 -0
- package/src/store/sqlite/stats.ts +127 -0
- package/src/store/sqlite/utils.ts +125 -0
- package/src/store/sqlite.ts +20 -2204
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server/html.ts — single-page HTML dashboard template.
|
|
3
|
+
*/
|
|
4
|
+
import { dashboardServerVersion } from "./state.js";
|
|
5
|
+
export function dashboardHtml(tierName) {
|
|
6
|
+
return `<!DOCTYPE html>
|
|
7
|
+
<html lang="en">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="utf-8">
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
11
|
+
<title>mega-compact dashboard</title>
|
|
12
|
+
<style>
|
|
13
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
14
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; line-height: 1.5; }
|
|
15
|
+
h1 { font-size: 20px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; color: #f0f6fc; }
|
|
16
|
+
h1 .tier { background: #1f6feb; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
|
|
17
|
+
h1 .version-pill { background: #30363d; color: #8b949e; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
18
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
|
|
19
|
+
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
|
|
20
|
+
.card.safe { border-color: #238636; }
|
|
21
|
+
.card.safe h2 { color: #3fb950; }
|
|
22
|
+
.safe-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; line-height: 1.5; }
|
|
23
|
+
.value.ok { color: #3fb950; }
|
|
24
|
+
.label {
|
|
25
|
+
cursor: help;
|
|
26
|
+
border-bottom: 1px dotted #484f58;
|
|
27
|
+
}
|
|
28
|
+
.card.legend { grid-column: 1 / -1; }
|
|
29
|
+
.legend-list { margin: 0; padding-left: 18px; color: #c9d1d9; }
|
|
30
|
+
.legend-list li { margin-bottom: 8px; line-height: 1.5; }
|
|
31
|
+
.legend-list b { color: #f0f6fc; }
|
|
32
|
+
.legend-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; font-style: italic; }
|
|
33
|
+
.card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
|
|
34
|
+
.meter-track { background: #21262d; border-radius: 4px; height: 20px; overflow: hidden; margin: 8px 0; }
|
|
35
|
+
.meter-fill { height: 100%; border-radius: 4px; transition: width .6s ease; min-width: 2px; }
|
|
36
|
+
.meter-green { background: #238636; }
|
|
37
|
+
.meter-yellow { background: #d29922; }
|
|
38
|
+
.meter-red { background: #f85149; }
|
|
39
|
+
.meter-label { font-size: 24px; font-weight: 700; color: #f0f6fc; }
|
|
40
|
+
.meter-sub { font-size: 12px; color: #8b949e; }
|
|
41
|
+
.status-row { display: flex; align-items: center; gap: 8px; margin: 6px 0; font-size: 14px; }
|
|
42
|
+
.status-row .bullet { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
43
|
+
.bullet-on { background: #3fb950; box-shadow: 0 0 6px #3fb95088; }
|
|
44
|
+
.bullet-off { background: #484f58; }
|
|
45
|
+
.bullet-na { background: #d29922; }
|
|
46
|
+
.state-text { font-size: 13px; color: #8b949e; margin-top: 8px; font-family: monospace; }
|
|
47
|
+
.stat-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
|
|
48
|
+
.stat-grid .label { color: #8b949e; }
|
|
49
|
+
.stat-grid .value { color: #f0f6fc; font-weight: 600; font-family: monospace; }
|
|
50
|
+
.conf-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
|
|
51
|
+
.conf-grid .label { color: #8b949e; }
|
|
52
|
+
.conf-grid .value { color: #f0f6fc; font-family: monospace; }
|
|
53
|
+
.events { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
|
|
54
|
+
.events h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
|
|
55
|
+
.events-wrap { max-height: 240px; overflow-y: auto; font-family: monospace; font-size: 12px; }
|
|
56
|
+
.ev { padding: 3px 0; border-bottom: 1px solid #21262d; display: flex; gap: 8px; align-items: baseline; }
|
|
57
|
+
.ev:last-child { border-bottom: none; }
|
|
58
|
+
.ev-type { font-weight: 700; min-width: 70px; text-align: right; }
|
|
59
|
+
.ev-type-compact { color: #3fb950; }
|
|
60
|
+
.ev-type-recall { color: #a371f7; }
|
|
61
|
+
.ev-time { color: #484f58; font-size: 10px; min-width: 80px; }
|
|
62
|
+
.ev-detail { color: #8b949e; flex: 1; }
|
|
63
|
+
.updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
|
|
64
|
+
.empty { color: #484f58; font-style: italic; font-size: 13px; padding: 8px 0; }
|
|
65
|
+
.offline-banner { background: #f8514922; border: 1px solid #f85149; border-radius: 6px; padding: 10px 16px; margin-bottom: 16px; font-size: 13px; color: #f85149; display: none; }
|
|
66
|
+
.tabs { display: flex; gap: 8px; margin-bottom: 20px; }
|
|
67
|
+
.tab { background: #161b22; color: #8b949e; border: 1px solid #30363d; border-radius: 6px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .15s ease; }
|
|
68
|
+
.tab:hover { color: #c9d1d9; border-color: #484f58; }
|
|
69
|
+
.tab.active { background: #1f6feb; color: #fff; border-color: #1f6feb; }
|
|
70
|
+
.tab-panel { display: none; }
|
|
71
|
+
.tab-panel.active { display: block; }
|
|
72
|
+
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 20px; }
|
|
73
|
+
.summary-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
|
|
74
|
+
.summary-card .num { font-size: 24px; font-weight: 700; color: #f0f6fc; }
|
|
75
|
+
.summary-card .lbl { font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: .5px; margin-top: 4px; }
|
|
76
|
+
table.repos { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
|
|
77
|
+
table.repos th, table.repos td { text-align: left; padding: 10px 14px; font-size: 13px; border-bottom: 1px solid #21262d; }
|
|
78
|
+
table.repos th { color: #8b949e; text-transform: uppercase; letter-spacing: .5px; font-size: 11px; background: #0d1117; }
|
|
79
|
+
table.repos td.num { font-family: monospace; color: #f0f6fc; text-align: right; }
|
|
80
|
+
table.repos tr:last-child td { border-bottom: none; }
|
|
81
|
+
table.repos tr:hover td { background: #1c2128; }
|
|
82
|
+
.repo-model { color: #a371f7; }
|
|
83
|
+
.repo-none { color: #484f58; font-style: italic; }
|
|
84
|
+
.updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
|
|
85
|
+
.model-pill { background: #6e40c9; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
|
|
86
|
+
.card.cost h2 { color: #a371f7; }
|
|
87
|
+
.cost-usd { font-size: 22px; font-weight: 700; color: #3fb950; }
|
|
88
|
+
.cost-sub { font-size: 12px; color: #8b949e; margin-top: 4px; }
|
|
89
|
+
.repo-link { cursor: pointer; }
|
|
90
|
+
.repo-link:hover td { color: #58a6ff; }
|
|
91
|
+
.repo-detail { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 50; }
|
|
92
|
+
.repo-detail.open { display: flex; }
|
|
93
|
+
.repo-detail-box { background: #161b22; border: 1px solid #30363d; border-radius: 10px; padding: 24px; width: 560px; max-width: 92vw; max-height: 86vh; overflow-y: auto; }
|
|
94
|
+
.repo-detail-box h2 { font-size: 14px; color: #f0f6fc; margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
|
|
95
|
+
.repo-close { cursor: pointer; color: #8b949e; font-size: 20px; line-height: 1; border: none; background: none; padding: 0 4px; }
|
|
96
|
+
.repo-close:hover { color: #f0f6fc; }
|
|
97
|
+
.repo-path { font-size: 11px; color: #484f58; word-break: break-all; margin: -8px 0 12px; }
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
|
|
102
|
+
<div class="offline-banner" id="offline-banner">Dashboard data unavailable — waiting for a pi session to write snapshot...</div>
|
|
103
|
+
|
|
104
|
+
<h1><span>mega-compact</span><span class="tier" id="hdr-tier">${tierName}</span><span class="version-pill">v${dashboardServerVersion}</span><span class="model-pill" id="hdr-model">—</span></h1>
|
|
105
|
+
|
|
106
|
+
<nav class="tabs">
|
|
107
|
+
<button class="tab active" data-tab="current">Current repo</button>
|
|
108
|
+
<button class="tab" data-tab="all">All repos</button>
|
|
109
|
+
<button class="tab" data-tab="active">Active Repos</button>
|
|
110
|
+
<button class="tab" data-tab="summary">Summary</button>
|
|
111
|
+
</nav>
|
|
112
|
+
|
|
113
|
+
<!-- Current repo (existing single-repo view) -->
|
|
114
|
+
<div class="tab-panel" id="panel-current">
|
|
115
|
+
<div class="grid">
|
|
116
|
+
<div class="card">
|
|
117
|
+
<h2>Context Window</h2>
|
|
118
|
+
<div class="meter-label" id="ctx-pct">—</div>
|
|
119
|
+
<div class="meter-track"><div class="meter-fill" id="ctx-bar" style="width:0%"></div></div>
|
|
120
|
+
<div class="meter-sub" id="ctx-sub">waiting for data</div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="card">
|
|
123
|
+
<h2>Trigger Status</h2>
|
|
124
|
+
<div class="status-row"><div class="bullet" id="tr-armed"></div><span>Armed (context ≥ fast gate)</span></div>
|
|
125
|
+
<div class="status-row"><div class="bullet" id="tr-ready"></div><span>Ready (tokens ≥ threshold)</span></div>
|
|
126
|
+
<div class="state-text" id="tr-state">waiting</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="card">
|
|
129
|
+
<h2>Vector Store</h2>
|
|
130
|
+
<div class="stat-grid">
|
|
131
|
+
<span class="label" title="A saved summary of a chunk of your conversation that was compacted to free up space.">Checkpoints</span><span class="value" id="st-count">0</span>
|
|
132
|
+
<span class="label" title="Total size of the original conversation text dropped into compaction this session, including redundant regions skipped by dedup. This is the 'in'.">Original (dropped)</span><span class="value" id="st-in">0</span>
|
|
133
|
+
<span class="label" title="Compact summaries we are currently holding as 'memory' for this session (the 'out'). Smaller is better.">Kept (summaries)</span><span class="value" id="st-kept">0</span>
|
|
134
|
+
<span class="label" title="Conversation space freed = dropped − kept (the 'saved').">Freed (dropped − kept)</span><span class="value" id="st-freed">0</span>
|
|
135
|
+
<span class="label" title="How many times old context was automatically brought back into the conversation because it was relevant to what you were doing.">Injected</span><span class="value" id="st-injected">0</span>
|
|
136
|
+
<span class="label" title="Of the times we recalled old context, how often it was actually on-topic.">Recall Relevance</span><span class="value" id="st-dedup">0%</span>
|
|
137
|
+
<span class="label" title="How often new content matched something we already had, so we skipped storing a duplicate copy. Higher = less wasted space.">Storage Dedup</span><span class="value" id="st-sdedup">0%</span>
|
|
138
|
+
<span class="label" title="How many duplicate chunks we collapsed into one instead of storing separately.">Collapsed</span><span class="value" id="st-collapsed">0</span>
|
|
139
|
+
<span class="label" title="The ID of the most recent saved checkpoint.">Last ID</span><span class="value" id="st-lastid">—</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="meter-track" style="margin-top:10px"><div class="meter-fill" id="st-compress-bar" style="width:0%"></div></div>
|
|
142
|
+
<div class="meter-sub" id="st-compress-sub">waiting for compaction…</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="card">
|
|
145
|
+
<h2>Repo (all sessions)</h2>
|
|
146
|
+
<div class="stat-grid">
|
|
147
|
+
<span class="label">Checkpoints</span><span class="value" id="rp-count">0</span>
|
|
148
|
+
<span class="label">Original (dropped)</span><span class="value" id="rp-in">0</span>
|
|
149
|
+
<span class="label">Kept (summaries)</span><span class="value" id="rp-kept">0</span>
|
|
150
|
+
<span class="label">Freed (dropped − kept)</span><span class="value" id="rp-freed">0</span>
|
|
151
|
+
<span class="label">Sessions</span><span class="value" id="rp-sessions">0</span>
|
|
152
|
+
<span class="label">Collapsed</span><span class="value" id="rp-collapsed">0</span>
|
|
153
|
+
<span class="label">Storage Dedup</span><span class="value" id="rp-sdedup">0%</span>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="meter-track" style="margin-top:10px"><div class="meter-fill" id="rp-compress-bar" style="width:0%"></div></div>
|
|
156
|
+
<div class="meter-sub" id="rp-compress-sub">waiting for compaction…</div>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="card safe">
|
|
159
|
+
<h2>🛡 Data Safety</h2>
|
|
160
|
+
<div class="stat-grid">
|
|
161
|
+
<span class="label">Regions Retained</span><span class="value" id="ig-retained">0</span>
|
|
162
|
+
<span class="label">Compressed-Original</span><span class="value" id="ig-bytes">0 B</span>
|
|
163
|
+
<span class="label">Dedup Duplicates</span><span class="value" id="ig-dupes">0</span>
|
|
164
|
+
<span class="label">Permanently Deleted</span><span class="value ok" id="ig-deleted">0 B</span>
|
|
165
|
+
</div>
|
|
166
|
+
<p class="safe-note">Every compacted region is kept verbatim (compressed). "Drop" = removed from the live window only. We never delete your data.</p>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="card">
|
|
169
|
+
<h2>Configuration</h2>
|
|
170
|
+
<div class="conf-grid">
|
|
171
|
+
<span class="label" title="Live pressure band — climbs low→mega as context fills the window.">Tier (live)</span><span class="value" id="cf-tier">${tierName}</span>
|
|
172
|
+
<span class="label" title="The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.">Preset</span><span class="value" id="cf-preset">—</span>
|
|
173
|
+
<span class="label" title="Live pressure = currentTokens / threshold — % of the model context window (threshold fires at the tier's % of window).">Pressure</span><span class="value" id="cf-pressure">—</span>
|
|
174
|
+
<span class="label" title="Compaction threshold = tierPct × model context window — mega-compact trims BELOW pi's native ~80% auto-compact for any model size.">Threshold</span><span class="value" id="cf-threshold">—</span>
|
|
175
|
+
<span class="label" title="Fast-gate arming floor — the live trim arms once context passes this % of the window.">Fast Gate</span><span class="value" id="cf-gate">—</span>
|
|
176
|
+
<span class="label">Auto</span><span class="value" id="cf-auto">—</span>
|
|
177
|
+
<span class="label">Anchor</span><span class="value" id="cf-anchor">—</span>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="card cost">
|
|
181
|
+
<h2>💰 Model & Cost Savings</h2>
|
|
182
|
+
<div class="cost-usd" id="cost-usd">≈ $0.00 saved</div>
|
|
183
|
+
<div class="cost-sub" id="cost-windows">0 context-windows extended</div>
|
|
184
|
+
<div class="stat-grid" style="margin-top:12px">
|
|
185
|
+
<span class="label" title="The model pi is currently using — its pricing drives the cost figure.">Model</span><span class="value" id="md-name">—</span>
|
|
186
|
+
<span class="label" title="The provider serving the model.">Provider</span><span class="value" id="md-provider">—</span>
|
|
187
|
+
<span class="label" title="USD per input token, from the model's pricing.">Input Rate</span><span class="value" id="md-input">—</span>
|
|
188
|
+
<span class="label" title="USD per output token, from the model's pricing.">Output Rate</span><span class="value" id="md-output">—</span>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="card">
|
|
192
|
+
<h2>Crew / Agents</h2>
|
|
193
|
+
<div class="stat-grid">
|
|
194
|
+
<span class="label">Active Agents</span><span class="value" id="cr-agents">0</span>
|
|
195
|
+
<span class="label">Current Turn</span><span class="value" id="cr-turn">0</span>
|
|
196
|
+
<span class="label">Status</span><span class="value" id="cr-status">idle</span>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="card legend">
|
|
200
|
+
<h2>What these numbers mean</h2>
|
|
201
|
+
<ul class="legend-list">
|
|
202
|
+
<li><b>Original (dropped)</b> — everything compacted away (including duplicates caught by dedup). The "in."</li>
|
|
203
|
+
<li><b>Kept (summaries)</b> — compact summaries still held as "memory" (the "out").</li>
|
|
204
|
+
<li><b>Freed</b> = dropped − kept — tokens saved so far (higher = better).</li>
|
|
205
|
+
<li><b>Compression %</b> — Freed ÷ Dropped — the headline efficiency number. Higher = more space reclaimed.</li>
|
|
206
|
+
<li><b>Storage dedup %</b> — how often new content matched something already saved, so no duplicate copy was written.</li>
|
|
207
|
+
<li><b>Data safety</b> — every compacted region is kept verbatim (compressed). Nothing is permanently deleted; you can restore any of it.</li>
|
|
208
|
+
</ul>
|
|
209
|
+
<p class="legend-note">Hover any label above for a quick explanation.</p>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="card">
|
|
212
|
+
<h2>💾 Cache Hits & Compactions</h2>
|
|
213
|
+
<div class="stat-grid">
|
|
214
|
+
<span class="label">Cache Hits (session)</span><span class="value" id="ch-session">0</span>
|
|
215
|
+
<span class="label">Cache Hits (total)</span><span class="value" id="ch-total">0</span>
|
|
216
|
+
<span class="label">Tokens Saved (session)</span><span class="value" id="ch-tok-session">0</span>
|
|
217
|
+
<span class="label">Tokens Saved (total)</span><span class="value" id="ch-tok-total">0</span>
|
|
218
|
+
<span class="label">Compactions (session)</span><span class="value" id="cp-session">0</span>
|
|
219
|
+
<span class="label">Compactions (total)</span><span class="value" id="cp-total">0</span>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="card">
|
|
223
|
+
<h2>⏱ Time Saved (est.)</h2>
|
|
224
|
+
<div class="stat-grid">
|
|
225
|
+
<span class="label">Compact (session)</span><span class="value" id="ts-compact-session">0</span>
|
|
226
|
+
<span class="label">Compact (total)</span><span class="value" id="ts-compact-total">0</span>
|
|
227
|
+
<span class="label">Cache Hit (session)</span><span class="value" id="ts-cache-session">0</span>
|
|
228
|
+
<span class="label">Cache Hit (total)</span><span class="value" id="ts-cache-total">0</span>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div class="events">
|
|
234
|
+
<h2>Event Stream</h2>
|
|
235
|
+
<div class="events-wrap" id="events"><div class="empty">connecting…</div></div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<h2 style="margin-top:24px;font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px">All Repositories</h2>
|
|
239
|
+
<table class="repos">
|
|
240
|
+
<thead>
|
|
241
|
+
<tr>
|
|
242
|
+
<th>Repo</th><th>Model</th>
|
|
243
|
+
<th style="text-align:right">Checkpoints</th>
|
|
244
|
+
<th style="text-align:right">Tokens Saved</th>
|
|
245
|
+
<th style="text-align:right">Retained</th>
|
|
246
|
+
<th style="text-align:right">Last Compacted</th>
|
|
247
|
+
</tr>
|
|
248
|
+
</thead>
|
|
249
|
+
<tbody id="cur-rows"><tr><td colspan="6" class="repo-none">loading…</td></tr></tbody>
|
|
250
|
+
</table>
|
|
251
|
+
<div class="updated" id="cur-updated"></div>
|
|
252
|
+
|
|
253
|
+
<div class="updated" id="updated"></div>
|
|
254
|
+
</div><!-- /panel-current -->
|
|
255
|
+
|
|
256
|
+
<!-- Active repos (live cache-hit / compaction stats across machines) -->
|
|
257
|
+
<div class="tab-panel" id="panel-active">
|
|
258
|
+
<div class="card">
|
|
259
|
+
<h2>Active Repos — Live Cache Hits & Compactions</h2>
|
|
260
|
+
<p class="legend-note">Repos seen within the last 30 minutes, with their per-repo cache-hit, compaction, and time-saved (est.) totals pulled live from each repo's dashboard.json.</p>
|
|
261
|
+
<table class="repos">
|
|
262
|
+
<thead>
|
|
263
|
+
<tr>
|
|
264
|
+
<th>Repo</th><th>Model</th><th>Tier</th>
|
|
265
|
+
<th style="text-align:right">Context %</th><th>State</th>
|
|
266
|
+
<th style="text-align:right">Compactions (s/t)</th>
|
|
267
|
+
<th style="text-align:right">Cache Hits (s/t)</th>
|
|
268
|
+
<th style="text-align:right">Compact s/t (s)</th>
|
|
269
|
+
<th style="text-align:right">CacheHit s/t (s)</th>
|
|
270
|
+
</tr>
|
|
271
|
+
</thead>
|
|
272
|
+
<tbody id="active-rows"><tr><td colspan="9" class="repo-none">loading…</td></tr></tbody>
|
|
273
|
+
</table>
|
|
274
|
+
<div class="updated" id="active-updated"></div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<!-- Per-repo detail modal -->
|
|
279
|
+
<div class="repo-detail" id="repo-detail">
|
|
280
|
+
<div class="repo-detail-box">
|
|
281
|
+
<h2><span id="rd-name">Repo</span><button class="repo-close" id="rd-close" title="Close">×</button></h2>
|
|
282
|
+
<div class="repo-path" id="rd-path"></div>
|
|
283
|
+
<div class="stat-grid">
|
|
284
|
+
<span class="label">Model</span><span class="value" id="rd-model">—</span>
|
|
285
|
+
<span class="label">Checkpoints</span><span class="value" id="rd-cp">0</span>
|
|
286
|
+
<span class="label">Tokens Saved</span><span class="value" id="rd-saved">0</span>
|
|
287
|
+
<span class="label">Compressed-Original</span><span class="value" id="rd-bytes">0 B</span>
|
|
288
|
+
<span class="label">Last Compacted</span><span class="value" id="rd-when">—</span>
|
|
289
|
+
<span class="label">Provider</span><span class="value" id="rd-provider">—</span>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<!-- All repos (machine-wide registry from index.sqlite) -->
|
|
295
|
+
<div class="tab-panel" id="panel-all">
|
|
296
|
+
<table class="repos">
|
|
297
|
+
<thead>
|
|
298
|
+
<tr>
|
|
299
|
+
<th>Repo</th><th>Model</th>
|
|
300
|
+
<th style="text-align:right">Checkpoints</th>
|
|
301
|
+
<th style="text-align:right">Tokens Saved</th>
|
|
302
|
+
<th style="text-align:right">Retained</th>
|
|
303
|
+
<th style="text-align:right">Last Compacted</th>
|
|
304
|
+
</tr>
|
|
305
|
+
</thead>
|
|
306
|
+
<tbody id="all-rows"><tr><td colspan="6" class="repo-none">loading…</td></tr></tbody>
|
|
307
|
+
</table>
|
|
308
|
+
<div class="updated" id="all-updated"></div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<!-- Summary (aggregate across all repos) -->
|
|
312
|
+
<div class="tab-panel" id="panel-summary">
|
|
313
|
+
<div class="summary-grid">
|
|
314
|
+
<div class="summary-card"><div class="num" id="sm-repos">0</div><div class="lbl">Repositories</div></div>
|
|
315
|
+
<div class="summary-card"><div class="num" id="sm-checkpoints">0</div><div class="lbl">Total Checkpoints</div></div>
|
|
316
|
+
<div class="summary-card"><div class="num" id="sm-saved">0</div><div class="lbl">Total Tokens Saved</div></div>
|
|
317
|
+
<div class="summary-card"><div class="num" id="sm-bytes">0 B</div><div class="lbl">Compressed-Original</div></div>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<h2 style="margin-top:24px;font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px">Savings by Model</h2>
|
|
321
|
+
<p class="legend-note" style="margin-bottom:10px">How much context & cost mega-compact has reclaimed, grouped by the model you were running. Compression ratio reflects workload/content, not model quality.</p>
|
|
322
|
+
<table class="repos">
|
|
323
|
+
<thead>
|
|
324
|
+
<tr>
|
|
325
|
+
<th>Model</th><th>Provider</th>
|
|
326
|
+
<th style="text-align:right" title="Tokens dropped from context by compaction (the input reclaimed)">Tokens In</th>
|
|
327
|
+
<th style="text-align:right" title="Tokens kept as compacted summaries still in context (the output retained)">Tokens Out</th>
|
|
328
|
+
<th style="text-align:right">Freed</th>
|
|
329
|
+
<th style="text-align:right" title="Model context window (max input tokens the model accepts)">Ctx Window</th>
|
|
330
|
+
<th style="text-align:right" title="Model max output tokens per turn">Max Out</th>
|
|
331
|
+
<th style="text-align:right" title="Reasoning-capable model">Reas.</th>
|
|
332
|
+
<th style="text-align:right" title="Distinct sessions with at least one checkpoint">Sessions</th>
|
|
333
|
+
<th style="text-align:right">Checkpoints</th>
|
|
334
|
+
<th style="text-align:right" title="USD per input token">In $/tok</th>
|
|
335
|
+
<th style="text-align:right" title="USD per output token">Out $/tok</th>
|
|
336
|
+
<th style="text-align:right">$ Saved</th>
|
|
337
|
+
<th style="text-align:right">Last Used</th>
|
|
338
|
+
</tr>
|
|
339
|
+
</thead>
|
|
340
|
+
<tbody id="bm-rows"><tr><td colspan="14" class="repo-none">loading…</td></tr></tbody>
|
|
341
|
+
</table>
|
|
342
|
+
<p class="legend-note" style="margin-top:8px">Tokens In = Σ original region tokens dropped by compaction. Tokens Out = Σ compacted summary tokens still retained in context. Freed = Tokens In − Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come from the latest captured model snapshot for each repo.</p>
|
|
343
|
+
|
|
344
|
+
<div class="updated" id="sm-updated"></div>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<script>
|
|
348
|
+
(function() {
|
|
349
|
+
var evBox = document.getElementById('events');
|
|
350
|
+
var evBuffer = [];
|
|
351
|
+
var MAX_EV = 50;
|
|
352
|
+
var offlineBanner = document.getElementById('offline-banner');
|
|
353
|
+
|
|
354
|
+
function bullet(el, on, na) {
|
|
355
|
+
el.className = 'bullet ' + (na ? 'bullet-na' : on ? 'bullet-on' : 'bullet-off');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function renderSnapshot(d) {
|
|
359
|
+
if (!d || !d.updatedAt) { offlineBanner.style.display = 'block'; return; }
|
|
360
|
+
offlineBanner.style.display = 'none';
|
|
361
|
+
|
|
362
|
+
var pct = d.context.percent || 0;
|
|
363
|
+
document.getElementById('ctx-pct').textContent = pct + '%';
|
|
364
|
+
var bar = document.getElementById('ctx-bar');
|
|
365
|
+
bar.style.width = Math.max(pct, 1) + '%';
|
|
366
|
+
bar.className = 'meter-fill ' + (pct >= 90 ? 'meter-red' : pct >= 70 ? 'meter-yellow' : 'meter-green');
|
|
367
|
+
var tok = d.context.tokens != null ? d.context.tokens.toLocaleString() : '?';
|
|
368
|
+
var win = d.context.contextWindow ? d.context.contextWindow.toLocaleString() : '?';
|
|
369
|
+
document.getElementById('ctx-sub').textContent = tok + ' / ' + win + ' tokens';
|
|
370
|
+
|
|
371
|
+
bullet(document.getElementById('tr-armed'), d.trigger.armed, false);
|
|
372
|
+
bullet(document.getElementById('tr-ready'), d.trigger.ready, !d.trigger.armed);
|
|
373
|
+
var state = d.trigger.ready ? 'THRESHOLD EXCEEDED — compacting next event' :
|
|
374
|
+
d.trigger.armed ? 'past fast gate — monitoring token count' : 'idle — below fast gate';
|
|
375
|
+
document.getElementById('tr-state').textContent = state;
|
|
376
|
+
|
|
377
|
+
// ---- Vector Store — reconciled token accounting (same formula as widget) -
|
|
378
|
+
document.getElementById('st-count').textContent = d.store.checkpointCount;
|
|
379
|
+
// Compression block from the snapshot (Freed = In − Out, single formula).
|
|
380
|
+
var c = d.compression || {};
|
|
381
|
+
var sess = c.session || { tokensIn:0, tokensOut:0, tokensFreed:0, compressionPct:0, dedupPct:0 };
|
|
382
|
+
var cRepo = c.repo || { tokensIn:0, tokensOut:0, tokensFreed:0, compressionPct:0, dedupPct:0 };
|
|
383
|
+
document.getElementById('st-in').textContent = sess.tokensIn.toLocaleString();
|
|
384
|
+
document.getElementById('st-kept').textContent = sess.tokensOut.toLocaleString();
|
|
385
|
+
document.getElementById('st-freed').textContent = sess.tokensFreed.toLocaleString();
|
|
386
|
+
var sp = sess.compressionPct || 0;
|
|
387
|
+
document.getElementById('st-compress-bar').style.width = Math.max(sp * 100, 0.5) + '%';
|
|
388
|
+
document.getElementById('st-compress-bar').className = 'meter-fill ' + (sp >= 0.9 ? 'meter-green' : sp >= 0.6 ? 'meter-yellow' : 'meter-red');
|
|
389
|
+
document.getElementById('st-compress-sub').textContent = (sp * 100 >= 10 ? Math.round(sp * 100) : (sp * 100).toFixed(1)) + '% tokens saved · dedup: ' + (sess.dedupPct * 100 >= 10 ? Math.round(sess.dedupPct * 100) : (sess.dedupPct * 100).toFixed(1)) + '%';
|
|
390
|
+
// ------
|
|
391
|
+
document.getElementById('st-injected').textContent = d.store.injectedCount;
|
|
392
|
+
document.getElementById('st-dedup').textContent = Math.round(d.store.dedupHitRate * 100) + '%';
|
|
393
|
+
var sdr = d.store.storageDedupRate || 0;
|
|
394
|
+
document.getElementById('st-sdedup').textContent = (sdr * 100 >= 10 ? Math.round(sdr * 100) : (sdr * 100).toFixed(1)) + '%';
|
|
395
|
+
document.getElementById('st-collapsed').textContent = d.store.dedupCollapsed || 0;
|
|
396
|
+
document.getElementById('st-lastid').textContent = d.session.lastCheckpointId || '—';
|
|
397
|
+
|
|
398
|
+
// ---- Repo (all sessions) — same compression fields, repo scope ----------
|
|
399
|
+
document.getElementById('rp-count').textContent = (d.repo && d.repo.checkpointCount || 0).toLocaleString();
|
|
400
|
+
document.getElementById('rp-in').textContent = cRepo.tokensIn.toLocaleString();
|
|
401
|
+
document.getElementById('rp-kept').textContent = cRepo.tokensOut.toLocaleString();
|
|
402
|
+
document.getElementById('rp-freed').textContent = cRepo.tokensFreed.toLocaleString();
|
|
403
|
+
document.getElementById('rp-sessions').textContent = (d.repo && d.repo.sessionCount || 0).toLocaleString();
|
|
404
|
+
document.getElementById('rp-collapsed').textContent = (d.repo && d.repo.dedupCollapsed || 0).toLocaleString();
|
|
405
|
+
var rdr = d.repo && d.repo.storageDedupRate || 0;
|
|
406
|
+
document.getElementById('rp-sdedup').textContent = (rdr * 100 >= 10 ? Math.round(rdr * 100) : (rdr * 100).toFixed(1)) + '%';
|
|
407
|
+
var rp = cRepo.compressionPct || 0;
|
|
408
|
+
document.getElementById('rp-compress-bar').style.width = Math.max(rp * 100, 0.5) + '%';
|
|
409
|
+
document.getElementById('rp-compress-bar').className = 'meter-fill ' + (rp >= 0.9 ? 'meter-green' : rp >= 0.6 ? 'meter-yellow' : 'meter-red');
|
|
410
|
+
document.getElementById('rp-compress-sub').textContent = (rp * 100 >= 10 ? Math.round(rp * 100) : (rp * 100).toFixed(1)) + '% tokens saved · dedup: ' + (cRepo.dedupPct * 100 >= 10 ? Math.round(cRepo.dedupPct * 100) : (cRepo.dedupPct * 100).toFixed(1)) + '%';
|
|
411
|
+
|
|
412
|
+
// Data-safety invariant (Phase 0 — trust foundation).
|
|
413
|
+
var ig = d.integrity || { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 };
|
|
414
|
+
function fmtBytes(b) {
|
|
415
|
+
b = b || 0;
|
|
416
|
+
if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
|
|
417
|
+
if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
|
|
418
|
+
return b + ' B';
|
|
419
|
+
}
|
|
420
|
+
document.getElementById('ig-retained').textContent = (ig.regionsRetained || 0).toLocaleString();
|
|
421
|
+
document.getElementById('ig-bytes').textContent = fmtBytes(ig.compressedOriginalBytes);
|
|
422
|
+
document.getElementById('ig-dupes').textContent = (ig.duplicatesCollapsed || 0).toLocaleString();
|
|
423
|
+
document.getElementById('ig-deleted').textContent = fmtBytes(ig.bytesPermanentlyDeleted);
|
|
424
|
+
|
|
425
|
+
// Crew / agents (live sub-agent activity + turn).
|
|
426
|
+
var crew = d.crew || { activeAgents: 0, currentTurn: 0 };
|
|
427
|
+
document.getElementById('cr-agents').textContent = crew.activeAgents || 0;
|
|
428
|
+
document.getElementById('cr-turn').textContent = crew.currentTurn || 0;
|
|
429
|
+
document.getElementById('cr-status').textContent = (crew.activeAgents > 0)
|
|
430
|
+
? ('▶ ' + crew.activeAgents + ' running') : 'idle';
|
|
431
|
+
|
|
432
|
+
// S24: headline tier is the LIVE pressure band; the config card shows the
|
|
433
|
+
// env preset + live pressure ratio so the user sees the system react.
|
|
434
|
+
document.getElementById('hdr-tier').textContent = d.tier;
|
|
435
|
+
document.getElementById('cf-tier').textContent = d.tier + ' (live)';
|
|
436
|
+
document.getElementById('cf-preset').textContent = d.presetTier;
|
|
437
|
+
document.getElementById('cf-pressure').textContent = Math.round((d.pressure || 0) * 100) + '%';
|
|
438
|
+
// (b) Threshold: show the effective token threshold AND the % of the model
|
|
439
|
+
// context window it represents (percentage-based tiers). d.config.tierPct
|
|
440
|
+
// is present on the live snapshot written by the runtime (Phase-1/2a).
|
|
441
|
+
var cfgPct = d.config.tierPct;
|
|
442
|
+
var cw = d.context.contextWindow || 0;
|
|
443
|
+
var thresholdTxt = d.config.thresholdTokens.toLocaleString();
|
|
444
|
+
if (cfgPct != null && cw > 0) {
|
|
445
|
+
thresholdTxt += ' (' + Math.round(cfgPct * 100) + '% of ' + cw.toLocaleString() + ')';
|
|
446
|
+
}
|
|
447
|
+
document.getElementById('cf-threshold').textContent = thresholdTxt;
|
|
448
|
+
// (c) Fast Gate: arming floor — live trim arms once context passes this %.
|
|
449
|
+
document.getElementById('cf-gate').textContent = d.config.fastGatePct + '%';
|
|
450
|
+
document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
|
|
451
|
+
document.getElementById('cf-anchor').textContent = d.config.anchorUserMessages;
|
|
452
|
+
|
|
453
|
+
// --- Active model + cost savings (same calc as /mega-status) ---------------
|
|
454
|
+
var model = d.model;
|
|
455
|
+
document.getElementById('hdr-model').textContent = model && model.name ? model.name : '—';
|
|
456
|
+
document.getElementById('md-name').textContent = model && model.name ? model.name : '—';
|
|
457
|
+
document.getElementById('md-provider').textContent = model && model.providerName ? model.providerName : (model && model.provider ? model.provider : '—');
|
|
458
|
+
document.getElementById('md-input').textContent = model && model.inputRate ? '$' + (model.inputRate).toFixed(6) : '—';
|
|
459
|
+
document.getElementById('md-output').textContent = model && model.outputRate ? '$' + (model.outputRate).toFixed(6) : '—';
|
|
460
|
+
var repoSaved = cRepo.tokensFreed || 0;
|
|
461
|
+
if (model && model.inputRate && repoSaved > 0) {
|
|
462
|
+
var usd = (repoSaved * model.inputRate);
|
|
463
|
+
var win = d.context.contextWindow || 0;
|
|
464
|
+
var windows = win > 0 ? (repoSaved / win).toFixed(1) : '0';
|
|
465
|
+
document.getElementById('cost-usd').textContent = '≈ $' + usd.toFixed(4) + ' saved';
|
|
466
|
+
document.getElementById('cost-windows').textContent = windows + ' context-windows extended';
|
|
467
|
+
} else {
|
|
468
|
+
document.getElementById('cost-usd').textContent = '≈ $0.00 saved';
|
|
469
|
+
document.getElementById('cost-windows').textContent = '0 context-windows extended';
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// --- Cache hits & compactions (live counters) ---------------------------
|
|
473
|
+
var ch = d.cacheHits || { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 };
|
|
474
|
+
var cp = d.compacts || { session: 0, total: 0 };
|
|
475
|
+
var ts = d.timeSaved || { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } };
|
|
476
|
+
document.getElementById('ch-session').textContent = (ch.session || 0).toLocaleString();
|
|
477
|
+
document.getElementById('ch-total').textContent = (ch.total || 0).toLocaleString();
|
|
478
|
+
document.getElementById('ch-tok-session').textContent = (ch.sessionTokensSaved || 0).toLocaleString();
|
|
479
|
+
document.getElementById('ch-tok-total').textContent = (ch.totalTokensSaved || 0).toLocaleString();
|
|
480
|
+
document.getElementById('cp-session').textContent = (cp.session || 0).toLocaleString();
|
|
481
|
+
document.getElementById('cp-total').textContent = (cp.total || 0).toLocaleString();
|
|
482
|
+
document.getElementById('ts-compact-session').textContent = fmtSec(ts.compact.sessionSec);
|
|
483
|
+
document.getElementById('ts-compact-total').textContent = fmtSec(ts.compact.totalSec);
|
|
484
|
+
document.getElementById('ts-cache-session').textContent = fmtSec(ts.cacheHit.sessionSec);
|
|
485
|
+
document.getElementById('ts-cache-total').textContent = fmtSec(ts.cacheHit.totalSec);
|
|
486
|
+
|
|
487
|
+
document.getElementById('updated').textContent = 'Updated ' + new Date(d.updatedAt).toLocaleTimeString();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function sanitize(s) {
|
|
491
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function renderEvent(ev) {
|
|
495
|
+
evBuffer.unshift(ev);
|
|
496
|
+
if (evBuffer.length > MAX_EV) evBuffer.length = MAX_EV;
|
|
497
|
+
evBox.innerHTML = evBuffer.map(function(e) {
|
|
498
|
+
var t = e.ts ? new Date(e.ts).toLocaleTimeString() : '';
|
|
499
|
+
var detail = '';
|
|
500
|
+
if (e.data) {
|
|
501
|
+
if (e.data.checkpointId) detail = sanitize(e.data.checkpointId);
|
|
502
|
+
else if (e.data.query) detail = sanitize(e.data.query.slice(0, 80));
|
|
503
|
+
if (e.data.tokenEstimate != null) detail += ' ' + e.data.tokenEstimate + ' tok';
|
|
504
|
+
if (e.data.deduped) detail += ' (deduped)';
|
|
505
|
+
if (e.data.injected != null) detail = 'injected: ' + e.data.injected + (e.data.empty ? ' (empty)' : '');
|
|
506
|
+
}
|
|
507
|
+
return '<div class="ev">' +
|
|
508
|
+
'<span class="ev-time">' + t + '</span>' +
|
|
509
|
+
'<span class="ev-type ev-type-' + sanitize(e.type) + '">' + sanitize(e.type) + '</span>' +
|
|
510
|
+
'<span class="ev-detail">' + detail + '</span></div>';
|
|
511
|
+
}).join('');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Poll snapshot every 2s
|
|
515
|
+
function pollSnapshot() {
|
|
516
|
+
fetch('/api/snapshot').then(function(r) { return r.json(); }).then(renderSnapshot).catch(function() {}); // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
517
|
+
}
|
|
518
|
+
pollSnapshot();
|
|
519
|
+
setInterval(pollSnapshot, 2000);
|
|
520
|
+
|
|
521
|
+
// SSE for events
|
|
522
|
+
function connectSSE() {
|
|
523
|
+
var es = new EventSource('/api/events');
|
|
524
|
+
es.onmessage = function(msg) {
|
|
525
|
+
try { renderEvent(JSON.parse(msg.data)); } catch(e) {}
|
|
526
|
+
};
|
|
527
|
+
es.onerror = function() {
|
|
528
|
+
es.close();
|
|
529
|
+
setTimeout(connectSSE, 3000);
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
connectSSE();
|
|
533
|
+
|
|
534
|
+
// --- Multi-repo (index.sqlite via /api/index) ---------------------------
|
|
535
|
+
function fmtBytesTop(b) {
|
|
536
|
+
b = b || 0;
|
|
537
|
+
if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
|
|
538
|
+
if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
|
|
539
|
+
return b + ' B';
|
|
540
|
+
}
|
|
541
|
+
function renderIndex(d) {
|
|
542
|
+
d = d || { updatedAt: null, summary: null, repos: [] };
|
|
543
|
+
var repos = d.repos || [];
|
|
544
|
+
var s = d.summary || { totalRepos: 0, totalCheckpoints: 0, totalTokensSaved: 0, totalCompressedOriginalBytes: 0 };
|
|
545
|
+
document.getElementById('sm-repos').textContent = (s.totalRepos || 0).toLocaleString();
|
|
546
|
+
document.getElementById('sm-checkpoints').textContent = (s.totalCheckpoints || 0).toLocaleString();
|
|
547
|
+
document.getElementById('sm-saved').textContent = (s.totalTokensSaved || 0).toLocaleString();
|
|
548
|
+
document.getElementById('sm-bytes').textContent = fmtBytesTop(s.totalCompressedOriginalBytes);
|
|
549
|
+
|
|
550
|
+
// Shared clickable-row renderer for both the in-current table and the
|
|
551
|
+
// All-repos tab — each row opens the per-repo detail modal.
|
|
552
|
+
function rowsHtml() {
|
|
553
|
+
if (!repos.length) return '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
|
|
554
|
+
return repos.map(function(r) {
|
|
555
|
+
var model = r.modelName
|
|
556
|
+
? '<span class="repo-model">' + sanitize(r.modelName) + '</span>'
|
|
557
|
+
: '<span class="repo-none">—</span>';
|
|
558
|
+
var when = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
|
|
559
|
+
return '<tr class="repo-link" data-repo="' + sanitize(r.repoRoot) + '">' +
|
|
560
|
+
'<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
|
|
561
|
+
'<td>' + model + '</td>' +
|
|
562
|
+
'<td class="num">' + (r.checkpointCount || 0).toLocaleString() + '</td>' +
|
|
563
|
+
'<td class="num">' + (r.tokensSaved || 0).toLocaleString() + '</td>' +
|
|
564
|
+
'<td class="num">' + fmtBytesTop(r.compressedOriginalBytes) + '</td>' +
|
|
565
|
+
'<td class="num">' + sanitize(when) + '</td>' +
|
|
566
|
+
'</tr>';
|
|
567
|
+
}).join('');
|
|
568
|
+
}
|
|
569
|
+
document.getElementById('cur-rows').innerHTML = rowsHtml();
|
|
570
|
+
document.getElementById('all-rows').innerHTML = rowsHtml();
|
|
571
|
+
bindRepoRows();
|
|
572
|
+
|
|
573
|
+
var stamp = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
|
|
574
|
+
document.getElementById('cur-updated').textContent = stamp;
|
|
575
|
+
document.getElementById('all-updated').textContent = stamp;
|
|
576
|
+
document.getElementById('sm-updated').textContent = stamp;
|
|
577
|
+
renderByModel(repos);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Savings-by-model aggregation for the Summary tab — groups the machine-
|
|
581
|
+
// wide repo registry by (modelName || '(unknown)') so the user can see how
|
|
582
|
+
// much context + cost mega-compact has reclaimed, broken down by which model
|
|
583
|
+
// they were running. $ Saved = Σ(tokensSaved × inputRate) per model. Sorted
|
|
584
|
+
// by tokens saved descending so the biggest-reclaim model wins the top row.
|
|
585
|
+
function renderByModel(repos) {
|
|
586
|
+
var rows = document.getElementById('bm-rows');
|
|
587
|
+
if (!rows) return;
|
|
588
|
+
if (!repos || !repos.length) {
|
|
589
|
+
rows.innerHTML = '<tr><td colspan="14" class="repo-none">No repositories registered yet.</td></tr>';
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
var groups = {};
|
|
593
|
+
for (var i = 0; i < repos.length; i++) {
|
|
594
|
+
var r = repos[i];
|
|
595
|
+
var key = (r.modelName && String(r.modelName).trim()) || '(unknown)';
|
|
596
|
+
if (!groups[key]) groups[key] = {
|
|
597
|
+
model: key, provider: r.providerName || r.provider || '—', repos: 0, checkpoints: 0,
|
|
598
|
+
tokensSaved: 0, tokensIn: 0, tokensOut: 0, sessions: 0, usd: 0, lastAt: 0,
|
|
599
|
+
inRates: [], outRates: [], ctxWindows: [], maxTokens: [], reasoning: null,
|
|
600
|
+
};
|
|
601
|
+
var g = groups[key];
|
|
602
|
+
g.repos++;
|
|
603
|
+
g.checkpoints += (r.checkpointCount || 0);
|
|
604
|
+
g.tokensSaved += (r.tokensSaved || 0);
|
|
605
|
+
g.tokensIn += (r.tokensDropped || 0);
|
|
606
|
+
g.tokensOut += (r.tokensKept || 0);
|
|
607
|
+
g.sessions += (r.sessions || 0);
|
|
608
|
+
if (r.inputRate) { g.usd += (r.tokensSaved || 0) * r.inputRate; g.inRates.push(r.inputRate); }
|
|
609
|
+
if (r.outputRate) g.outRates.push(r.outputRate);
|
|
610
|
+
if (r.contextWindow) g.ctxWindows.push(r.contextWindow);
|
|
611
|
+
if (r.maxTokens) g.maxTokens.push(r.maxTokens);
|
|
612
|
+
if (r.reasoning != null) g.reasoning = r.reasoning;
|
|
613
|
+
if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt) g.lastAt = r.lastCompactedAt;
|
|
614
|
+
}
|
|
615
|
+
var arr = [];
|
|
616
|
+
for (var k in groups) { if (Object.prototype.hasOwnProperty.call(groups, k)) arr.push(groups[k]); }
|
|
617
|
+
arr.sort(function(a, b) { return b.tokensSaved - a.tokensSaved; });
|
|
618
|
+
// Helper: a set of numeric samples collapses to a single value when all
|
|
619
|
+
// repos in the group agree, otherwise shows the range (min–max) so the
|
|
620
|
+
// user can see mixed-config model groups at a glance.
|
|
621
|
+
function collapseNum(samples) {
|
|
622
|
+
if (!samples || !samples.length) return '—';
|
|
623
|
+
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
|
|
624
|
+
return lo === hi ? lo.toLocaleString() : lo.toLocaleString() + '–' + hi.toLocaleString();
|
|
625
|
+
}
|
|
626
|
+
function collapseRate(samples) {
|
|
627
|
+
if (!samples || !samples.length) return '—';
|
|
628
|
+
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
|
|
629
|
+
var fmt = function(v) { return '$' + v.toFixed(6); };
|
|
630
|
+
return lo === hi ? fmt(lo) : fmt(lo) + '–' + fmt(hi);
|
|
631
|
+
}
|
|
632
|
+
rows.innerHTML = arr.map(function(g) {
|
|
633
|
+
var freed = (g.tokensIn || 0) - (g.tokensOut || 0);
|
|
634
|
+
var usd = g.usd > 0 ? '$' + g.usd.toFixed(4) : '—';
|
|
635
|
+
var when = g.lastAt ? new Date(g.lastAt).toLocaleString() : '—';
|
|
636
|
+
var reas = g.reasoning == null ? '—' : (g.reasoning ? 'yes' : 'no');
|
|
637
|
+
return '<tr>' +
|
|
638
|
+
'<td><span class="repo-model">' + sanitize(g.model) + '</span></td>' +
|
|
639
|
+
'<td>' + sanitize(g.provider) + '</td>' +
|
|
640
|
+
'<td class="num">' + (g.tokensIn || 0).toLocaleString() + '</td>' +
|
|
641
|
+
'<td class="num">' + (g.tokensOut || 0).toLocaleString() + '</td>' +
|
|
642
|
+
'<td class="num">' + freed.toLocaleString() + '</td>' +
|
|
643
|
+
'<td class="num">' + collapseNum(g.ctxWindows) + '</td>' +
|
|
644
|
+
'<td class="num">' + collapseNum(g.maxTokens) + '</td>' +
|
|
645
|
+
'<td class="num">' + reas + '</td>' +
|
|
646
|
+
'<td class="num">' + g.sessions.toLocaleString() + '</td>' +
|
|
647
|
+
'<td class="num">' + g.checkpoints.toLocaleString() + '</td>' +
|
|
648
|
+
'<td class="num">' + collapseRate(g.inRates) + '</td>' +
|
|
649
|
+
'<td class="num">' + collapseRate(g.outRates) + '</td>' +
|
|
650
|
+
'<td class="num">' + sanitize(usd) + '</td>' +
|
|
651
|
+
'<td class="num">' + sanitize(when) + '</td>' +
|
|
652
|
+
'</tr>';
|
|
653
|
+
}).join('');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Per-repo detail modal ---------------------------------------------------
|
|
657
|
+
var detailEl = document.getElementById('repo-detail');
|
|
658
|
+
var indexCache = { repos: [] };
|
|
659
|
+
function openRepoDetail(root) {
|
|
660
|
+
var r = null;
|
|
661
|
+
for (var i = 0; i < indexCache.repos.length; i++) {
|
|
662
|
+
if (indexCache.repos[i].repoRoot === root) { r = indexCache.repos[i]; break; }
|
|
663
|
+
}
|
|
664
|
+
if (!r) return;
|
|
665
|
+
document.getElementById('rd-name').textContent = r.displayName || r.repoRoot;
|
|
666
|
+
document.getElementById('rd-path').textContent = r.repoRoot;
|
|
667
|
+
document.getElementById('rd-model').textContent = r.modelName || '—';
|
|
668
|
+
document.getElementById('rd-provider').textContent = r.providerName || (r.provider || '—');
|
|
669
|
+
document.getElementById('rd-cp').textContent = (r.checkpointCount || 0).toLocaleString();
|
|
670
|
+
document.getElementById('rd-saved').textContent = (r.tokensSaved || 0).toLocaleString();
|
|
671
|
+
document.getElementById('rd-bytes').textContent = fmtBytesTop(r.compressedOriginalBytes);
|
|
672
|
+
document.getElementById('rd-when').textContent = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
|
|
673
|
+
detailEl.classList.add('open');
|
|
674
|
+
}
|
|
675
|
+
document.getElementById('rd-close').addEventListener('click', function() { detailEl.classList.remove('open'); });
|
|
676
|
+
detailEl.addEventListener('click', function(e) { if (e.target === detailEl) detailEl.classList.remove('open'); });
|
|
677
|
+
function bindRepoRows() {
|
|
678
|
+
var rows = document.querySelectorAll('.repo-link');
|
|
679
|
+
for (var i = 0; i < rows.length; i++) {
|
|
680
|
+
rows[i].addEventListener('click', function() { openRepoDetail(this.getAttribute('data-repo')); });
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function pollIndex() {
|
|
684
|
+
fetch('/api/index').then(function(r) { return r.json(); }).then(function(d) { // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
685
|
+
indexCache = d && d.repos ? d : indexCache;
|
|
686
|
+
renderIndex(d);
|
|
687
|
+
}).catch(function() {});
|
|
688
|
+
}
|
|
689
|
+
pollIndex();
|
|
690
|
+
setInterval(pollIndex, 5000);
|
|
691
|
+
|
|
692
|
+
// --- Active repos (live cache-hit / compaction stats) ---------------------
|
|
693
|
+
function fmtSec(s) {
|
|
694
|
+
s = s || 0;
|
|
695
|
+
if (s >= 3600) return (s / 3600).toFixed(1) + 'h';
|
|
696
|
+
if (s >= 60) return Math.round(s / 60) + 'm';
|
|
697
|
+
if (s >= 1) return s.toFixed(1) + 's';
|
|
698
|
+
return Math.round(s * 1000) + 'ms';
|
|
699
|
+
}
|
|
700
|
+
function renderActiveRepos(d) {
|
|
701
|
+
d = d || { updatedAt: null, servers: [] };
|
|
702
|
+
var servers = d.servers || [];
|
|
703
|
+
var rowsEl = document.getElementById('active-rows');
|
|
704
|
+
if (!rowsEl) return;
|
|
705
|
+
if (!servers.length) {
|
|
706
|
+
rowsEl.innerHTML = '<tr><td colspan="9" class="repo-none">No active repositories.</td></tr>';
|
|
707
|
+
} else {
|
|
708
|
+
rowsEl.innerHTML = servers.map(function(r) {
|
|
709
|
+
var ch = r.cacheHits || { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 };
|
|
710
|
+
var cp = r.compacts || { session: 0, total: 0 };
|
|
711
|
+
var ts = r.timeSaved || { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } };
|
|
712
|
+
return '<tr>' +
|
|
713
|
+
'<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
|
|
714
|
+
'<td>' + sanitize(r.model || '—') + '</td>' +
|
|
715
|
+
'<td>' + sanitize(r.tier || '—') + '</td>' +
|
|
716
|
+
'<td class="num">' + (r.contextPct != null ? Math.round(r.contextPct * 100) + '%' : '—') + '</td>' +
|
|
717
|
+
'<td>' + sanitize(r.state || '—') + '</td>' +
|
|
718
|
+
'<td class="num">' + (cp.session || 0) + ' / ' + (cp.total || 0) + '</td>' +
|
|
719
|
+
'<td class="num">' + (ch.session || 0) + ' / ' + (ch.total || 0) + '</td>' +
|
|
720
|
+
'<td class="num">' + fmtSec(ts.compact.sessionSec) + ' / ' + fmtSec(ts.compact.totalSec) + '</td>' +
|
|
721
|
+
'<td class="num">' + fmtSec(ts.cacheHit.sessionSec) + ' / ' + fmtSec(ts.cacheHit.totalSec) + '</td>' +
|
|
722
|
+
'</tr>';
|
|
723
|
+
}).join('');
|
|
724
|
+
}
|
|
725
|
+
var upd = document.getElementById('active-updated');
|
|
726
|
+
if (upd) upd.textContent = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
|
|
727
|
+
}
|
|
728
|
+
function pollServers() {
|
|
729
|
+
fetch('/api/servers').then(function(r) { return r.json(); }).then(renderActiveRepos).catch(function() {}); // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
730
|
+
}
|
|
731
|
+
pollServers();
|
|
732
|
+
setInterval(pollServers, 5000);
|
|
733
|
+
|
|
734
|
+
// --- Tab switching ------------------------------------------------------
|
|
735
|
+
var tabs = document.querySelectorAll('.tab');
|
|
736
|
+
var panels = { current: 'panel-current', all: 'panel-all', active: 'panel-active', summary: 'panel-summary' };
|
|
737
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
738
|
+
tabs[i].addEventListener('click', function() {
|
|
739
|
+
var name = this.getAttribute('data-tab');
|
|
740
|
+
for (var j = 0; j < tabs.length; j++) tabs[j].classList.remove('active');
|
|
741
|
+
this.classList.add('active');
|
|
742
|
+
for (var k in panels) {
|
|
743
|
+
if (Object.prototype.hasOwnProperty.call(panels, k)) {
|
|
744
|
+
var el = document.getElementById(panels[k]);
|
|
745
|
+
if (el) el.classList.toggle('active', k === name);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (name === 'all' || name === 'summary') pollIndex();
|
|
749
|
+
if (name === 'active') pollServers();
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
})();
|
|
753
|
+
</script>
|
|
754
|
+
</body>
|
|
755
|
+
</html>`;
|
|
756
|
+
}
|