pi-mega-compact 0.4.17 → 0.4.19
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/dist/extensions/dashboard-server.js +303 -1
- package/dist/extensions/mega-runtime.js +46 -1
- package/dist/src/store/sqlite.js +162 -0
- package/extensions/DASHBOARD.md +18 -5
- package/extensions/dashboard-server.ts +326 -1
- package/extensions/mega-dashboard.ts +8 -0
- package/extensions/mega-runtime.ts +45 -1
- package/package.json +1 -1
- package/src/store/sqlite.ts +214 -0
package/src/store/sqlite.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import Database from "better-sqlite3";
|
|
19
19
|
import { existsSync, mkdirSync } from "node:fs";
|
|
20
|
+
import { homedir, tmpdir } from "node:os";
|
|
20
21
|
import { join } from "node:path";
|
|
21
22
|
import { getStateDir } from "../store.js";
|
|
22
23
|
import type { StoredCheckpoint, SessionState } from "../store.js";
|
|
@@ -62,6 +63,219 @@ export function openStore(stateDir: string = getStateDir()): Database.Database {
|
|
|
62
63
|
return db;
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Global machine-wide index (Phase 5b): a single SQLite DB, separate from every
|
|
68
|
+
// per-repo store, that aggregates one row per repo this machine has run on. The
|
|
69
|
+
// multi-repo dashboard (Summary / All-repos tabs) reads it so ONE dashboard can
|
|
70
|
+
// show every repo's checkpoints, tokens saved, and active model — instead of a
|
|
71
|
+
// per-repo dashboard that only ever sees the repo it was launched from.
|
|
72
|
+
//
|
|
73
|
+
// Written by every pi process on repo-switch (bindRepo) + model capture; read by
|
|
74
|
+
// the dashboard server. Concurrency across 10+ pi processes is handled by WAL +
|
|
75
|
+
// infrequent idempotent upserts (ON CONFLICT). Fully local (PREVENT-PI-004).
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/** Resolve the machine-wide index directory (env-overridable). */
|
|
79
|
+
export function getIndexDir(): string {
|
|
80
|
+
const override = process.env.MEGACOMPACT_INDEX_DIR;
|
|
81
|
+
if (override && override.trim() !== "") return override;
|
|
82
|
+
// homedir() can throw in exotic sandboxes; fall back to tmpdir.
|
|
83
|
+
try {
|
|
84
|
+
return join(homedir(), ".mega-compact-index");
|
|
85
|
+
} catch {
|
|
86
|
+
return join(tmpdir(), ".mega-compact-index");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let indexCache: Database.Database | undefined;
|
|
91
|
+
let indexCacheDir: string | undefined;
|
|
92
|
+
|
|
93
|
+
/** Open (or reuse) the machine-wide index DB. WAL for concurrent writers. */
|
|
94
|
+
export function openIndexStore(indexDir: string = getIndexDir()): Database.Database {
|
|
95
|
+
if (indexCache && indexCacheDir === indexDir) return indexCache;
|
|
96
|
+
if (!existsSync(indexDir)) mkdirSync(indexDir, { recursive: true });
|
|
97
|
+
const db = new Database(join(indexDir, "index.sqlite"));
|
|
98
|
+
db.pragma("journal_mode = WAL");
|
|
99
|
+
db.pragma("busy_timeout = 3000"); // tolerate brief cross-process write contention
|
|
100
|
+
db.exec(`
|
|
101
|
+
CREATE TABLE IF NOT EXISTS repo_registry (
|
|
102
|
+
repo_root TEXT PRIMARY KEY,
|
|
103
|
+
display_name TEXT,
|
|
104
|
+
state_dir TEXT NOT NULL,
|
|
105
|
+
first_seen INTEGER,
|
|
106
|
+
last_seen INTEGER,
|
|
107
|
+
last_compacted_at INTEGER,
|
|
108
|
+
checkpoint_count INTEGER DEFAULT 0,
|
|
109
|
+
tokens_saved INTEGER DEFAULT 0,
|
|
110
|
+
compressed_original_bytes INTEGER DEFAULT 0,
|
|
111
|
+
provider TEXT,
|
|
112
|
+
provider_name TEXT,
|
|
113
|
+
model_name TEXT,
|
|
114
|
+
input_rate REAL,
|
|
115
|
+
output_rate REAL,
|
|
116
|
+
model_captured_at INTEGER
|
|
117
|
+
);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_registry_last_seen ON repo_registry(last_seen DESC);
|
|
119
|
+
`);
|
|
120
|
+
indexCache = db;
|
|
121
|
+
indexCacheDir = indexDir;
|
|
122
|
+
return db;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** One row of the global repo registry (multi-repo dashboard source). */
|
|
126
|
+
export interface RepoRegistryRow {
|
|
127
|
+
repoRoot: string;
|
|
128
|
+
displayName: string;
|
|
129
|
+
stateDir: string;
|
|
130
|
+
firstSeen: number;
|
|
131
|
+
lastSeen: number;
|
|
132
|
+
lastCompactedAt: number | null;
|
|
133
|
+
checkpointCount: number;
|
|
134
|
+
tokensSaved: number;
|
|
135
|
+
compressedOriginalBytes: number;
|
|
136
|
+
provider: string | null;
|
|
137
|
+
providerName: string | null;
|
|
138
|
+
modelName: string | null;
|
|
139
|
+
inputRate: number | null;
|
|
140
|
+
outputRate: number | null;
|
|
141
|
+
modelCapturedAt: number | null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Upsert a repo's aggregate stats into the global index. Called on repo-switch
|
|
146
|
+
* (infrequent). Preserves first_seen + the model columns on update (model is
|
|
147
|
+
* written separately by recordRepoModel so we never clobber it here with nulls).
|
|
148
|
+
*/
|
|
149
|
+
export function upsertRepoRegistry(
|
|
150
|
+
row: {
|
|
151
|
+
repoRoot: string;
|
|
152
|
+
displayName: string;
|
|
153
|
+
stateDir: string;
|
|
154
|
+
checkpointCount: number;
|
|
155
|
+
tokensSaved: number;
|
|
156
|
+
compressedOriginalBytes: number;
|
|
157
|
+
lastCompactedAt?: number | null;
|
|
158
|
+
},
|
|
159
|
+
indexDir: string = getIndexDir(),
|
|
160
|
+
): void {
|
|
161
|
+
const db = openIndexStore(indexDir);
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
db.prepare(
|
|
164
|
+
`INSERT INTO repo_registry
|
|
165
|
+
(repo_root, display_name, state_dir, first_seen, last_seen, last_compacted_at,
|
|
166
|
+
checkpoint_count, tokens_saved, compressed_original_bytes)
|
|
167
|
+
VALUES (@repo_root, @display_name, @state_dir, @now, @now, @last_compacted_at,
|
|
168
|
+
@checkpoint_count, @tokens_saved, @compressed_original_bytes)
|
|
169
|
+
ON CONFLICT(repo_root) DO UPDATE SET
|
|
170
|
+
display_name = excluded.display_name,
|
|
171
|
+
state_dir = excluded.state_dir,
|
|
172
|
+
last_seen = excluded.last_seen,
|
|
173
|
+
last_compacted_at = COALESCE(excluded.last_compacted_at, repo_registry.last_compacted_at),
|
|
174
|
+
checkpoint_count = excluded.checkpoint_count,
|
|
175
|
+
tokens_saved = excluded.tokens_saved,
|
|
176
|
+
compressed_original_bytes = excluded.compressed_original_bytes`,
|
|
177
|
+
).run({
|
|
178
|
+
repo_root: row.repoRoot,
|
|
179
|
+
display_name: row.displayName,
|
|
180
|
+
state_dir: row.stateDir,
|
|
181
|
+
now,
|
|
182
|
+
last_compacted_at: row.lastCompactedAt ?? null,
|
|
183
|
+
checkpoint_count: row.checkpointCount,
|
|
184
|
+
tokens_saved: row.tokensSaved,
|
|
185
|
+
compressed_original_bytes: row.compressedOriginalBytes,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Record the active model/provider for a repo in the global index (denormalized
|
|
191
|
+
* so the All-repos table shows model without opening each repo's DB). Upserts a
|
|
192
|
+
* bare registry row if the repo isn't registered yet.
|
|
193
|
+
*/
|
|
194
|
+
export function recordRepoModel(
|
|
195
|
+
repoRoot: string,
|
|
196
|
+
model: {
|
|
197
|
+
provider: string;
|
|
198
|
+
providerName: string | null;
|
|
199
|
+
modelName: string | null;
|
|
200
|
+
inputRate: number;
|
|
201
|
+
outputRate: number;
|
|
202
|
+
stateDir: string;
|
|
203
|
+
displayName: string;
|
|
204
|
+
},
|
|
205
|
+
indexDir: string = getIndexDir(),
|
|
206
|
+
): void {
|
|
207
|
+
const db = openIndexStore(indexDir);
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
db.prepare(
|
|
210
|
+
`INSERT INTO repo_registry
|
|
211
|
+
(repo_root, display_name, state_dir, first_seen, last_seen,
|
|
212
|
+
provider, provider_name, model_name, input_rate, output_rate, model_captured_at)
|
|
213
|
+
VALUES (@repo_root, @display_name, @state_dir, @now, @now,
|
|
214
|
+
@provider, @provider_name, @model_name, @input_rate, @output_rate, @now)
|
|
215
|
+
ON CONFLICT(repo_root) DO UPDATE SET
|
|
216
|
+
last_seen = excluded.last_seen,
|
|
217
|
+
provider = excluded.provider,
|
|
218
|
+
provider_name = excluded.provider_name,
|
|
219
|
+
model_name = excluded.model_name,
|
|
220
|
+
input_rate = excluded.input_rate,
|
|
221
|
+
output_rate = excluded.output_rate,
|
|
222
|
+
model_captured_at = excluded.model_captured_at`,
|
|
223
|
+
).run({
|
|
224
|
+
repo_root: repoRoot,
|
|
225
|
+
display_name: model.displayName,
|
|
226
|
+
state_dir: model.stateDir,
|
|
227
|
+
now,
|
|
228
|
+
provider: model.provider,
|
|
229
|
+
provider_name: model.providerName,
|
|
230
|
+
model_name: model.modelName,
|
|
231
|
+
input_rate: model.inputRate,
|
|
232
|
+
output_rate: model.outputRate,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function mapRegistryRow(row: any): RepoRegistryRow {
|
|
237
|
+
return {
|
|
238
|
+
repoRoot: row.repo_root,
|
|
239
|
+
displayName: row.display_name ?? "",
|
|
240
|
+
stateDir: row.state_dir,
|
|
241
|
+
firstSeen: row.first_seen ?? 0,
|
|
242
|
+
lastSeen: row.last_seen ?? 0,
|
|
243
|
+
lastCompactedAt: row.last_compacted_at ?? null,
|
|
244
|
+
checkpointCount: row.checkpoint_count ?? 0,
|
|
245
|
+
tokensSaved: row.tokens_saved ?? 0,
|
|
246
|
+
compressedOriginalBytes: row.compressed_original_bytes ?? 0,
|
|
247
|
+
provider: row.provider ?? null,
|
|
248
|
+
providerName: row.provider_name ?? null,
|
|
249
|
+
modelName: row.model_name ?? null,
|
|
250
|
+
inputRate: row.input_rate ?? null,
|
|
251
|
+
outputRate: row.output_rate ?? null,
|
|
252
|
+
modelCapturedAt: row.model_captured_at ?? null,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** All registered repos, most-recently-seen first. */
|
|
257
|
+
export function listRepoRegistry(indexDir: string = getIndexDir()): RepoRegistryRow[] {
|
|
258
|
+
const db = openIndexStore(indexDir);
|
|
259
|
+
const rows = db.prepare("SELECT * FROM repo_registry ORDER BY last_seen DESC").all() as any[];
|
|
260
|
+
return rows.map(mapRegistryRow);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** A single repo's registry row, or undefined. */
|
|
264
|
+
export function getRepoRegistry(repoRoot: string, indexDir: string = getIndexDir()): RepoRegistryRow | undefined {
|
|
265
|
+
const db = openIndexStore(indexDir);
|
|
266
|
+
const row = db.prepare("SELECT * FROM repo_registry WHERE repo_root = ?").get(repoRoot) as any;
|
|
267
|
+
return row ? mapRegistryRow(row) : undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Close the cached index connection (test teardown only). */
|
|
271
|
+
export function closeIndexStore(): void {
|
|
272
|
+
if (indexCache) {
|
|
273
|
+
indexCache.close();
|
|
274
|
+
indexCache = undefined;
|
|
275
|
+
indexCacheDir = undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
65
279
|
function initSchema(db: Database.Database): void {
|
|
66
280
|
db.exec(`
|
|
67
281
|
CREATE TABLE IF NOT EXISTS context_chunks (
|