pi-mega-compact 0.8.21 → 0.8.22

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 (76) hide show
  1. package/dist/extensions/dashboard-server/dashboard-client-core.js +201 -0
  2. package/dist/extensions/dashboard-server/dashboard-client-game.js +241 -0
  3. package/dist/extensions/dashboard-server/dashboard-client-repos.js +212 -0
  4. package/dist/extensions/dashboard-server/dashboard-client.js +19 -0
  5. package/dist/extensions/dashboard-server/html.js +2 -621
  6. package/dist/extensions/dashboard-server/routes-core.js +62 -0
  7. package/dist/extensions/dashboard-server/routes-game.js +323 -0
  8. package/dist/extensions/dashboard-server/routes-repo.js +170 -0
  9. package/dist/extensions/dashboard-server/routes-sessions.js +159 -0
  10. package/dist/extensions/dashboard-server/routes.js +10 -0
  11. package/dist/extensions/dashboard-server/server.js +26 -623
  12. package/dist/extensions/mega-commands.js +4 -3
  13. package/dist/extensions/mega-events/agent-handlers.js +2 -1
  14. package/dist/extensions/mega-events/compact-handlers.js +26 -0
  15. package/dist/extensions/mega-events/session-handlers.js +2 -1
  16. package/dist/extensions/mega-pipeline/compact.js +3 -2
  17. package/dist/extensions/mega-runtime/state.js +7 -7
  18. package/dist/src/dedup/raptor/promote.test.js +5 -5
  19. package/dist/src/dedup/sprint12.test.js +7 -7
  20. package/dist/src/dedup-engine.test.js +29 -29
  21. package/dist/src/e2e.test.js +38 -38
  22. package/dist/src/engine.js +3 -3
  23. package/dist/src/engine.test.js +6 -6
  24. package/dist/src/importance.js +197 -0
  25. package/dist/src/importance.test.js +372 -0
  26. package/dist/src/ratio.bench.test.js +18 -18
  27. package/dist/src/recall.js +6 -5
  28. package/dist/src/recall.test.js +85 -27
  29. package/dist/src/sprint14.test.js +2 -2
  30. package/dist/src/store/migrate.test.js +5 -5
  31. package/dist/src/store/sprint10.test.js +5 -5
  32. package/dist/src/store/sqlite/global-index.js +5 -174
  33. package/dist/src/store/sqlite/global-sessions.js +190 -0
  34. package/dist/src/vector-read.js +168 -0
  35. package/dist/src/vector-search.js +191 -0
  36. package/dist/src/vectorStore.js +10 -297
  37. package/dist/src/vectorStore.test.js +32 -32
  38. package/extensions/dashboard-server/dashboard-client-core.ts +202 -0
  39. package/extensions/dashboard-server/dashboard-client-game.ts +242 -0
  40. package/extensions/dashboard-server/dashboard-client-repos.ts +213 -0
  41. package/extensions/dashboard-server/dashboard-client.ts +21 -0
  42. package/extensions/dashboard-server/html.ts +2 -621
  43. package/extensions/dashboard-server/routes-core.ts +113 -0
  44. package/extensions/dashboard-server/routes-game.ts +386 -0
  45. package/extensions/dashboard-server/routes-repo.ts +212 -0
  46. package/extensions/dashboard-server/routes-sessions.ts +195 -0
  47. package/extensions/dashboard-server/routes.ts +13 -0
  48. package/extensions/dashboard-server/server.ts +37 -700
  49. package/extensions/mega-commands.ts +4 -3
  50. package/extensions/mega-events/agent-handlers.ts +2 -1
  51. package/extensions/mega-events/compact-handlers.ts +28 -0
  52. package/extensions/mega-events/session-handlers.ts +2 -1
  53. package/extensions/mega-pipeline/compact.ts +3 -2
  54. package/extensions/mega-runtime/state.ts +7 -7
  55. package/extensions/openclaw-mega-compact.ts +2 -2
  56. package/package.json +1 -1
  57. package/src/dedup/raptor/promote.test.ts +5 -5
  58. package/src/dedup/sprint12.test.ts +7 -7
  59. package/src/dedup-engine.test.ts +30 -30
  60. package/src/e2e.test.ts +38 -38
  61. package/src/engine.test.ts +6 -6
  62. package/src/engine.ts +3 -3
  63. package/src/importance.test.ts +538 -0
  64. package/src/importance.ts +312 -0
  65. package/src/ratio.bench.test.ts +18 -18
  66. package/src/recall.test.ts +101 -29
  67. package/src/recall.ts +9 -9
  68. package/src/sprint14.test.ts +2 -2
  69. package/src/store/migrate.test.ts +5 -5
  70. package/src/store/sprint10.test.ts +5 -5
  71. package/src/store/sqlite/global-index.ts +18 -290
  72. package/src/store/sqlite/global-sessions.ts +291 -0
  73. package/src/vector-read.ts +237 -0
  74. package/src/vector-search.ts +231 -0
  75. package/src/vectorStore.test.ts +32 -32
  76. package/src/vectorStore.ts +29 -356
