pi-mega-compact 0.7.8 → 0.8.0
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/html.js +1023 -0
- package/dist/extensions/dashboard-server/html.test.js +41 -0
- package/dist/extensions/dashboard-server/index-reader.js +133 -0
- package/dist/extensions/dashboard-server/server.js +530 -0
- package/dist/extensions/dashboard-server/server.test.js +120 -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-s32.test.js +181 -0
- package/dist/extensions/dashboard-server.js +7 -1315
- package/dist/extensions/mega-commands.js +162 -134
- package/dist/extensions/mega-compact.js +3 -0
- 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 +222 -0
- package/dist/extensions/mega-events/compact-handlers.js +162 -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-game-cmds.js +106 -0
- package/dist/extensions/mega-game-cmds.test.js +113 -0
- 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 +877 -0
- package/dist/extensions/mega-runtime/state.test.js +171 -0
- package/dist/extensions/mega-runtime/widget.js +270 -0
- package/dist/extensions/mega-runtime/widget.test.js +160 -0
- package/dist/extensions/mega-runtime.js +15 -947
- package/dist/src/config/themes.js +84 -0
- package/dist/src/config/themes.test.js +94 -0
- package/dist/src/game/scoring.js +105 -0
- package/dist/src/game/scoring.test.js +98 -0
- package/dist/src/store/sqlite/checkpoints.js +145 -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/game-achievements.js +111 -0
- package/dist/src/store/sqlite/game-achievements.test.js +67 -0
- package/dist/src/store/sqlite/game-scores.js +105 -0
- package/dist/src/store/sqlite/game-scores.test.js +106 -0
- package/dist/src/store/sqlite/game-state.js +54 -0
- package/dist/src/store/sqlite/game-state.test.js +76 -0
- package/dist/src/store/sqlite/global-index.js +224 -0
- package/dist/src/store/sqlite/maintenance.js +235 -0
- package/dist/src/store/sqlite/memories.js +164 -0
- package/dist/src/store/sqlite/meta.js +82 -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 +294 -0
- package/dist/src/store/sqlite/session-state.js +28 -0
- package/dist/src/store/sqlite/stats.js +66 -0
- package/dist/src/store/sqlite/utils.js +120 -0
- package/dist/src/store/sqlite.js +23 -1607
- package/extensions/dashboard-server/html.test.ts +50 -0
- package/extensions/dashboard-server/html.ts +1026 -0
- package/extensions/dashboard-server/index-reader.ts +130 -0
- package/extensions/dashboard-server/server.test.ts +131 -0
- package/extensions/dashboard-server/server.ts +505 -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-s32.test.ts +195 -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-compact.ts +3 -0
- 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 +262 -0
- package/extensions/mega-events/compact-handlers.ts +192 -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-game-cmds.test.ts +137 -0
- package/extensions/mega-game-cmds.ts +122 -0
- 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.test.ts +171 -0
- package/extensions/mega-runtime/state.ts +967 -0
- package/extensions/mega-runtime/widget.test.ts +185 -0
- package/extensions/mega-runtime/widget.ts +359 -0
- package/extensions/mega-runtime.ts +15 -1093
- package/package.json +4 -3
- package/src/config/themes.test.ts +116 -0
- package/src/config/themes.ts +124 -0
- package/src/game/scoring.test.ts +103 -0
- package/src/game/scoring.ts +158 -0
- 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/game-achievements.test.ts +80 -0
- package/src/store/sqlite/game-achievements.ts +147 -0
- package/src/store/sqlite/game-scores.test.ts +132 -0
- package/src/store/sqlite/game-scores.ts +168 -0
- package/src/store/sqlite/game-state.test.ts +89 -0
- package/src/store/sqlite/game-state.ts +87 -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 +305 -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 +23 -2204
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server/index-reader.ts — machine-wide repo registry reader.
|
|
3
|
+
*
|
|
4
|
+
* The extension writes a machine-wide repo registry into a single SQLite DB
|
|
5
|
+
* (<indexDir>/index.sqlite) as the concurrency-safe write path; the dashboard
|
|
6
|
+
* reads that table directly (one read-only connection, opened per request so a
|
|
7
|
+
* concurrent writer's WAL never blocks the request). All registry data lives in
|
|
8
|
+
* SQLite (the project's one-store invariant) — there is no JSON mirror. Same
|
|
9
|
+
* index-dir resolution as src/store/sqlite.ts getIndexDir().
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { DatabaseSync } from "node:sqlite";
|
|
16
|
+
import type { IndexRepo, IndexIndex } from "./types.js";
|
|
17
|
+
|
|
18
|
+
export function getIndexDir(): string {
|
|
19
|
+
const override = process.env.MEGACOMPACT_INDEX_DIR;
|
|
20
|
+
if (override && override.trim() !== "") return override;
|
|
21
|
+
try {
|
|
22
|
+
return join(homedir(), ".mega-compact-index");
|
|
23
|
+
} catch {
|
|
24
|
+
return join("/tmp", ".mega-compact-index");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Read the machine-wide repo registry from SQLite (read-only, single shot). */
|
|
29
|
+
export function readIndex(): IndexIndex | null {
|
|
30
|
+
const indexPath = join(getIndexDir(), "index.sqlite");
|
|
31
|
+
if (!existsSync(indexPath)) return null;
|
|
32
|
+
let db: DatabaseSync | undefined;
|
|
33
|
+
try {
|
|
34
|
+
// Read-only + immutable WAL so a concurrent writer's WAL never blocks us.
|
|
35
|
+
db = new DatabaseSync(indexPath, { readOnly: true });
|
|
36
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
37
|
+
const rows = db
|
|
38
|
+
.prepare("SELECT * FROM repo_registry ORDER BY last_seen DESC")
|
|
39
|
+
.all() as Record<string, unknown>[];
|
|
40
|
+
const mapped: IndexRepo[] = rows.map((r) => ({
|
|
41
|
+
repoRoot: String(r.repo_root ?? ""),
|
|
42
|
+
displayName: String(r.display_name ?? ""),
|
|
43
|
+
stateDir: String(r.state_dir ?? ""),
|
|
44
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
45
|
+
tokensSaved: Number(r.tokens_saved ?? 0),
|
|
46
|
+
compressedOriginalBytes: Number(r.compressed_original_bytes ?? 0),
|
|
47
|
+
lastCompactedAt: (r.last_compacted_at as number | null) ?? null,
|
|
48
|
+
provider: (r.provider as string | null) ?? null,
|
|
49
|
+
providerName: (r.provider_name as string | null) ?? null,
|
|
50
|
+
modelName: (r.model_name as string | null) ?? null,
|
|
51
|
+
inputRate: (r.input_rate as number | null) ?? null,
|
|
52
|
+
outputRate: (r.output_rate as number | null) ?? null,
|
|
53
|
+
lastSeen: Number(r.last_seen ?? 0),
|
|
54
|
+
// Defaults — enriched below from each repo's own store.
|
|
55
|
+
tokensKept: 0,
|
|
56
|
+
tokensDropped: 0,
|
|
57
|
+
sessions: 0,
|
|
58
|
+
contextWindow: null,
|
|
59
|
+
maxTokens: null,
|
|
60
|
+
reasoning: null,
|
|
61
|
+
}));
|
|
62
|
+
// Enrich each repo with per-store token + model detail read directly via
|
|
63
|
+
// node:sqlite (same zero-dependency invariant as readIndex; no store graph
|
|
64
|
+
// import). Best-effort: a missing/corrupt store degrades to the defaults
|
|
65
|
+
// above so the dashboard never fails to render.
|
|
66
|
+
for (const repo of mapped) {
|
|
67
|
+
try {
|
|
68
|
+
const storePath = join(repo.stateDir, "sqlite.db");
|
|
69
|
+
if (existsSync(storePath)) {
|
|
70
|
+
const sdb = new DatabaseSync(storePath, { readOnly: true });
|
|
71
|
+
try {
|
|
72
|
+
const tok = sdb
|
|
73
|
+
.prepare(
|
|
74
|
+
`SELECT COALESCE(SUM(token_estimate),0) AS kept,
|
|
75
|
+
COALESCE(SUM(original_token_estimate),0) AS dropped,
|
|
76
|
+
COUNT(DISTINCT session_id) AS sess
|
|
77
|
+
FROM context_chunks WHERE dedup_status != 'removed'`,
|
|
78
|
+
)
|
|
79
|
+
.get() as { kept: number; dropped: number; sess: number };
|
|
80
|
+
repo.tokensKept = Number(tok.kept ?? 0);
|
|
81
|
+
repo.tokensDropped = Number(tok.dropped ?? 0);
|
|
82
|
+
repo.sessions = Number(tok.sess ?? 0);
|
|
83
|
+
const mrow = sdb
|
|
84
|
+
.prepare(
|
|
85
|
+
`SELECT context_window, max_tokens, reasoning
|
|
86
|
+
FROM model_snapshots ORDER BY captured_at DESC LIMIT 1`,
|
|
87
|
+
)
|
|
88
|
+
.get() as { context_window: number; max_tokens: number; reasoning: number } | undefined;
|
|
89
|
+
if (mrow) {
|
|
90
|
+
repo.contextWindow = Number(mrow.context_window ?? 0) || null;
|
|
91
|
+
repo.maxTokens = Number(mrow.max_tokens ?? 0) || null;
|
|
92
|
+
repo.reasoning = Number(mrow.reasoning ?? 0) === 1;
|
|
93
|
+
}
|
|
94
|
+
} finally {
|
|
95
|
+
sdb.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
/* best-effort — keep the defaults */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Defensive display hygiene (belt-and-suspenders — the real fix is that
|
|
103
|
+
// tests now isolate via MEGACOMPACT_INDEX_DIR): drop transient test/temp
|
|
104
|
+
// paths that should never have been real repos, and collapse duplicate
|
|
105
|
+
// display names to the most-recently-seen row (rows are last_seen DESC, so
|
|
106
|
+
// the first occurrence wins). Keeps the All-repos list readable.
|
|
107
|
+
const isTransient = (p: string) =>
|
|
108
|
+
/^\/tmp\//.test(p) || /^\/private\/tmp\//.test(p) || /^\/var\/folders\//.test(p) ||
|
|
109
|
+
/\/mc-(ext|e2e|resume|recall)-/.test(p);
|
|
110
|
+
const seenName = new Set<string>();
|
|
111
|
+
const repos: IndexRepo[] = [];
|
|
112
|
+
for (const r of mapped) {
|
|
113
|
+
if (isTransient(r.repoRoot)) continue;
|
|
114
|
+
if (seenName.has(r.displayName)) continue;
|
|
115
|
+
seenName.add(r.displayName);
|
|
116
|
+
repos.push(r);
|
|
117
|
+
}
|
|
118
|
+
const summary = {
|
|
119
|
+
totalRepos: repos.length,
|
|
120
|
+
totalCheckpoints: repos.reduce((a, r) => a + r.checkpointCount, 0),
|
|
121
|
+
totalTokensSaved: repos.reduce((a, r) => a + r.tokensSaved, 0),
|
|
122
|
+
totalCompressedOriginalBytes: repos.reduce((a, r) => a + r.compressedOriginalBytes, 0),
|
|
123
|
+
};
|
|
124
|
+
return { updatedAt: new Date().toISOString(), summary, repos };
|
|
125
|
+
} catch {
|
|
126
|
+
return null;
|
|
127
|
+
} finally {
|
|
128
|
+
try { db?.close(); } catch { /* ignore */ }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server.test.ts — S34 /api/game-scores endpoint (GET leaderboard, metric
|
|
3
|
+
* validation, method + limit handling). Mirrors the S32 /api/game-state
|
|
4
|
+
* spawn-and-fetch harness.
|
|
5
|
+
*/
|
|
6
|
+
import { test, describe } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { recordScore } from "../../src/store/sqlite.js";
|
|
13
|
+
|
|
14
|
+
const SERVER_ENTRY = new URL("./server.js", import.meta.url).pathname;
|
|
15
|
+
|
|
16
|
+
function waitFor(cond: () => boolean | Promise<boolean>, timeoutMs = 6000): Promise<void> {
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const tick = async () => {
|
|
20
|
+
if (await cond()) return resolve();
|
|
21
|
+
if (Date.now() - start > timeoutMs) return reject(new Error("timeout"));
|
|
22
|
+
setTimeout(tick, 50);
|
|
23
|
+
};
|
|
24
|
+
tick();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function freshDir(prefix: string): string {
|
|
29
|
+
return mkdtempSync(join(tmpdir(), prefix));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function withServer<T>(
|
|
33
|
+
port: string,
|
|
34
|
+
dir: string,
|
|
35
|
+
fn: (port: number) => Promise<T>,
|
|
36
|
+
): Promise<T> {
|
|
37
|
+
process.env.MEGACOMPACT_DASHBOARD_PORT = port;
|
|
38
|
+
const child = spawn(process.execPath, [SERVER_ENTRY, dir], { stdio: "ignore" });
|
|
39
|
+
try {
|
|
40
|
+
await waitFor(async () => {
|
|
41
|
+
try {
|
|
42
|
+
const raw = JSON.parse(readFileSync(join(dir, "port.pid"), "utf-8"));
|
|
43
|
+
const res = await fetch(`http://localhost:${raw.port}/api/version`);
|
|
44
|
+
return res.ok;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const raw = JSON.parse(readFileSync(join(dir, "port.pid"), "utf-8"));
|
|
50
|
+
return await fn(raw.port);
|
|
51
|
+
} finally {
|
|
52
|
+
child.kill("SIGTERM");
|
|
53
|
+
delete process.env.MEGACOMPACT_DASHBOARD_PORT;
|
|
54
|
+
rmSync(dir, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("S34 /api/game-scores", () => {
|
|
59
|
+
test("GET ?metric=cache returns recorded rows as JSON array", async () => {
|
|
60
|
+
const dir = freshDir("dash-gs-cache-");
|
|
61
|
+
recordScore(dir, { repo_root: "/repo/a", metric: "cache", value: 42 });
|
|
62
|
+
await withServer("19430", dir, async (port) => {
|
|
63
|
+
const res = await fetch(`http://localhost:${port}/api/game-scores?metric=cache`);
|
|
64
|
+
assert.equal(res.status, 200);
|
|
65
|
+
const rows = (await res.json()) as Array<{ repo_root: string; value: number; ts: number }>;
|
|
66
|
+
assert.ok(Array.isArray(rows));
|
|
67
|
+
assert.equal(rows.length, 1);
|
|
68
|
+
assert.equal(rows[0].repo_root, "/repo/a");
|
|
69
|
+
assert.equal(rows[0].value, 42);
|
|
70
|
+
assert.ok(typeof rows[0].ts === "number");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("unknown metric -> 400", async () => {
|
|
75
|
+
const dir = freshDir("dash-gs-bad-");
|
|
76
|
+
await withServer("19431", dir, async (port) => {
|
|
77
|
+
const res = await fetch(`http://localhost:${port}/api/game-scores?metric=bogus`);
|
|
78
|
+
assert.equal(res.status, 400);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("non-GET (POST) -> 405", async () => {
|
|
83
|
+
const dir = freshDir("dash-gs-meth-");
|
|
84
|
+
await withServer("19432", dir, async (port) => {
|
|
85
|
+
const res = await fetch(`http://localhost:${port}/api/game-scores`, { method: "POST" });
|
|
86
|
+
assert.equal(res.status, 405);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("limit clamp: 3 rows -> default 10 returns 3, limit=2 caps to 2, limit<=0 clamps to >=1", async () => {
|
|
91
|
+
const dir = freshDir("dash-gs-lim-");
|
|
92
|
+
recordScore(dir, { repo_root: "/repo/a", metric: "cache", value: 1 });
|
|
93
|
+
recordScore(dir, { repo_root: "/repo/b", metric: "cache", value: 2 });
|
|
94
|
+
recordScore(dir, { repo_root: "/repo/c", metric: "cache", value: 3 });
|
|
95
|
+
await withServer("19433", dir, async (port) => {
|
|
96
|
+
const all = (await fetch(`http://localhost:${port}/api/game-scores?metric=cache`).then((r) => r.json())) as unknown[];
|
|
97
|
+
assert.equal(all.length, 3);
|
|
98
|
+
const capped = (await fetch(`http://localhost:${port}/api/game-scores?metric=cache&limit=2`).then((r) => r.json())) as unknown[];
|
|
99
|
+
assert.equal(capped.length, 2);
|
|
100
|
+
const zero = (await fetch(`http://localhost:${port}/api/game-scores?metric=cache&limit=0`).then((r) => r.json())) as unknown[];
|
|
101
|
+
assert.ok(zero.length >= 1, "limit<=0 clamps to >=1 row");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
describe("S35 /api/achievements", () => {
|
|
108
|
+
test("GET returns the 9 seeded achievement rows", async () => {
|
|
109
|
+
const dir = freshDir("dash-ach-");
|
|
110
|
+
await withServer("19434", dir, async (port) => {
|
|
111
|
+
const res = await fetch(`http://localhost:${port}/api/achievements`);
|
|
112
|
+
assert.equal(res.status, 200);
|
|
113
|
+
const rows = (await res.json()) as Array<{ id: string; title: string; hidden: number; unlocked_at: number | null }>;
|
|
114
|
+
assert.ok(Array.isArray(rows));
|
|
115
|
+
assert.equal(rows.length, 9);
|
|
116
|
+
const opie = rows.find((r) => r.id === "opie_wild_ride");
|
|
117
|
+
assert.ok(opie, "opie_wild_ride seeded");
|
|
118
|
+
assert.equal(opie!.hidden, 1);
|
|
119
|
+
assert.equal(opie!.unlocked_at, null);
|
|
120
|
+
assert.equal(rows.find((r) => r.id === "first_compact")!.title, "First Compact");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("non-GET (POST) -> 405", async () => {
|
|
125
|
+
const dir = freshDir("dash-ach-meth-");
|
|
126
|
+
await withServer("19435", dir, async (port) => {
|
|
127
|
+
const res = await fetch(`http://localhost:${port}/api/achievements`, { method: "POST" });
|
|
128
|
+
assert.equal(res.status, 405);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|