pi-mega-compact 0.4.25 → 0.4.26
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.
|
@@ -15,6 +15,7 @@ import { C, MARKER_TYPE, } from "./mega-runtime.js";
|
|
|
15
15
|
import { resolveRepoRoot, preserveRecentForPressure } from "./mega-config.js";
|
|
16
16
|
import { runRaptor } from "../src/dedup/raptor/index.js";
|
|
17
17
|
import { loadDedupConfig } from "../src/config/dedup.js";
|
|
18
|
+
import { upsertEmbedding as indexUpsertEmbedding } from "../src/store/vectorIndex.js";
|
|
18
19
|
/** Run the full compaction pipeline and persist a checkpoint. Returns the result. */
|
|
19
20
|
export function runCompact(pi, runtime, config, ctx, messages, opts = {}) {
|
|
20
21
|
runtime.bindRepo(ctx.cwd);
|
|
@@ -131,6 +132,24 @@ export function runCompact(pi, runtime, config, ctx, messages, opts = {}) {
|
|
|
131
132
|
/* non-fatal: tree refresh never blocks a compaction */
|
|
132
133
|
}
|
|
133
134
|
}
|
|
135
|
+
// Slice 2: best-effort mirror of the new checkpoint into the async global
|
|
136
|
+
// PGlite/HNSW vector index. Fires once per compaction (not per-add), so the
|
|
137
|
+
// shared global dir is never hammered by concurrent test workers.
|
|
138
|
+
// Non-fatal: a WASM init failure degrades to the sync scan silently.
|
|
139
|
+
if (!result.deduped) {
|
|
140
|
+
try {
|
|
141
|
+
const all = runtime.store.list(sid);
|
|
142
|
+
const latest = all.find((cp) => cp.checkpointId === result.checkpointId);
|
|
143
|
+
if (latest?.embedding) {
|
|
144
|
+
void indexUpsertEmbedding(runtime.currentStateDir, sid, latest.checkpointId, latest.embedding).catch(() => {
|
|
145
|
+
/* non-fatal: index refresh never blocks a compaction */
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* non-fatal: index refresh never blocks a compaction */
|
|
151
|
+
}
|
|
152
|
+
}
|
|
134
153
|
runtime.setStatus(ctx, runtime.rt.persistedThisSession
|
|
135
154
|
? `mega-compact: ${result.checkpointId} · ${saved} tok saved`
|
|
136
155
|
: `mega-compact: ready`);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
19
|
import { join } from "node:path";
|
|
20
|
-
import { mkdirSync } from "node:fs";
|
|
20
|
+
import { mkdirSync, rmSync, existsSync } from "node:fs";
|
|
21
21
|
// PGlite + pgvector are script-free WASM (no native build) → survive pi's
|
|
22
22
|
// install-script block. Imported lazily so a missing/broken package degrades
|
|
23
23
|
// gracefully instead of crashing module load.
|
|
@@ -70,37 +70,61 @@ export function initVectorIndex() {
|
|
|
70
70
|
return Promise.resolve(db);
|
|
71
71
|
if (initPromise)
|
|
72
72
|
return initPromise;
|
|
73
|
-
initPromise = (
|
|
74
|
-
try {
|
|
75
|
-
const dir = indexDir();
|
|
76
|
-
mkdirSync(dir, { recursive: true });
|
|
77
|
-
const pg = await new PGlite({
|
|
78
|
-
dataDir: dir,
|
|
79
|
-
extensions: { vector },
|
|
80
|
-
});
|
|
81
|
-
await pg.exec("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
82
|
-
await pg.exec(`
|
|
83
|
-
CREATE TABLE IF NOT EXISTS vector_index (
|
|
84
|
-
repo_id TEXT NOT NULL,
|
|
85
|
-
session_id TEXT NOT NULL,
|
|
86
|
-
checkpoint_id TEXT NOT NULL,
|
|
87
|
-
embedding vector(${EMBEDDING_DIM}) NOT NULL,
|
|
88
|
-
PRIMARY KEY (repo_id, session_id, checkpoint_id)
|
|
89
|
-
);
|
|
90
|
-
`);
|
|
91
|
-
// HNSW index over cosine distance for fast NN. Created idempotently.
|
|
92
|
-
await pg.exec("CREATE INDEX IF NOT EXISTS vector_index_hnsw ON vector_index USING hnsw (embedding vector_cosine_ops);");
|
|
93
|
-
db = pg;
|
|
94
|
-
return pg;
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
disabled = true;
|
|
98
|
-
logWarn(`init failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
})();
|
|
73
|
+
initPromise = openPgLite(/* retryOnCorrupt */ true);
|
|
102
74
|
return initPromise;
|
|
103
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Open + schema-init PGlite. When `retryOnCorrupt` is true, a WASM-level
|
|
78
|
+
* abort (typically from a corrupted/torn data dir) triggers a delete + one
|
|
79
|
+
* retry — the dir is rebuilt from scratch by PGlite's initdb.
|
|
80
|
+
*/
|
|
81
|
+
async function openPgLite(retryOnCorrupt) {
|
|
82
|
+
try {
|
|
83
|
+
const dir = indexDir();
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
const pg = await new PGlite({
|
|
86
|
+
dataDir: dir,
|
|
87
|
+
extensions: { vector },
|
|
88
|
+
});
|
|
89
|
+
await pg.exec("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
90
|
+
await pg.exec(`
|
|
91
|
+
CREATE TABLE IF NOT EXISTS vector_index (
|
|
92
|
+
repo_id TEXT NOT NULL,
|
|
93
|
+
session_id TEXT NOT NULL,
|
|
94
|
+
checkpoint_id TEXT NOT NULL,
|
|
95
|
+
embedding vector(${EMBEDDING_DIM}) NOT NULL,
|
|
96
|
+
PRIMARY KEY (repo_id, session_id, checkpoint_id)
|
|
97
|
+
);
|
|
98
|
+
`);
|
|
99
|
+
// HNSW index over cosine distance for fast NN. Created idempotently.
|
|
100
|
+
await pg.exec("CREATE INDEX IF NOT EXISTS vector_index_hnsw ON vector_index USING hnsw (embedding vector_cosine_ops);");
|
|
101
|
+
db = pg;
|
|
102
|
+
return pg;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
// Self-heal: a WASM Aborted() typically means the data dir is corrupted
|
|
107
|
+
// (torn WAL from concurrent access). Delete it and retry once.
|
|
108
|
+
if (retryOnCorrupt &&
|
|
109
|
+
(msg.includes("Aborted") || msg.includes("RuntimeError"))) {
|
|
110
|
+
try {
|
|
111
|
+
const dir = indexDir();
|
|
112
|
+
if (existsSync(dir)) {
|
|
113
|
+
rmSync(dir, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
// Clear singleton state so the retry starts fresh.
|
|
116
|
+
initPromise = undefined;
|
|
117
|
+
return openPgLite(/* retryOnCorrupt */ false);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Self-heal failed — fall through to disable.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
disabled = true;
|
|
124
|
+
logWarn(`init failed: ${msg}`);
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
104
128
|
function toVectorLiteral(v) {
|
|
105
129
|
// pgvector text form: [a,b,c]. Guard against NaN/Inf for a clean literal.
|
|
106
130
|
const parts = v.map((x) => (Number.isFinite(x) ? x : 0));
|
|
@@ -182,6 +206,8 @@ export async function closeVectorIndex() {
|
|
|
182
206
|
}
|
|
183
207
|
db = undefined;
|
|
184
208
|
initPromise = undefined;
|
|
209
|
+
disabled = false;
|
|
210
|
+
warned = false;
|
|
185
211
|
}
|
|
186
212
|
/**
|
|
187
213
|
* Rebuild the entire index from the authoritative node:sqlite store. Used for
|
package/dist/src/vectorStore.js
CHANGED
|
@@ -20,7 +20,7 @@ import { mmrRerank } from "./dedup/mmr.js";
|
|
|
20
20
|
import { topK } from "./dedup/topk.js";
|
|
21
21
|
import { openBloom, saveBloom } from "./store/bloom.js";
|
|
22
22
|
import { listCheckpoints, nextCheckpointId, upsertCheckpoint, getCheckpoint, loadSessionState, saveSessionState, upsertMinhashSignature, insertLshBuckets, lshCandidateChunks, setDedupStatus, addTokensSaved, getDedupStats, bumpDedupStats, repoStats as repoStatsFromStore, dataInvariantStats, } from "./store/sqlite.js";
|
|
23
|
-
import {
|
|
23
|
+
import { initVectorIndex, searchAsync as vectorIndexSearch, } from "./store/vectorIndex.js";
|
|
24
24
|
import { rehydrateRaptorTree } from "./dedup/raptor/index.js";
|
|
25
25
|
import { stagedExpansion } from "./dedup/raptor/retrieval.js";
|
|
26
26
|
import { migrateJsonToSqlite } from "./store/migrate.js";
|
|
@@ -292,12 +292,6 @@ export class VectorStore {
|
|
|
292
292
|
// Cumulative store-wide dedup accounting (attempt, not collapsed).
|
|
293
293
|
bumpDedupStats(false, this.stateDir);
|
|
294
294
|
onTier?.({ tier: "new", status: "stored" });
|
|
295
|
-
// Slice 2: best-effort, fire-and-forget mirror of this new checkpoint into
|
|
296
|
-
// the async global PGlite/HNSW index. NEVER awaited — must not block or
|
|
297
|
-
// throw into the synchronous add() path. On failure the index degrades to
|
|
298
|
-
// the sync scan (handled inside vectorIndex). The node:sqlite store remains
|
|
299
|
-
// authoritative; the index is rebuildable from it at any time.
|
|
300
|
-
void indexUpsertEmbedding(this.repoId, sessionId, checkpointId, checkpoint.embedding);
|
|
301
295
|
return { checkpoint, deduped: false };
|
|
302
296
|
}
|
|
303
297
|
/**
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { resolveRepoRoot, preserveRecentForPressure, type MegaConfig } from "./mega-config.js";
|
|
23
23
|
import { runRaptor } from "../src/dedup/raptor/index.js";
|
|
24
24
|
import { loadDedupConfig } from "../src/config/dedup.js";
|
|
25
|
+
import { upsertEmbedding as indexUpsertEmbedding } from "../src/store/vectorIndex.js";
|
|
25
26
|
|
|
26
27
|
export type RunCompactResult =
|
|
27
28
|
| { skipped: true }
|
|
@@ -164,6 +165,29 @@ export function runCompact(
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
// Slice 2: best-effort mirror of the new checkpoint into the async global
|
|
169
|
+
// PGlite/HNSW vector index. Fires once per compaction (not per-add), so the
|
|
170
|
+
// shared global dir is never hammered by concurrent test workers.
|
|
171
|
+
// Non-fatal: a WASM init failure degrades to the sync scan silently.
|
|
172
|
+
if (!result.deduped) {
|
|
173
|
+
try {
|
|
174
|
+
const all = runtime.store.list(sid);
|
|
175
|
+
const latest = all.find((cp) => cp.checkpointId === result.checkpointId);
|
|
176
|
+
if (latest?.embedding) {
|
|
177
|
+
void indexUpsertEmbedding(
|
|
178
|
+
runtime.currentStateDir,
|
|
179
|
+
sid,
|
|
180
|
+
latest.checkpointId,
|
|
181
|
+
latest.embedding,
|
|
182
|
+
).catch(() => {
|
|
183
|
+
/* non-fatal: index refresh never blocks a compaction */
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
/* non-fatal: index refresh never blocks a compaction */
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
167
191
|
runtime.setStatus(
|
|
168
192
|
ctx,
|
|
169
193
|
runtime.rt.persistedThisSession
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.26",
|
|
4
4
|
"description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BSD-2-Clause",
|
package/src/store/vectorIndex.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { join } from "node:path";
|
|
21
|
-
import { mkdirSync } from "node:fs";
|
|
21
|
+
import { mkdirSync, rmSync, existsSync } from "node:fs";
|
|
22
22
|
|
|
23
23
|
// PGlite + pgvector are script-free WASM (no native build) → survive pi's
|
|
24
24
|
// install-script block. Imported lazily so a missing/broken package degrades
|
|
@@ -82,37 +82,65 @@ export function initVectorIndex(): Promise<PGliteInstance | undefined> {
|
|
|
82
82
|
if (isVectorIndexDisabled()) return Promise.resolve(undefined);
|
|
83
83
|
if (db) return Promise.resolve(db);
|
|
84
84
|
if (initPromise) return initPromise;
|
|
85
|
-
initPromise = (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
85
|
+
initPromise = openPgLite(/* retryOnCorrupt */ true);
|
|
86
|
+
return initPromise;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Open + schema-init PGlite. When `retryOnCorrupt` is true, a WASM-level
|
|
91
|
+
* abort (typically from a corrupted/torn data dir) triggers a delete + one
|
|
92
|
+
* retry — the dir is rebuilt from scratch by PGlite's initdb.
|
|
93
|
+
*/
|
|
94
|
+
async function openPgLite(
|
|
95
|
+
retryOnCorrupt: boolean,
|
|
96
|
+
): Promise<PGliteInstance | undefined> {
|
|
97
|
+
try {
|
|
98
|
+
const dir = indexDir();
|
|
99
|
+
mkdirSync(dir, { recursive: true });
|
|
100
|
+
const pg = await new PGlite({
|
|
101
|
+
dataDir: dir,
|
|
102
|
+
extensions: { vector },
|
|
103
|
+
});
|
|
104
|
+
await pg.exec("CREATE EXTENSION IF NOT EXISTS vector;");
|
|
105
|
+
await pg.exec(`
|
|
106
|
+
CREATE TABLE IF NOT EXISTS vector_index (
|
|
107
|
+
repo_id TEXT NOT NULL,
|
|
108
|
+
session_id TEXT NOT NULL,
|
|
109
|
+
checkpoint_id TEXT NOT NULL,
|
|
110
|
+
embedding vector(${EMBEDDING_DIM}) NOT NULL,
|
|
111
|
+
PRIMARY KEY (repo_id, session_id, checkpoint_id)
|
|
106
112
|
);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
`);
|
|
114
|
+
// HNSW index over cosine distance for fast NN. Created idempotently.
|
|
115
|
+
await pg.exec(
|
|
116
|
+
"CREATE INDEX IF NOT EXISTS vector_index_hnsw ON vector_index USING hnsw (embedding vector_cosine_ops);",
|
|
117
|
+
);
|
|
118
|
+
db = pg;
|
|
119
|
+
return pg;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
122
|
+
// Self-heal: a WASM Aborted() typically means the data dir is corrupted
|
|
123
|
+
// (torn WAL from concurrent access). Delete it and retry once.
|
|
124
|
+
if (
|
|
125
|
+
retryOnCorrupt &&
|
|
126
|
+
(msg.includes("Aborted") || msg.includes("RuntimeError"))
|
|
127
|
+
) {
|
|
128
|
+
try {
|
|
129
|
+
const dir = indexDir();
|
|
130
|
+
if (existsSync(dir)) {
|
|
131
|
+
rmSync(dir, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
// Clear singleton state so the retry starts fresh.
|
|
134
|
+
initPromise = undefined;
|
|
135
|
+
return openPgLite(/* retryOnCorrupt */ false);
|
|
136
|
+
} catch {
|
|
137
|
+
// Self-heal failed — fall through to disable.
|
|
138
|
+
}
|
|
113
139
|
}
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
disabled = true;
|
|
141
|
+
logWarn(`init failed: ${msg}`);
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
116
144
|
}
|
|
117
145
|
|
|
118
146
|
function toVectorLiteral(v: number[]): string {
|
|
@@ -211,6 +239,8 @@ export async function closeVectorIndex(): Promise<void> {
|
|
|
211
239
|
}
|
|
212
240
|
db = undefined;
|
|
213
241
|
initPromise = undefined;
|
|
242
|
+
disabled = false;
|
|
243
|
+
warned = false;
|
|
214
244
|
}
|
|
215
245
|
|
|
216
246
|
/**
|
package/src/vectorStore.ts
CHANGED
|
@@ -40,7 +40,6 @@ import {
|
|
|
40
40
|
dataInvariantStats,
|
|
41
41
|
} from "./store/sqlite.js";
|
|
42
42
|
import {
|
|
43
|
-
upsertEmbedding as indexUpsertEmbedding,
|
|
44
43
|
initVectorIndex,
|
|
45
44
|
searchAsync as vectorIndexSearch,
|
|
46
45
|
type VectorIndexHit,
|
|
@@ -388,12 +387,6 @@ export class VectorStore {
|
|
|
388
387
|
// Cumulative store-wide dedup accounting (attempt, not collapsed).
|
|
389
388
|
bumpDedupStats(false, this.stateDir);
|
|
390
389
|
onTier?.({ tier: "new", status: "stored" });
|
|
391
|
-
// Slice 2: best-effort, fire-and-forget mirror of this new checkpoint into
|
|
392
|
-
// the async global PGlite/HNSW index. NEVER awaited — must not block or
|
|
393
|
-
// throw into the synchronous add() path. On failure the index degrades to
|
|
394
|
-
// the sync scan (handled inside vectorIndex). The node:sqlite store remains
|
|
395
|
-
// authoritative; the index is rebuildable from it at any time.
|
|
396
|
-
void indexUpsertEmbedding(this.repoId, sessionId, checkpointId, checkpoint.embedding);
|
|
397
390
|
return { checkpoint, deduped: false };
|
|
398
391
|
}
|
|
399
392
|
|