@@ -0,0 +1,168 @@
1
+ /**
2
+ * vector-read.ts — free functions for VectorStore read-only operations.
3
+ *
4
+ * Extracted from vectorStore.ts (PR0 split) to bring it under 500 lines.
5
+ * All functions take `store: VectorStore` as first param and access store
6
+ * fields/props directly via type-cast to access private fields (acceptable
7
+ * since these fields were effectively public via the original methods).
8
+ *
9
+ * Call sites in src/ and extensions/ are rewritten from `store.stats(sid)`
10
+ * → `vectorStats(store, sid)`.
11
+ */
12
+ import { cosineSimilarity } from "./embedder.js";
13
+ import { listCheckpoints, loadSessionState, saveSessionState, setDedupStatus, getDedupStats, repoStats as repoStatsFromStore, dataInvariantStats, } from "./store/sqlite.js";
14
+ import { normalizeSessionId } from "./store.js";
15
+ import { computeRegionHash } from "./vectorStore.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Cosine
18
+ // ---------------------------------------------------------------------------
19
+ /** Convenience wrapper for raw vector cosine similarity (exposed for tests). */
20
+ export function vectorSimilarity(_store, a, b) {
21
+ return cosineSimilarity(a, b);
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // SemDeDup
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * SemDeDup offline cleanup (Sprint 12, QA #17): within a session, mark the
28
+ * lower-quality row of any pair scoring cosine > `threshold` as
29
+ * `dedup_status='removed'` (kept, not deleted — retrieval excludes it). Keeps
30
+ * the row with the higher `tokenEstimate` (more context preserved). Runs as a
31
+ * single scan; idempotent (re-running skips already-removed rows).
32
+ *
33
+ * Returns the number of rows marked removed.
34
+ */
35
+ export function vectorSemDedup(store, sessionId, threshold) {
36
+ const sid = normalizeSessionId(sessionId);
37
+ const stateDir = store.stateDir;
38
+ const cfg = store.cfg;
39
+ const thr = threshold ?? cfg.SEMDEDUP_COSINE;
40
+ const cps = listCheckpoints(sid, stateDir).filter((c) => c.dedupStatus !== "removed");
41
+ let removed = 0;
42
+ for (let i = 0; i < cps.length; i++) {
43
+ for (let j = i + 1; j < cps.length; j++) {
44
+ const a = cps[i];
45
+ const b = cps[j];
46
+ if (a.dedupStatus === "removed" || b.dedupStatus === "removed")
47
+ continue;
48
+ if (cosineSimilarity(a.embedding, b.embedding) > thr) {
49
+ const keep = a.tokenEstimate >= b.tokenEstimate ? a : b;
50
+ const drop = keep === a ? b : a;
51
+ setDedupStatus(drop.checkpointId, sid, "removed", stateDir);
52
+ drop.dedupStatus = "removed";
53
+ removed++;
54
+ }
55
+ }
56
+ }
57
+ return removed;
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Dedup sentinel
61
+ // ---------------------------------------------------------------------------
62
+ /**
63
+ * Dedup sentinel check: has this region already been stored/represented?
64
+ * Consulted by both the persist path and the recall/inline path.
65
+ */
66
+ export function vectorDedupe(store, sessionId, regionHashOrText, isText = false) {
67
+ const stateDir = store.stateDir;
68
+ const sid = normalizeSessionId(sessionId);
69
+ const hash = isText
70
+ ? computeRegionHash(regionHashOrText)
71
+ : regionHashOrText;
72
+ const state = loadSessionState(sid, stateDir);
73
+ if (state.storedRegionHashes.includes(hash))
74
+ return true;
75
+ return listCheckpoints(sid, stateDir).some((c) => c.regionHash === hash);
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Injection tracking
79
+ // ---------------------------------------------------------------------------
80
+ /** Mark a checkpoint as injected into the window (recall dedup). */
81
+ export function vectorMarkInjected(store, sessionId, checkpointId) {
82
+ const stateDir = store.stateDir;
83
+ const sid = normalizeSessionId(sessionId);
84
+ const state = loadSessionState(sid, stateDir);
85
+ if (!state.injectedCheckpointIds.includes(checkpointId)) {
86
+ state.injectedCheckpointIds.push(checkpointId);
87
+ saveSessionState(sid, state, stateDir);
88
+ }
89
+ }
90
+ /** True if this checkpoint was already injected this session. */
91
+ export function vectorWasInjected(store, sessionId, checkpointId) {
92
+ const stateDir = store.stateDir;
93
+ const state = loadSessionState(normalizeSessionId(sessionId), stateDir);
94
+ return state.injectedCheckpointIds.includes(checkpointId);
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // List & TopSimilar
98
+ // ---------------------------------------------------------------------------
99
+ /** All checkpoints for a session (sorted by checkpointId). */
100
+ export function vectorList(store, sessionId) {
101
+ const stateDir = store.stateDir;
102
+ return listCheckpoints(normalizeSessionId(sessionId), stateDir);
103
+ }
104
+ /**
105
+ * Return the n most similar checkpoints to the current (most recent) checkpoint
106
+ * by cosine similarity. Returns fewer than n if the session has fewer checkpoints.
107
+ * The current checkpoint itself is excluded from results.
108
+ */
109
+ export function vectorTopSimilar(store, sessionId, n) {
110
+ const stateDir = store.stateDir;
111
+ const sid = normalizeSessionId(sessionId);
112
+ const checkpoints = listCheckpoints(sid, stateDir);
113
+ if (checkpoints.length <= 1)
114
+ return [];
115
+ const ordered = [...checkpoints].sort((a, b) => a.checkpointId.localeCompare(b.checkpointId));
116
+ const current = ordered[ordered.length - 1];
117
+ const scored = ordered
118
+ .filter((cp) => cp.checkpointId !== current.checkpointId)
119
+ .map((cp) => ({
120
+ checkpoint: cp,
121
+ score: cosineSimilarity(current.embedding, cp.embedding),
122
+ }))
123
+ .sort((a, b) => b.score - a.score);
124
+ return scored.slice(0, n);
125
+ }
126
+ /**
127
+ * Store statistics for status reporting / logging. Returns counts + the last
128
+ * (highest-numbered) checkpoint, or nulls when the session is empty.
129
+ */
130
+ export function vectorStats(store, sessionId) {
131
+ const stateDir = store.stateDir;
132
+ const sid = normalizeSessionId(sessionId);
133
+ const cps = listCheckpoints(sid, stateDir);
134
+ const state = loadSessionState(sid, stateDir);
135
+ const ordered = [...cps].sort((a, b) => a.checkpointId.localeCompare(b.checkpointId));
136
+ const last = ordered[ordered.length - 1];
137
+ const injected = state.injectedCheckpointIds.length;
138
+ const ds = getDedupStats(stateDir);
139
+ const sessionTok = cps.reduce((s, c) => s + (c.tokenEstimate ?? 0), 0);
140
+ const sessionOrig = cps.reduce((s, c) => s + (c.originalTokenEstimate ?? 0), 0);
141
+ const sessionSaved = cps.reduce((s, c) => s + Math.max(0, (c.originalTokenEstimate ?? 0) - (c.tokenEstimate ?? 0)), 0);
142
+ return {
143
+ checkpointCount: cps.length,
144
+ totalTokenEstimate: sessionTok,
145
+ lastCheckpointId: last?.checkpointId,
146
+ lastSummary: last?.summary,
147
+ injectedCount: injected,
148
+ dedupHitRate: cps.length === 0 ? 0 : injected / cps.length,
149
+ storageDedupRate: ds.attempts === 0 ? 0 : ds.deduped / ds.attempts,
150
+ tokensSaved: sessionSaved,
151
+ originalTokens: sessionOrig,
152
+ dedupAttempts: ds.attempts,
153
+ dedupCollapsed: ds.deduped,
154
+ };
155
+ }
156
+ /**
157
+ * Repo-wide stats — aggregates every session in this store (one per repo).
158
+ * Cumulative, resumable, cross-device.
159
+ */
160
+ export function vectorRepoStats(store) {
161
+ const stateDir = store.stateDir;
162
+ return repoStatsFromStore(stateDir);
163
+ }
164
+ /** Data-safety invariant: regions retained vs bytes permanently deleted. */
165
+ export function vectorDataInvariant(store) {
166
+ const stateDir = store.stateDir;
167
+ return dataInvariantStats(stateDir);
168
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * vector-search.ts — free functions for VectorStore search operations.
3
+ *
4
+ * Extracted from vectorStore.ts (PR0 split) to bring it under 500 lines.
5
+ * All functions take `store: VectorStore` as first param and access store
6
+ * fields/props directly via type-cast to access private fields (acceptable
7
+ * since these fields were effectively public via the original methods).
8
+ *
9
+ * Call sites in src/ and extensions/ are rewritten from `store.search(...)`
10
+ * → `vectorSearch(store, ...)` and `store.searchAsync(...)` →
11
+ * `vectorSearchAsync(store, ...)`.
12
+ */
13
+ import { cosineSimilarity } from "./embedder.js";
14
+ import { normalizeSessionId } from "./store.js";
15
+ import { mmrRerank } from "./dedup/mmr.js";
16
+ import { topK } from "./dedup/topk.js";
17
+ import { listCheckpoints, getCheckpoint, maxCheckpointTimestamp, } from "./store/sqlite.js";
18
+ import { initVectorIndex, searchAsync as vectorIndexSearch, } from "./store/vectorIndex.js";
19
+ import { rehydrateRaptorTree, isShadowMode } from "./dedup/raptor/index.js";
20
+ import { stagedExpansion } from "./dedup/raptor/retrieval.js";
21
+ // ---------------------------------------------------------------------------
22
+ // raptorSearchHits — internal helper (NOT exported)
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Serve the RAPTOR tree for a query (Fix D): rehydrate the persisted tree and
26
+ * return its staged-expansion leaf hits as SearchHits. Returns [] when no tree
27
+ * exists (small sessions — flat search remains the path). Best-effort/non-fatal.
28
+ */
29
+ function raptorSearchHits(store, sid, query, k) {
30
+ const t0 = Date.now();
31
+ try {
32
+ const stateDir = store.stateDir;
33
+ const cfg = store.cfg;
34
+ const embedder = store.embedder;
35
+ const record = store.record;
36
+ // S25 gate (a): honor the shadow contract at SERVE time. The tree is still
37
+ // built + persisted (logging-only) but NOT merged into recall while
38
+ // RAPTOR_SHADOW_MODE is anything other than "false".
39
+ if (isShadowMode())
40
+ return [];
41
+ const tree = rehydrateRaptorTree(sid, stateDir);
42
+ if (!tree || !tree.rootId)
43
+ return [];
44
+ // S25 gate (b): freshness + fallback guards. Skip a tree built before the
45
+ // newest checkpoint (stale → may reference trimmed/deduped leaves) or one
46
+ // whose root is a budget-exhausted extractive fallback (level 99).
47
+ if (tree.timedOut)
48
+ return [];
49
+ const maxTs = maxCheckpointTimestamp(sid, stateDir);
50
+ if (tree.builtAt && tree.builtAt < maxTs)
51
+ return [];
52
+ const leafIds = stagedExpansion(query, tree, {
53
+ embedder,
54
+ k,
55
+ topM: cfg.RAPTOR_CLUSTERS_PER_LEVEL,
56
+ mmrLambda: cfg.MMR_LAMBDA,
57
+ });
58
+ if (leafIds.length === 0)
59
+ return [];
60
+ const all = listCheckpoints(sid, stateDir).filter((cp) => cp.dedupStatus !== "removed");
61
+ const qv = embedder.embed(query);
62
+ const hits = [];
63
+ for (const id of leafIds) {
64
+ const cp = all.find((c) => c.checkpointId === id);
65
+ if (cp)
66
+ hits.push({ checkpoint: cp, score: cosineSimilarity(qv, cp.embedding) });
67
+ }
68
+ // S25 monitoring: emit a raptor_serve decision so canary.ts can track
69
+ // p95 latency + the tier's live traffic (non-fatal, best-effort).
70
+ record("RAPTOR", hits.length > 0 ? "new" : "mark_only", `leaves=${leafIds.length}`, Date.now() - t0);
71
+ return hits;
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // vectorSearch
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Semantic search within a session's checkpoints. Returns top-K by cosine
82
+ * similarity, diversified via MMR (QA #10) so a cluster of near-identical
83
+ * hits yields at most a few distinct-relevance results.
84
+ *
85
+ * Heap-based top-K (QA #4, O(N log k)) replaces the old full sort; MMR then
86
+ * reranks the candidate window for diversity.
87
+ */
88
+ export function vectorSearch(store, sessionId, query, k = 3) {
89
+ const stateDir = store.stateDir;
90
+ const cfg = store.cfg;
91
+ const embedder = store.embedder;
92
+ const sid = normalizeSessionId(sessionId);
93
+ const checkpoints = listCheckpoints(sid, stateDir).filter((cp) => cp.dedupStatus !== "removed");
94
+ if (checkpoints.length === 0)
95
+ return [];
96
+ const qv = embedder.embed(query);
97
+ const scored = checkpoints.map((cp) => ({
98
+ checkpoint: cp,
99
+ score: cosineSimilarity(qv, cp.embedding),
100
+ }));
101
+ // Heap top-K over a widened window (2k) so MMR has diverse candidates.
102
+ const window = topK(scored.map((h) => ({ item: h, score: h.score })), Math.max(k * 2, k)).map((s) => s.item);
103
+ // MMR (QA #10) is part of the L2 semantic tier: skip it when L2 is disabled
104
+ // (Sprint 14 flag), returning the plain relevance-ranked window instead.
105
+ if (!cfg.L2_ENABLED)
106
+ return window.slice(0, k);
107
+ // Fix D: when RAPTOR is promoted, ALSO recall high-level tree summaries and
108
+ // merge them with the flat hits via MMR so RAPTOR + flat don't double-cover.
109
+ // RAPTOR returns fewer, broader hits (O(log n) high-level nodes) than the
110
+ // O(n) flat leaves, tightening the block at read time.
111
+ if (cfg.RAPTOR_ENABLED) {
112
+ const rh = raptorSearchHits(store, sid, query, k);
113
+ if (rh.length > 0) {
114
+ const merged = [...window];
115
+ for (const h of rh) {
116
+ if (!merged.some((m) => m.checkpoint.checkpointId === h.checkpoint.checkpointId)) {
117
+ merged.push(h);
118
+ }
119
+ }
120
+ const mmrItems = merged.map((h) => ({
121
+ item: h,
122
+ vector: h.checkpoint.embedding,
123
+ relevance: h.score,
124
+ }));
125
+ return mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
126
+ }
127
+ }
128
+ const mmrItems = window.map((h) => ({
129
+ item: h,
130
+ vector: h.checkpoint.embedding,
131
+ relevance: h.score,
132
+ }));
133
+ const ranked = mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
134
+ return ranked;
135
+ }
136
+ /**
137
+ * Slice 2: async cross-repo (or single-repo) recall via the PGlite/HNSW index.
138
+ *
139
+ * This is the ONLY async recall surface and is a BONUS path — the synchronous
140
+ * `vectorSearch` above remains the default. `opts.repoId` scopes to one repo;
141
+ * omit it for cross-repo nearest-neighbor recall (the headline capability the
142
+ * sync per-session scan cannot provide).
143
+ *
144
+ * Best-effort: if the index is disabled/empty/failing, we fall back to the
145
+ * synchronous per-session `vectorSearch` for THIS repo so callers always get
146
+ * a sensible result. Hydrates each hit's StoredCheckpoint from the authoritative
147
+ * node:sqlite store (the hit's repoId doubles as that repo's stateDir), then
148
+ * MMR-dedupes the merged set.
149
+ */
150
+ export async function vectorSearchAsync(store, sessionId, query, k = 3, opts = {}) {
151
+ const cfg = store.cfg;
152
+ const embedder = store.embedder;
153
+ const sid = normalizeSessionId(sessionId);
154
+ const qv = embedder.embed(query);
155
+ // repoId filter: explicit opts.repoId wins; else this repo unless crossRepo.
156
+ const selfRepo = store.repoId;
157
+ const repoId = opts.repoId ?? (opts.crossRepo ? undefined : selfRepo);
158
+ let indexHits = [];
159
+ try {
160
+ await initVectorIndex();
161
+ indexHits = await vectorIndexSearch(qv, { k: Math.max(k * 2, k), repoId });
162
+ }
163
+ catch {
164
+ indexHits = [];
165
+ }
166
+ if (indexHits.length === 0) {
167
+ // Index empty/unavailable → synchronous per-session fallback (this repo).
168
+ return vectorSearch(store, sid, query, k);
169
+ }
170
+ // Hydrate each index hit from the authoritative node:sqlite store. repoId is
171
+ // that repo's stateDir, so cross-repo hits resolve against their own store.
172
+ // Tag cross-repo hits with their source repoId so the recall block can label
173
+ // them ("from repo <name>"); same-repo hits stay unlabeled.
174
+ const hydrated = [];
175
+ for (const h of indexHits) {
176
+ const cp = getCheckpoint(h.sessionId, h.checkpointId, h.repoId);
177
+ if (cp && cp.dedupStatus !== "removed") {
178
+ const crossRepo = opts.crossRepo && selfRepo && h.repoId && h.repoId !== selfRepo;
179
+ hydrated.push({ checkpoint: cp, score: h.score, repoId: crossRepo ? h.repoId : undefined });
180
+ }
181
+ }
182
+ if (hydrated.length === 0)
183
+ return vectorSearch(store, sid, query, k);
184
+ // MMR-dedupe the merged candidate set for diversity (mirrors sync search).
185
+ const mmrItems = hydrated.map((h) => ({
186
+ item: h,
187
+ vector: h.checkpoint.embedding,
188
+ relevance: h.score,
189
+ }));
190
+ return mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
191
+ }