pi-mega-compact 0.8.20 → 0.8.21

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 (38) hide show
  1. package/README.md +6 -0
  2. package/dist/extensions/dashboard-client/src/hooks/useApi.js +51 -0
  3. package/dist/extensions/dashboard-client/src/hooks/useSSE.js +63 -0
  4. package/dist/extensions/mega-compact-s38.test.js +28 -2
  5. package/dist/extensions/mega-events/agent-handlers.js +16 -0
  6. package/dist/extensions/mega-events/error-classifier.js +8 -3
  7. package/extensions/dashboard-client/package-lock.json +1 -1
  8. package/extensions/dashboard-client/package.json +1 -1
  9. package/extensions/mega-compact-s38.test.ts +32 -2
  10. package/extensions/mega-events/agent-handlers.ts +15 -0
  11. package/extensions/mega-events/error-classifier.ts +8 -2
  12. package/package.json +1 -1
  13. package/dist/extensions/dashboard-server/helpers.js +0 -37
  14. package/dist/extensions/dashboard-server/html/all-repos-tab.js +0 -26
  15. package/dist/extensions/dashboard-server/html/body-open.js +0 -23
  16. package/dist/extensions/dashboard-server/html/current-repo-tab.js +0 -130
  17. package/dist/extensions/dashboard-server/html/head-open.js +0 -16
  18. package/dist/extensions/dashboard-server/html/high-score-tab.js +0 -25
  19. package/dist/extensions/dashboard-server/html/repo-detail-modal.js +0 -26
  20. package/dist/extensions/dashboard-server/html/script.js +0 -259
  21. package/dist/extensions/dashboard-server/html/styles.js +0 -103
  22. package/dist/extensions/dashboard-server/html/summary-tab.js +0 -19
  23. package/dist/extensions/dashboard-server/html-template.js +0 -41
  24. package/dist/src/store/sqlite/connection.js +0 -35
  25. package/dist/src/store/sqlite/index-store.js +0 -167
  26. package/dist/src/store/sqlite/memory.js +0 -54
  27. package/dist/src/store/sqlite/minhash-lsh.js +0 -47
  28. package/dist/src/store/sqlite/sessions.js +0 -39
  29. package/dist/src/store/sqlite/transaction.js +0 -19
  30. package/dist/src/vectorStore/add.js +0 -260
  31. package/dist/src/vectorStore/dedup.js +0 -52
  32. package/dist/src/vectorStore/index.js +0 -10
  33. package/dist/src/vectorStore/queries.js +0 -83
  34. package/dist/src/vectorStore/search.js +0 -95
  35. package/dist/src/vectorStore/session.js +0 -19
  36. package/dist/src/vectorStore/store.js +0 -105
  37. package/dist/src/vectorStore/types.js +0 -6
  38. package/dist/src/vectorStore/utils.js +0 -23
