codemem 0.20.10 → 0.21.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/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/index.js +85 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/commands/db.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/commands/db.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4CpC,eAAO,MAAM,SAAS,SAEe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAEN,KAAK,uBAAuB,EAG5B,MAAM,uBAAuB,CAAC;AAQ/B,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKhE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASjD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAED,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,WAAW,EAAE,MAAM,GAAG,IAAI,GACxB,MAAM,GAAG,IAAI,CAGf;AAqLD,wBAAgB,yBAAyB,CACxC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,uBAAuB,EACnC,QAAQ,GAAE,MAAM,EAAqB,GACnC,MAAM,EAAE,CAgBV;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS9D;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUpF;AA6RD,eAAO,MAAM,YAAY,SAuBtB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_COORDINATOR_DB_PATH, MemoryStore, ObserverClient, RawEventSweeper, VERSION, backfillTagsText, backfillVectors, buildRawEventEnvelopeFromHook, connect, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorListJoinRequestsAction, coordinatorReviewJoinRequestAction, createCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, ensureDeviceIdentity, exportMemories, fingerprintPublicKey, getRawEventStatus, importMemories, initDatabase, isEmbeddingDisabled, loadPublicKey, loadSqliteVec, rawEventsGate, readCodememConfigFile, readCoordinatorSyncConfig, readImportPayload, resolveDbPath, resolveProject, retryRawEventFailures, runSyncDaemon, runSyncPass, schema, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
|
|
2
|
+
import { DEFAULT_COORDINATOR_DB_PATH, MemoryStore, ObserverClient, RawEventSweeper, SyncRetentionRunner, VERSION, backfillTagsText, backfillVectors, buildRawEventEnvelopeFromHook, bulkPruneReplicationOpsByAgeCutoff, connect, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorListJoinRequestsAction, coordinatorReviewJoinRequestAction, createCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, ensureDeviceIdentity, exportMemories, fingerprintPublicKey, getRawEventStatus, importMemories, initDatabase, isEmbeddingDisabled, loadPublicKey, loadSqliteVec, planReplicationOpsAgePrune, pruneReplicationOps, rawEventsGate, readCodememConfigFile, readCoordinatorSyncConfig, readImportPayload, resolveDbPath, resolveProject, retryRawEventFailures, runSyncDaemon, runSyncPass, schema, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import omelette from "omelette";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -197,6 +197,18 @@ function parseKindsCsv(value) {
|
|
|
197
197
|
const kinds = value.split(",").map((kind) => kind.trim()).filter((kind) => kind.length > 0);
|
|
198
198
|
return kinds.length > 0 ? kinds : void 0;
|
|
199
199
|
}
|
|
200
|
+
function estimateReplicationOpsBytes(db) {
|
|
201
|
+
try {
|
|
202
|
+
const row = db.prepare(`SELECT COALESCE(SUM(pgsize), 0) AS total_bytes
|
|
203
|
+
FROM dbstat
|
|
204
|
+
WHERE name = 'replication_ops'
|
|
205
|
+
OR name LIKE 'idx_replication_ops_%'
|
|
206
|
+
OR name LIKE 'sqlite_autoindex_replication_ops_%'`).get();
|
|
207
|
+
return Number(row?.total_bytes ?? 0);
|
|
208
|
+
} catch {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
200
212
|
var dbCommand = new Command("db").configureHelp(helpStyle).description("Database maintenance");
|
|
201
213
|
dbCommand.addCommand(new Command("init").configureHelp(helpStyle).description("Verify the SQLite database is present and schema-ready").option("--db <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").option("--db-path <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").action((opts) => {
|
|
202
214
|
const result = initDatabase(opts.db ?? opts.dbPath);
|
|
@@ -208,6 +220,67 @@ dbCommand.addCommand(new Command("init").configureHelp(helpStyle).description("V
|
|
|
208
220
|
p.intro("codemem db vacuum");
|
|
209
221
|
p.log.success(`Vacuumed: ${result.path}`);
|
|
210
222
|
p.outro(`Size: ${result.sizeBytes.toLocaleString()} bytes`);
|
|
223
|
+
})).addCommand(new Command("prune-replication-ops").configureHelp(helpStyle).description("Prune replication op history with approximate oldest-first retention, dry-run, and progress reporting").option("--db <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").option("--db-path <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").option("--dry-run", "show current size/targets without deleting").option("--max-age-days <days>", "retention age threshold in days", "30").option("--max-size-mb <mb>", "target replication log budget in MB", "512").option("--batch-ops <n>", "max ops deleted per batch", "5000").option("--batch-runtime-ms <ms>", "max runtime per batch in ms", "2000").option("--vacuum", "run VACUUM explicitly after prune completes").action((opts) => {
|
|
224
|
+
const dbPath = resolveDbPath(opts.db ?? opts.dbPath);
|
|
225
|
+
const db = connect(dbPath);
|
|
226
|
+
let dbOpen = true;
|
|
227
|
+
try {
|
|
228
|
+
const maxAgeDays = Number.parseInt(opts.maxAgeDays, 10) || 30;
|
|
229
|
+
const maxSizeMb = Number.parseInt(opts.maxSizeMb, 10) || 512;
|
|
230
|
+
const batchOps = Number.parseInt(opts.batchOps, 10) || 5e3;
|
|
231
|
+
const batchRuntimeMs = Number.parseInt(opts.batchRuntimeMs, 10) || 2e3;
|
|
232
|
+
const beforeBytes = estimateReplicationOpsBytes(db);
|
|
233
|
+
p.intro("codemem db prune-replication-ops");
|
|
234
|
+
p.log.info(`Replication ops size: ${formatBytes(beforeBytes)}`);
|
|
235
|
+
p.log.info(`Policy: approximately keep <= ${maxSizeMb} MB and <= ${maxAgeDays} day old history via oldest-first chunk pruning`);
|
|
236
|
+
const agePlan = planReplicationOpsAgePrune(db, maxAgeDays, batchOps);
|
|
237
|
+
if (agePlan.candidate_ops > 0) p.log.info(`Age pass plan: ${agePlan.candidate_ops.toLocaleString()} ops, ~${formatBytes(agePlan.estimated_candidate_bytes)} in ~${agePlan.estimated_batches.toLocaleString()} batch(es), cutoff ${agePlan.cutoff_cursor}`);
|
|
238
|
+
else p.log.info("Age pass plan: no ops older than cutoff");
|
|
239
|
+
if (opts.dryRun) {
|
|
240
|
+
p.outro("Dry run only; no changes made");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
let totalDeleted = 0;
|
|
244
|
+
let batches = 0;
|
|
245
|
+
let lastFloor = null;
|
|
246
|
+
let afterBytes = beforeBytes;
|
|
247
|
+
const ageResult = bulkPruneReplicationOpsByAgeCutoff(db, maxAgeDays, batchOps);
|
|
248
|
+
totalDeleted += ageResult.deleted;
|
|
249
|
+
lastFloor = ageResult.retained_floor_cursor;
|
|
250
|
+
afterBytes = ageResult.estimated_bytes_after ?? beforeBytes;
|
|
251
|
+
if (ageResult.deleted > 0) {
|
|
252
|
+
batches += 1;
|
|
253
|
+
p.log.step(`Age pass batch: deleted ${ageResult.deleted.toLocaleString()} ops, remaining size ~${formatBytes(afterBytes)}`);
|
|
254
|
+
}
|
|
255
|
+
while (true) {
|
|
256
|
+
const result = pruneReplicationOps(db, {
|
|
257
|
+
maxAgeDays: 365e3,
|
|
258
|
+
maxSizeBytes: maxSizeMb * 1024 * 1024,
|
|
259
|
+
maxDeleteOps: batchOps,
|
|
260
|
+
maxRuntimeMs: batchRuntimeMs
|
|
261
|
+
});
|
|
262
|
+
batches += 1;
|
|
263
|
+
totalDeleted += result.deleted;
|
|
264
|
+
lastFloor = result.retained_floor_cursor;
|
|
265
|
+
afterBytes = result.estimated_bytes_after ?? estimateReplicationOpsBytes(db);
|
|
266
|
+
p.log.step(`Batch ${batches}: deleted ${result.deleted.toLocaleString()} ops, remaining size ~${formatBytes(afterBytes)}`);
|
|
267
|
+
if (result.deleted === 0 || !result.stopped_by_budget) break;
|
|
268
|
+
}
|
|
269
|
+
p.log.info(`Deleted ops: ${totalDeleted.toLocaleString()}`);
|
|
270
|
+
p.log.info(`Estimated replication ops size after prune: ${formatBytes(afterBytes)} (approximate)`);
|
|
271
|
+
if (lastFloor) p.log.info(`Retained floor: ${lastFloor}`);
|
|
272
|
+
if (opts.vacuum) {
|
|
273
|
+
p.log.step("Running VACUUM as requested...");
|
|
274
|
+
db.close();
|
|
275
|
+
dbOpen = false;
|
|
276
|
+
const vacuumed = vacuumDatabase(dbPath);
|
|
277
|
+
p.outro(`Done. VACUUM complete. File size is now ${formatBytes(vacuumed.sizeBytes)}.`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
p.outro("Done. Retention is approximate oldest-first pruning. SQLite file size may not shrink until you run `codemem db vacuum` explicitly (or re-run this command with --vacuum).");
|
|
281
|
+
} finally {
|
|
282
|
+
if (dbOpen) db.close();
|
|
283
|
+
}
|
|
211
284
|
})).addCommand(new Command("raw-events-status").configureHelp(helpStyle).description("Show pending raw-event backlog by source stream").option("--db <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").option("--db-path <path>", "database path (default: $CODEMEM_DB or ~/.codemem/mem.sqlite)").option("-n, --limit <n>", "max rows to show", "25").option("--json", "output as JSON").action((opts) => {
|
|
212
285
|
const result = getRawEventStatus(opts.db ?? opts.dbPath, Number.parseInt(opts.limit, 10) || 25);
|
|
213
286
|
if (opts.json) {
|
|
@@ -1155,8 +1228,13 @@ async function startForegroundViewer(invocation) {
|
|
|
1155
1228
|
const sweeper = new RawEventSweeper(store, { observer });
|
|
1156
1229
|
sweeper.start();
|
|
1157
1230
|
const syncAbort = new AbortController();
|
|
1231
|
+
const retentionAbort = new AbortController();
|
|
1158
1232
|
const syncConfig = readCoordinatorSyncConfig(readCodememConfigFile());
|
|
1159
1233
|
const syncEnabled = syncConfig.syncEnabled;
|
|
1234
|
+
const retentionRunner = new SyncRetentionRunner({
|
|
1235
|
+
dbPath: resolveDbPath(invocation.dbPath ?? void 0),
|
|
1236
|
+
signal: retentionAbort.signal
|
|
1237
|
+
});
|
|
1160
1238
|
const syncRuntimeStatus = {
|
|
1161
1239
|
phase: syncEnabled ? "starting" : "disabled",
|
|
1162
1240
|
detail: syncEnabled ? "Waiting for viewer startup to finish" : "Sync is disabled"
|
|
@@ -1200,6 +1278,10 @@ async function startForegroundViewer(invocation) {
|
|
|
1200
1278
|
p.log.success(`Listening on http://${info.address}:${info.port}`);
|
|
1201
1279
|
p.log.info(`Database: ${dbPath}`);
|
|
1202
1280
|
p.log.step("Raw event sweeper started");
|
|
1281
|
+
if (syncConfig.syncRetentionEnabled) {
|
|
1282
|
+
retentionRunner.start();
|
|
1283
|
+
p.log.step("Retention maintenance runner started");
|
|
1284
|
+
}
|
|
1203
1285
|
if (syncEnabled) {
|
|
1204
1286
|
const syncStartDelayMs = 3e3;
|
|
1205
1287
|
p.log.step(`Sync daemon will start in background (${syncStartDelayMs / 1e3}s delay)`);
|
|
@@ -1243,7 +1325,9 @@ async function startForegroundViewer(invocation) {
|
|
|
1243
1325
|
const shutdown = async () => {
|
|
1244
1326
|
p.outro("shutting down");
|
|
1245
1327
|
syncAbort.abort();
|
|
1328
|
+
retentionAbort.abort();
|
|
1246
1329
|
await sweeper.stop();
|
|
1330
|
+
await retentionRunner.stop();
|
|
1247
1331
|
await new Promise((resolve) => {
|
|
1248
1332
|
let remaining = syncServer ? 2 : 1;
|
|
1249
1333
|
const done = () => {
|