@@ -1,26 +0,0 @@
1
- /**
2
- * Per-repo detail modal — fixed overlay opened when a repo row is clicked.
3
- *
4
- * Position-independent in the DOM; the modal is toggled via the `.open`
5
- * class by `script.ts`. Sits between the tab panels and the script so it
6
- * overlays whichever panel is active.
7
- */
8
- export function repoDetailModal() {
9
- return `<!-- Per-repo detail modal -->
10
- <div class="repo-detail" id="repo-detail">
11
- <div class="repo-detail-box">
12
- <h2><span id="rd-name">Repo</span><button class="repo-close" id="rd-close" title="Close">×</button></h2>
13
- <div class="repo-path" id="rd-path"></div>
14
- <div class="stat-grid">
15
- <span class="label">Model</span><span class="value" id="rd-model">—</span>
16
- <span class="label">Checkpoints</span><span class="value" id="rd-cp">0</span>
17
- <span class="label">Tokens Saved</span><span class="value" id="rd-saved">0</span>
18
- <span class="label">Compressed-Original</span><span class="value" id="rd-bytes">0 B</span>
19
- <span class="label">Last Compacted</span><span class="value" id="rd-when">—</span>
20
- <span class="label">Provider</span><span class="value" id="rd-provider">—</span>
21
- </div>
22
- </div>
23
- </div>
24
-
25
- `;
26
- }
@@ -1,259 +0,0 @@
1
- /**
2
- * Client-side script: snapshot polling, SSE event stream, multi-repo index
3
- * polling, per-repo detail modal, and tab switching.
4
- *
5
- * One IIFE — the logic is cross-tab (the index poller writes to both the
6
- * Current-repo table and the All-repos tab) so it stays cohesive rather than
7
- * being split per-tab. `</body></html>` closes the document.
8
- *
9
- * PREVENT-PI-004: the two `fetch(...)` calls below are the dashboard's
10
- * browser-side localhost probes and carry inline `// guardrails-allow
11
- * PREVENT-PI-004: <reason>` annotations (scanner-enforced).
12
- */
13
- export function script() {
14
- return `<script>
15
- (function() {
16
- var evBox = document.getElementById('events');
17
- var evBuffer = [];
18
- var MAX_EV = 50;
19
- var offlineBanner = document.getElementById('offline-banner');
20
-
21
- function bullet(el, on, na) {
22
- el.className = 'bullet ' + (na ? 'bullet-na' : on ? 'bullet-on' : 'bullet-off');
23
- }
24
-
25
- function renderSnapshot(d) {
26
- if (!d || !d.updatedAt) { offlineBanner.style.display = 'block'; return; }
27
- offlineBanner.style.display = 'none';
28
-
29
- var pct = d.context.percent || 0;
30
- document.getElementById('ctx-pct').textContent = pct + '%';
31
- var bar = document.getElementById('ctx-bar');
32
- bar.style.width = Math.max(pct, 1) + '%';
33
- bar.className = 'meter-fill ' + (pct >= 90 ? 'meter-red' : pct >= 70 ? 'meter-yellow' : 'meter-green');
34
- var tok = d.context.tokens != null ? d.context.tokens.toLocaleString() : '?';
35
- var win = d.context.contextWindow ? d.context.contextWindow.toLocaleString() : '?';
36
- document.getElementById('ctx-sub').textContent = tok + ' / ' + win + ' tokens';
37
-
38
- bullet(document.getElementById('tr-armed'), d.trigger.armed, false);
39
- bullet(document.getElementById('tr-ready'), d.trigger.ready, !d.trigger.armed);
40
- var state = d.trigger.ready ? 'THRESHOLD EXCEEDED — compacting next event' :
41
- d.trigger.armed ? 'past fast gate — monitoring token count' : 'idle — below fast gate';
42
- document.getElementById('tr-state').textContent = state;
43
-
44
- document.getElementById('st-count').textContent = d.store.checkpointCount;
45
- document.getElementById('st-tokens').textContent = d.store.totalTokenEstimate.toLocaleString();
46
- document.getElementById('st-orig').textContent = (d.store.originalTokens || 0).toLocaleString();
47
- document.getElementById('st-saved').textContent = (d.store.tokensSaved || 0).toLocaleString();
48
- document.getElementById('st-injected').textContent = d.store.injectedCount;
49
- document.getElementById('st-dedup').textContent = Math.round(d.store.dedupHitRate * 100) + '%';
50
- var sdr = d.store.storageDedupRate || 0;
51
- document.getElementById('st-sdedup').textContent = (sdr * 100 >= 10 ? Math.round(sdr * 100) : (sdr * 100).toFixed(1)) + '%';
52
- document.getElementById('st-collapsed').textContent = d.store.dedupCollapsed || 0;
53
- document.getElementById('st-lastid').textContent = d.session.lastCheckpointId || '—';
54
-
55
- // Repo-wide (all sessions in this repo's SQLite store).
56
- var repo = d.repo || { checkpointCount: 0, totalTokenEstimate: 0, originalTokens: 0, tokensSaved: 0, sessionCount: 0, dedupCollapsed: 0, storageDedupRate: 0 };
57
- document.getElementById('rp-count').textContent = repo.checkpointCount;
58
- document.getElementById('rp-tokens').textContent = repo.totalTokenEstimate.toLocaleString();
59
- document.getElementById('rp-orig').textContent = (repo.originalTokens || 0).toLocaleString();
60
- document.getElementById('rp-saved').textContent = (repo.tokensSaved || 0).toLocaleString();
61
- document.getElementById('rp-sessions').textContent = repo.sessionCount || 0;
62
- document.getElementById('rp-collapsed').textContent = repo.dedupCollapsed || 0;
63
- var rsdr = repo.storageDedupRate || 0;
64
- document.getElementById('rp-sdedup').textContent = (rsdr * 100 >= 10 ? Math.round(rsdr * 100) : (rsdr * 100).toFixed(1)) + '%';
65
-
66
- // Data-safety invariant (Phase 0 — trust foundation).
67
- var ig = d.integrity || { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 };
68
- function fmtBytes(b) {
69
- b = b || 0;
70
- if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
71
- if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
72
- return b + ' B';
73
- }
74
- document.getElementById('ig-retained').textContent = (ig.regionsRetained || 0).toLocaleString();
75
- document.getElementById('ig-bytes').textContent = fmtBytes(ig.compressedOriginalBytes);
76
- document.getElementById('ig-dupes').textContent = (ig.duplicatesCollapsed || 0).toLocaleString();
77
- document.getElementById('ig-deleted').textContent = fmtBytes(ig.bytesPermanentlyDeleted);
78
-
79
- // Crew / agents (live sub-agent activity + turn).
80
- var crew = d.crew || { activeAgents: 0, currentTurn: 0 };
81
- document.getElementById('cr-agents').textContent = crew.activeAgents || 0;
82
- document.getElementById('cr-turn').textContent = crew.currentTurn || 0;
83
- document.getElementById('cr-status').textContent = (crew.activeAgents > 0)
84
- ? ('▶ ' + crew.activeAgents + ' running') : 'idle';
85
-
86
- document.getElementById('cf-tier').textContent = d.tier;
87
- document.getElementById('cf-threshold').textContent = d.config.thresholdTokens.toLocaleString();
88
- document.getElementById('cf-gate').textContent = d.config.fastGatePct + '%';
89
- document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
90
- document.getElementById('cf-anchor').textContent = d.config.anchorUserMessages;
91
-
92
- // --- Active model + cost savings (same calc as /mega-status) ---------------
93
- var model = d.model;
94
- document.getElementById('hdr-model').textContent = model && model.name ? model.name : '—';
95
- document.getElementById('md-name').textContent = model && model.name ? model.name : '—';
96
- document.getElementById('md-provider').textContent = model && model.providerName ? model.providerName : (model && model.provider ? model.provider : '—');
97
- document.getElementById('md-input').textContent = model && model.inputRate ? '$' + (model.inputRate).toFixed(6) : '—';
98
- document.getElementById('md-output').textContent = model && model.outputRate ? '$' + (model.outputRate).toFixed(6) : '—';
99
- if (model && model.inputRate && repo.tokensSaved > 0) {
100
- var usd = (repo.tokensSaved * model.inputRate);
101
- var win = d.context.contextWindow || 0;
102
- var windows = win > 0 ? (repo.tokensSaved / win).toFixed(1) : '0';
103
- document.getElementById('cost-usd').textContent = '≈ $' + usd.toFixed(4) + ' saved';
104
- document.getElementById('cost-windows').textContent = windows + ' context-windows extended';
105
- } else {
106
- document.getElementById('cost-usd').textContent = '≈ $0.00 saved';
107
- document.getElementById('cost-windows').textContent = '0 context-windows extended';
108
- }
109
-
110
- document.getElementById('updated').textContent = 'Updated ' + new Date(d.updatedAt).toLocaleTimeString();
111
- }
112
-
113
- function sanitize(s) {
114
- return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
115
- }
116
-
117
- function renderEvent(ev) {
118
- evBuffer.unshift(ev);
119
- if (evBuffer.length > MAX_EV) evBuffer.length = MAX_EV;
120
- evBox.innerHTML = evBuffer.map(function(e) {
121
- var t = e.ts ? new Date(e.ts).toLocaleTimeString() : '';
122
- var detail = '';
123
- if (e.data) {
124
- if (e.data.checkpointId) detail = sanitize(e.data.checkpointId);
125
- else if (e.data.query) detail = sanitize(e.data.query.slice(0, 80));
126
- if (e.data.tokenEstimate != null) detail += ' ' + e.data.tokenEstimate + ' tok';
127
- if (e.data.deduped) detail += ' (deduped)';
128
- if (e.data.injected != null) detail = 'injected: ' + e.data.injected + (e.data.empty ? ' (empty)' : '');
129
- }
130
- return '<div class="ev">' +
131
- '<span class="ev-time">' + t + '</span>' +
132
- '<span class="ev-type ev-type-' + sanitize(e.type) + '">' + sanitize(e.type) + '</span>' +
133
- '<span class="ev-detail">' + detail + '</span></div>';
134
- }).join('');
135
- }
136
-
137
- // Poll snapshot every 2s
138
- function pollSnapshot() {
139
- fetch('/api/snapshot').then(function(r) { return r.json(); }).then(renderSnapshot).catch(function() {}); // guardrails-allow PREVENT-PI-004: dashboard browser-side fetch (localhost)
140
- }
141
- pollSnapshot();
142
- setInterval(pollSnapshot, 2000);
143
-
144
- // SSE for events
145
- function connectSSE() {
146
- var es = new EventSource('/api/events');
147
- es.onmessage = function(msg) {
148
- try { renderEvent(JSON.parse(msg.data)); } catch(e) {}
149
- };
150
- es.onerror = function() {
151
- es.close();
152
- setTimeout(connectSSE, 3000);
153
- };
154
- }
155
- connectSSE();
156
-
157
- // --- Multi-repo (index.sqlite via /api/index) ---------------------------
158
- function fmtBytesTop(b) {
159
- b = b || 0;
160
- if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
161
- if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
162
- return b + ' B';
163
- }
164
- function renderIndex(d) {
165
- d = d || { updatedAt: null, summary: null, repos: [] };
166
- var repos = d.repos || [];
167
- var s = d.summary || { totalRepos: 0, totalCheckpoints: 0, totalTokensSaved: 0, totalCompressedOriginalBytes: 0 };
168
- document.getElementById('sm-repos').textContent = (s.totalRepos || 0).toLocaleString();
169
- document.getElementById('sm-checkpoints').textContent = (s.totalCheckpoints || 0).toLocaleString();
170
- document.getElementById('sm-saved').textContent = (s.totalTokensSaved || 0).toLocaleString();
171
- document.getElementById('sm-bytes').textContent = fmtBytesTop(s.totalCompressedOriginalBytes);
172
-
173
- // Shared clickable-row renderer for both the in-current table and the
174
- // All-repos tab — each row opens the per-repo detail modal.
175
- function rowsHtml() {
176
- if (!repos.length) return '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
177
- return repos.map(function(r) {
178
- var model = r.modelName
179
- ? '<span class="repo-model">' + sanitize(r.modelName) + '</span>'
180
- : '<span class="repo-none">—</span>';
181
- var when = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
182
- return '<tr class="repo-link" data-repo="' + sanitize(r.repoRoot) + '">' +
183
- '<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
184
- '<td>' + model + '</td>' +
185
- '<td class="num">' + (r.checkpointCount || 0).toLocaleString() + '</td>' +
186
- '<td class="num">' + (r.tokensSaved || 0).toLocaleString() + '</td>' +
187
- '<td class="num">' + fmtBytesTop(r.compressedOriginalBytes) + '</td>' +
188
- '<td class="num">' + sanitize(when) + '</td>' +
189
- '</tr>';
190
- }).join('');
191
- }
192
- document.getElementById('cur-rows').innerHTML = rowsHtml();
193
- document.getElementById('all-rows').innerHTML = rowsHtml();
194
- bindRepoRows();
195
-
196
- var stamp = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
197
- document.getElementById('cur-updated').textContent = stamp;
198
- document.getElementById('all-updated').textContent = stamp;
199
- document.getElementById('sm-updated').textContent = stamp;
200
- }
201
-
202
- // Per-repo detail modal ---------------------------------------------------
203
- var detailEl = document.getElementById('repo-detail');
204
- var indexCache = { repos: [] };
205
- function openRepoDetail(root) {
206
- var r = null;
207
- for (var i = 0; i < indexCache.repos.length; i++) {
208
- if (indexCache.repos[i].repoRoot === root) { r = indexCache.repos[i]; break; }
209
- }
210
- if (!r) return;
211
- document.getElementById('rd-name').textContent = r.displayName || r.repoRoot;
212
- document.getElementById('rd-path').textContent = r.repoRoot;
213
- document.getElementById('rd-model').textContent = r.modelName || '—';
214
- document.getElementById('rd-provider').textContent = r.providerName || (r.provider || '—');
215
- document.getElementById('rd-cp').textContent = (r.checkpointCount || 0).toLocaleString();
216
- document.getElementById('rd-saved').textContent = (r.tokensSaved || 0).toLocaleString();
217
- document.getElementById('rd-bytes').textContent = fmtBytesTop(r.compressedOriginalBytes);
218
- document.getElementById('rd-when').textContent = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
219
- detailEl.classList.add('open');
220
- }
221
- document.getElementById('rd-close').addEventListener('click', function() { detailEl.classList.remove('open'); });
222
- detailEl.addEventListener('click', function(e) { if (e.target === detailEl) detailEl.classList.remove('open'); });
223
- function bindRepoRows() {
224
- var rows = document.querySelectorAll('.repo-link');
225
- for (var i = 0; i < rows.length; i++) {
226
- rows[i].addEventListener('click', function() { openRepoDetail(this.getAttribute('data-repo')); });
227
- }
228
- }
229
- function pollIndex() {
230
- fetch('/api/index').then(function(r) { return r.json(); }).then(function(d) { // guardrails-allow PREVENT-PI-004: dashboard browser-side fetch (localhost)
231
- indexCache = d && d.repos ? d : indexCache;
232
- renderIndex(d);
233
- }).catch(function() {});
234
- }
235
- pollIndex();
236
- setInterval(pollIndex, 5000);
237
-
238
- // --- Tab switching ------------------------------------------------------
239
- var tabs = document.querySelectorAll('.tab');
240
- var panels = { current: 'panel-current', all: 'panel-all', summary: 'panel-summary', highscore: 'panel-highscore' };
241
- for (var i = 0; i < tabs.length; i++) {
242
- tabs[i].addEventListener('click', function() {
243
- var name = this.getAttribute('data-tab');
244
- for (var j = 0; j < tabs.length; j++) tabs[j].classList.remove('active');
245
- this.classList.add('active');
246
- for (var k in panels) {
247
- if (Object.prototype.hasOwnProperty.call(panels, k)) {
248
- var el = document.getElementById(panels[k]);
249
- if (el) el.classList.toggle('active', k === name);
250
- }
251
- }
252
- if (name === 'all' || name === 'summary') pollIndex();
253
- });
254
- }
255
- })();
256
- </script>
257
- </body>
258
- </html>`;
259
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * Inline CSS for the dashboard.
3
- *
4
- * Extracted verbatim from the original monolithic `html-template.ts`.
5
- * Kept as a single cohesive stylesheet — splitting CSS into per-tab files
6
- * would fragment the design system (shared tokens, e.g. `.card`, `.stat-grid`,
7
- * `.meter-*` are reused across all tabs).
8
- */
9
- export function styles() {
10
- return `<style>
11
- * { margin: 0; padding: 0; box-sizing: border-box; }
12
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; line-height: 1.5; }
13
- h1 { font-size: 20px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; color: #f0f6fc; }
14
- h1 .tier { background: #1f6feb; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
15
- .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
16
- .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
17
- .card.safe { border-color: #238636; }
18
- .card.safe h2 { color: #3fb950; }
19
- .safe-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; line-height: 1.5; }
20
- .value.ok { color: #3fb950; }
21
- .label {
22
- cursor: help;
23
- border-bottom: 1px dotted #484f58;
24
- }
25
- .card.legend { grid-column: 1 / -1; }
26
- .legend-list { margin: 0; padding-left: 18px; color: #c9d1d9; }
27
- .legend-list li { margin-bottom: 8px; line-height: 1.5; }
28
- .legend-list b { color: #f0f6fc; }
29
- .legend-note { font-size: 12px; color: #8b949e; margin: 12px 0 0; font-style: italic; }
30
- .card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
31
- .meter-track { background: #21262d; border-radius: 4px; height: 20px; overflow: hidden; margin: 8px 0; }
32
- .meter-fill { height: 100%; border-radius: 4px; transition: width .6s ease; min-width: 2px; }
33
- .meter-green { background: #238636; }
34
- .meter-yellow { background: #d29922; }
35
- .meter-red { background: #f85149; }
36
- .meter-label { font-size: 24px; font-weight: 700; color: #f0f6fc; }
37
- .meter-sub { font-size: 12px; color: #8b949e; }
38
- .status-row { display: flex; align-items: center; gap: 8px; margin: 6px 0; font-size: 14px; }
39
- .status-row .bullet { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
40
- .bullet-on { background: #3fb950; box-shadow: 0 0 6px #3fb95088; }
41
- .bullet-off { background: #484f58; }
42
- .bullet-na { background: #d29922; }
43
- .state-text { font-size: 13px; color: #8b949e; margin-top: 8px; font-family: monospace; }
44
- .stat-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
45
- .stat-grid .label { color: #8b949e; }
46
- .stat-grid .value { color: #f0f6fc; font-weight: 600; font-family: monospace; }
47
- .conf-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
48
- .conf-grid .label { color: #8b949e; }
49
- .conf-grid .value { color: #f0f6fc; font-family: monospace; }
50
- .events { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
51
- .events h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: #8b949e; margin-bottom: 12px; font-weight: 600; }
52
- .events-wrap { max-height: 240px; overflow-y: auto; font-family: monospace; font-size: 12px; }
53
- .ev { padding: 3px 0; border-bottom: 1px solid #21262d; display: flex; gap: 8px; align-items: baseline; }
54
- .ev:last-child { border-bottom: none; }
55
- .ev-type { font-weight: 700; min-width: 70px; text-align: right; }
56
- .ev-type-compact { color: #3fb950; }
57
- .ev-type-recall { color: #a371f7; }
58
- .ev-time { color: #484f58; font-size: 10px; min-width: 80px; }
59
- .ev-detail { color: #8b949e; flex: 1; }
60
- .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
61
- .empty { color: #484f58; font-style: italic; font-size: 13px; padding: 8px 0; }
62
- .offline-banner { background: #f8514922; border: 1px solid #f85149; border-radius: 6px; padding: 10px 16px; margin-bottom: 16px; font-size: 13px; color: #f85149; display: none; }
63
- .tabs { display: flex; gap: 8px; margin-bottom: 20px; }
64
- .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; }
65
- .tab:hover { color: #c9d1d9; border-color: #484f58; }
66
- .tab.active { background: #1f6feb; color: #fff; border-color: #1f6feb; }
67
- .tab.future { opacity: .7; }
68
- .tab .soon { font-size: 9px; font-weight: 700; background: #6e40c9; color: #fff; padding: 1px 6px; border-radius: 8px; margin-left: 6px; text-transform: uppercase; letter-spacing: .5px; }
69
- .tab-panel { display: none; }
70
- .tab-panel.active { display: block; }
71
- .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 20px; }
72
- .summary-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
73
- .summary-card .num { font-size: 24px; font-weight: 700; color: #f0f6fc; }
74
- .summary-card .lbl { font-size: 12px; color: #8b949e; text-transform: uppercase; letter-spacing: .5px; margin-top: 4px; }
75
- table.repos { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
76
- table.repos th, table.repos td { text-align: left; padding: 10px 14px; font-size: 13px; border-bottom: 1px solid #21262d; }
77
- table.repos th { color: #8b949e; text-transform: uppercase; letter-spacing: .5px; font-size: 11px; background: #0d1117; }
78
- table.repos td.num { font-family: monospace; color: #f0f6fc; text-align: right; }
79
- table.repos tr:last-child td { border-bottom: none; }
80
- table.repos tr:hover td { background: #1c2128; }
81
- .repo-model { color: #a371f7; }
82
- .repo-none { color: #484f58; font-style: italic; }
83
- .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
84
- .model-pill { background: #6e40c9; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
85
- .card.cost h2 { color: #a371f7; }
86
- .cost-usd { font-size: 22px; font-weight: 700; color: #3fb950; }
87
- .cost-sub { font-size: 12px; color: #8b949e; margin-top: 4px; }
88
- .repo-link { cursor: pointer; }
89
- .repo-link:hover td { color: #58a6ff; }
90
- .repo-detail { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 50; }
91
- .repo-detail.open { display: flex; }
92
- .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; }
93
- .repo-detail-box h2 { font-size: 14px; color: #f0f6fc; margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
94
- .repo-close { cursor: pointer; color: #8b949e; font-size: 20px; line-height: 1; border: none; background: none; padding: 0 4px; }
95
- .repo-close:hover { color: #f0f6fc; }
96
- .repo-path { font-size: 11px; color: #484f58; word-break: break-all; margin: -8px 0 12px; }
97
- .placeholder { text-align: center; padding: 48px 16px; color: #8b949e; }
98
- .placeholder .em { font-size: 40px; margin-bottom: 12px; }
99
- .placeholder h2 { font-size: 16px; color: #f0f6fc; margin-bottom: 8px; }
100
- .placeholder p { font-size: 13px; line-height: 1.6; max-width: 420px; margin: 0 auto; }
101
- </style>
102
- `;
103
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * "Summary" tab panel — aggregate counts across all registered repos.
3
- *
4
- * Populated by the index poller in `script.ts`.
5
- */
6
- export function summaryTab() {
7
- return `<!-- Summary (aggregate across all repos) -->
8
- <div class="tab-panel" id="panel-summary">
9
- <div class="summary-grid">
10
- <div class="summary-card"><div class="num" id="sm-repos">0</div><div class="lbl">Repositories</div></div>
11
- <div class="summary-card"><div class="num" id="sm-checkpoints">0</div><div class="lbl">Total Checkpoints</div></div>
12
- <div class="summary-card"><div class="num" id="sm-saved">0</div><div class="lbl">Total Tokens Saved</div></div>
13
- <div class="summary-card"><div class="num" id="sm-bytes">0 B</div><div class="lbl">Compressed-Original</div></div>
14
- </div>
15
- <div class="updated" id="sm-updated"></div>
16
- </div>
17
-
18
- `;
19
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * HTML template for the dashboard server — composer.
3
- *
4
- * Originally a single 518-line monolithic file returning one large HTML page
5
- * template. Split into per-concern fragments under `./html/` (each tab is its
6
- * own file; plus shared styles, header chrome, the repo-detail modal, and the
7
- * client script). This file composes them back into the full document.
8
- *
9
- * `tierName` is threaded into the heading badge and the Configuration card.
10
- *
11
- * Fragment layout:
12
- * headOpen() — <!DOCTYPE html>…<title>
13
- * styles() — <style>…</style> (shared CSS, design-system level)
14
- * bodyOpen(tier) — </head><body>…<h1>…</nav> (incl. High Score future tab)
15
- * currentRepoTab(tier) — panel-current (cards + event stream + in-panel table)
16
- * allReposTab() — panel-all (machine-wide repo table)
17
- * summaryTab() — panel-summary (aggregate cards)
18
- * highScoreTab() — panel-highscore (FUTURE placeholder, next milestone)
19
- * repoDetailModal() — per-repo overlay (position-independent)
20
- * script() — <script>…</script></body></html>
21
- */
22
- import { headOpen } from "./html/head-open.js";
23
- import { styles } from "./html/styles.js";
24
- import { bodyOpen } from "./html/body-open.js";
25
- import { currentRepoTab } from "./html/current-repo-tab.js";
26
- import { allReposTab } from "./html/all-repos-tab.js";
27
- import { summaryTab } from "./html/summary-tab.js";
28
- import { highScoreTab } from "./html/high-score-tab.js";
29
- import { repoDetailModal } from "./html/repo-detail-modal.js";
30
- import { script } from "./html/script.js";
31
- export function dashboardHtml(tierName) {
32
- return (headOpen() +
33
- styles() +
34
- bodyOpen(tierName) +
35
- currentRepoTab(tierName) +
36
- repoDetailModal() +
37
- allReposTab() +
38
- summaryTab() +
39
- highScoreTab() +
40
- script());
41
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Per-repo SQLite connection management (open / reuse / close).
3
- *
4
- * In-process cache so the same stateDir reuses one connection (and so a fresh
5
- * VectorStore over the same dir shares the open DB). Cross-process durability
6
- * comes from reopening the same file path — proven by the integration test.
7
- */
8
- import { DatabaseSync } from "node:sqlite";
9
- import { existsSync, mkdirSync } from "node:fs";
10
- import { join } from "node:path";
11
- import { getStateDir } from "../../store.js";
12
- import { initSchema } from "./schema.js";
13
- const cache = new Map();
14
- /** Open (or reuse) the SQLite store for a state dir. */
15
- export function openStore(stateDir = getStateDir()) {
16
- const existing = cache.get(stateDir);
17
- if (existing)
18
- return existing;
19
- if (!existsSync(stateDir))
20
- mkdirSync(stateDir, { recursive: true });
21
- const db = new DatabaseSync(join(stateDir, "sqlite.db"));
22
- db.exec("PRAGMA journal_mode = WAL");
23
- db.exec("PRAGMA foreign_keys = ON");
24
- initSchema(db);
25
- cache.set(stateDir, db);
26
- return db;
27
- }
28
- /** Close and evict a cached connection (test teardown only). */
29
- export function closeStore(stateDir) {
30
- const db = cache.get(stateDir);
31
- if (db) {
32
- db.close();
33
- cache.delete(stateDir);
34
- }
35
- }
@@ -1,167 +0,0 @@
1
- /**
2
- * Global machine-wide index store (Phase 5b).
3
- *
4
- * A single SQLite DB, separate from every per-repo store, that aggregates one
5
- * row per repo this machine has run on. The multi-repo dashboard (Summary /
6
- * All-repos tabs) reads it so ONE dashboard can show every repo's checkpoints,
7
- * tokens saved, and active model — instead of a per-repo dashboard that only
8
- * ever sees the repo it was launched from.
9
- *
10
- * Written by every pi process on repo-switch (bindRepo) + model capture; read by
11
- * the dashboard server. Concurrency across 10+ pi processes is handled by WAL +
12
- * infrequent idempotent upserts (ON CONFLICT). Fully local (PREVENT-PI-004).
13
- */
14
- import { DatabaseSync } from "node:sqlite";
15
- import { existsSync, mkdirSync } from "node:fs";
16
- import { homedir, tmpdir } from "node:os";
17
- import { join } from "node:path";
18
- /** Resolve the machine-wide index directory (env-overridable). */
19
- export function getIndexDir() {
20
- const override = process.env.MEGACOMPACT_INDEX_DIR;
21
- if (override && override.trim() !== "")
22
- return override;
23
- // homedir() can throw in exotic sandboxes; fall back to tmpdir.
24
- try {
25
- return join(homedir(), ".mega-compact-index");
26
- }
27
- catch {
28
- return join(tmpdir(), ".mega-compact-index");
29
- }
30
- }
31
- let indexCache;
32
- let indexCacheDir;
33
- /** Open (or reuse) the machine-wide index DB. WAL for concurrent writers. */
34
- export function openIndexStore(indexDir = getIndexDir()) {
35
- if (indexCache && indexCacheDir === indexDir)
36
- return indexCache;
37
- if (!existsSync(indexDir))
38
- mkdirSync(indexDir, { recursive: true });
39
- const iddb = new DatabaseSync(join(indexDir, "index.sqlite"));
40
- iddb.exec("PRAGMA journal_mode = WAL");
41
- iddb.exec("PRAGMA busy_timeout = 3000"); // tolerate brief cross-process write contention
42
- iddb.exec(`
43
- CREATE TABLE IF NOT EXISTS repo_registry (
44
- repo_root TEXT PRIMARY KEY,
45
- display_name TEXT,
46
- state_dir TEXT NOT NULL,
47
- first_seen INTEGER,
48
- last_seen INTEGER,
49
- last_compacted_at INTEGER,
50
- checkpoint_count INTEGER DEFAULT 0,
51
- tokens_saved INTEGER DEFAULT 0,
52
- compressed_original_bytes INTEGER DEFAULT 0,
53
- provider TEXT,
54
- provider_name TEXT,
55
- model_name TEXT,
56
- input_rate REAL,
57
- output_rate REAL,
58
- model_captured_at INTEGER
59
- );
60
- CREATE INDEX IF NOT EXISTS idx_registry_last_seen ON repo_registry(last_seen DESC);
61
- `);
62
- indexCache = iddb;
63
- indexCacheDir = indexDir;
64
- return iddb;
65
- }
66
- /**
67
- * Upsert a repo's aggregate stats into the global index. Called on repo-switch
68
- * (infrequent). Preserves first_seen + the model columns on update (model is
69
- * written separately by recordRepoModel so we never clobber it here with nulls).
70
- */
71
- export function upsertRepoRegistry(row, indexDir = getIndexDir()) {
72
- const db = openIndexStore(indexDir);
73
- const now = Date.now();
74
- db.prepare(`INSERT INTO repo_registry
75
- (repo_root, display_name, state_dir, first_seen, last_seen, last_compacted_at,
76
- checkpoint_count, tokens_saved, compressed_original_bytes)
77
- VALUES (@repo_root, @display_name, @state_dir, @now, @now, @last_compacted_at,
78
- @checkpoint_count, @tokens_saved, @compressed_original_bytes)
79
- ON CONFLICT(repo_root) DO UPDATE SET
80
- display_name = excluded.display_name,
81
- state_dir = excluded.state_dir,
82
- last_seen = excluded.last_seen,
83
- last_compacted_at = COALESCE(excluded.last_compacted_at, repo_registry.last_compacted_at),
84
- checkpoint_count = excluded.checkpoint_count,
85
- tokens_saved = excluded.tokens_saved,
86
- compressed_original_bytes = excluded.compressed_original_bytes`).run({
87
- repo_root: row.repoRoot,
88
- display_name: row.displayName,
89
- state_dir: row.stateDir,
90
- now,
91
- last_compacted_at: row.lastCompactedAt ?? null,
92
- checkpoint_count: row.checkpointCount,
93
- tokens_saved: row.tokensSaved,
94
- compressed_original_bytes: row.compressedOriginalBytes,
95
- });
96
- }
97
- /**
98
- * Record the active model/provider for a repo in the global index (denormalized
99
- * so the All-repos table shows model without opening each repo's DB). Upserts a
100
- * bare registry row if the repo isn't registered yet.
101
- */
102
- export function recordRepoModel(repoRoot, model, indexDir = getIndexDir()) {
103
- const db = openIndexStore(indexDir);
104
- const now = Date.now();
105
- db.prepare(`INSERT INTO repo_registry
106
- (repo_root, display_name, state_dir, first_seen, last_seen,
107
- provider, provider_name, model_name, input_rate, output_rate, model_captured_at)
108
- VALUES (@repo_root, @display_name, @state_dir, @now, @now,
109
- @provider, @provider_name, @model_name, @input_rate, @output_rate, @now)
110
- ON CONFLICT(repo_root) DO UPDATE SET
111
- last_seen = excluded.last_seen,
112
- provider = excluded.provider,
113
- provider_name = excluded.provider_name,
114
- model_name = excluded.model_name,
115
- input_rate = excluded.input_rate,
116
- output_rate = excluded.output_rate,
117
- model_captured_at = excluded.model_captured_at`).run({
118
- repo_root: repoRoot,
119
- display_name: model.displayName,
120
- state_dir: model.stateDir,
121
- now,
122
- provider: model.provider,
123
- provider_name: model.providerName,
124
- model_name: model.modelName,
125
- input_rate: model.inputRate,
126
- output_rate: model.outputRate,
127
- });
128
- }
129
- function mapRegistryRow(row) {
130
- return {
131
- repoRoot: row.repo_root,
132
- displayName: row.display_name ?? "",
133
- stateDir: row.state_dir,
134
- firstSeen: row.first_seen ?? 0,
135
- lastSeen: row.last_seen ?? 0,
136
- lastCompactedAt: row.last_compacted_at ?? null,
137
- checkpointCount: row.checkpoint_count ?? 0,
138
- tokensSaved: row.tokens_saved ?? 0,
139
- compressedOriginalBytes: row.compressed_original_bytes ?? 0,
140
- provider: row.provider ?? null,
141
- providerName: row.provider_name ?? null,
142
- modelName: row.model_name ?? null,
143
- inputRate: row.input_rate ?? null,
144
- outputRate: row.output_rate ?? null,
145
- modelCapturedAt: row.model_captured_at ?? null,
146
- };
147
- }
148
- /** All registered repos, most-recently-seen first. */
149
- export function listRepoRegistry(indexDir = getIndexDir()) {
150
- const db = openIndexStore(indexDir);
151
- const rows = db.prepare("SELECT * FROM repo_registry ORDER BY last_seen DESC").all();
152
- return rows.map(mapRegistryRow);
153
- }
154
- /** A single repo's registry row, or undefined. */
155
- export function getRepoRegistry(repoRoot, indexDir = getIndexDir()) {
156
- const db = openIndexStore(indexDir);
157
- const row = db.prepare("SELECT * FROM repo_registry WHERE repo_root = ?").get(repoRoot);
158
- return row ? mapRegistryRow(row) : undefined;
159
- }
160
- /** Close the cached index connection (test teardown only). */
161
- export function closeIndexStore() {
162
- if (indexCache) {
163
- indexCache.close();
164
- indexCache = undefined;
165
- indexCacheDir = undefined;
166
- }
167
- }