codemem 0.26.0 → 0.26.2
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/index.js +144 -22
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, RawEventSweeper, RefBackfillRunner, SessionContextBackfillRunner, SyncRetentionRunner, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, buildRawEventEnvelopeFromHook, compareMemoryRoleReports, connect, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, ensureDeviceIdentity, ensureSchemaBootstrapped, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, getExtractionBenchmarkProfile, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingSessionContextBackfill, hasUnsyncedSharedMemoryChanges, importMemories, initDatabase, isEmbeddingDisabled, loadObserverConfig, loadPublicKey, loadSqliteVec, planReplicationOpsAgePrune, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, retryRawEventFailures, runSyncDaemon, runSyncPass, schema, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
|
|
2
|
+
import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, REF_BACKFILL_JOB, RawEventSweeper, RefBackfillRunner, SESSION_CONTEXT_BACKFILL_JOB, SessionContextBackfillRunner, SyncRetentionRunner, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, buildRawEventEnvelopeFromHook, compareMemoryRoleReports, connect, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, ensureDeviceIdentity, ensureSchemaBootstrapped, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, getExtractionBenchmarkProfile, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingSessionContextBackfill, hasUnsyncedSharedMemoryChanges, importMemories, initDatabase, isEmbeddingDisabled, loadObserverConfig, loadPublicKey, loadSqliteVec, planReplicationOpsAgePrune, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, retryRawEventFailures, runSyncDaemon, runSyncPass, schema, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import omelette from "omelette";
|
|
5
5
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -3564,6 +3564,19 @@ function readViewerPidRecord(dbPath) {
|
|
|
3564
3564
|
}
|
|
3565
3565
|
return null;
|
|
3566
3566
|
}
|
|
3567
|
+
function normalizeViewerHost(host) {
|
|
3568
|
+
const normalized = host.trim().toLowerCase();
|
|
3569
|
+
if (normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]") return "loopback";
|
|
3570
|
+
return normalized;
|
|
3571
|
+
}
|
|
3572
|
+
async function findRuntimeViewerConflict(dbPath, target) {
|
|
3573
|
+
const record = readViewerPidRecord(dbPath);
|
|
3574
|
+
if (!record) return null;
|
|
3575
|
+
if (normalizeViewerHost(record.host) === normalizeViewerHost(target.host) && record.port === target.port) return null;
|
|
3576
|
+
if (!isProcessRunning(record.pid)) return null;
|
|
3577
|
+
if (!await respondsLikeCodememViewer(record)) return null;
|
|
3578
|
+
return record;
|
|
3579
|
+
}
|
|
3567
3580
|
function isProcessRunning(pid) {
|
|
3568
3581
|
try {
|
|
3569
3582
|
process.kill(pid, 0);
|
|
@@ -3735,6 +3748,88 @@ async function startBackgroundViewer(invocation) {
|
|
|
3735
3748
|
p.intro("codemem viewer");
|
|
3736
3749
|
p.outro(`Viewer started in background (pid ${child.pid}) at http://${invocation.host}:${invocation.port}`);
|
|
3737
3750
|
}
|
|
3751
|
+
function createSequentialBackfillCoordinator(store, jobPlans, signal) {
|
|
3752
|
+
const pollIntervalMs = 1e3;
|
|
3753
|
+
let activeRunner = null;
|
|
3754
|
+
let activePlan = null;
|
|
3755
|
+
let activePollTimer = null;
|
|
3756
|
+
let nextJobIndex = 0;
|
|
3757
|
+
let stopped = false;
|
|
3758
|
+
const clearPollTimer = () => {
|
|
3759
|
+
if (!activePollTimer) return;
|
|
3760
|
+
clearTimeout(activePollTimer);
|
|
3761
|
+
activePollTimer = null;
|
|
3762
|
+
};
|
|
3763
|
+
const schedulePoll = (fn) => {
|
|
3764
|
+
clearPollTimer();
|
|
3765
|
+
activePollTimer = setTimeout(fn, pollIntervalMs);
|
|
3766
|
+
if (typeof activePollTimer === "object" && "unref" in activePollTimer) activePollTimer.unref();
|
|
3767
|
+
};
|
|
3768
|
+
const waitForCurrentJob = () => {
|
|
3769
|
+
if (stopped || signal?.aborted || !activePlan || !activeRunner) return;
|
|
3770
|
+
const job = getMaintenanceJob(store.db, activePlan.kind);
|
|
3771
|
+
if (!activePlan.isPending(store.db)) {
|
|
3772
|
+
const finishedPlan = activePlan;
|
|
3773
|
+
const finishedRunner = activeRunner;
|
|
3774
|
+
activePlan = null;
|
|
3775
|
+
activeRunner = null;
|
|
3776
|
+
finishedRunner.stop().finally(() => {
|
|
3777
|
+
if (!stopped && !signal?.aborted) {
|
|
3778
|
+
p.log.step(`${finishedPlan.name} backfill complete`);
|
|
3779
|
+
startNextJob();
|
|
3780
|
+
}
|
|
3781
|
+
});
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
if (job?.status === "failed") {
|
|
3785
|
+
const failedPlan = activePlan;
|
|
3786
|
+
const failedRunner = activeRunner;
|
|
3787
|
+
activePlan = null;
|
|
3788
|
+
activeRunner = null;
|
|
3789
|
+
failedRunner.stop().finally(() => {
|
|
3790
|
+
if (!stopped && !signal?.aborted) {
|
|
3791
|
+
p.log.warn(`${failedPlan.name} backfill failed and will be retried on a later startup`);
|
|
3792
|
+
startNextJob();
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
schedulePoll(waitForCurrentJob);
|
|
3798
|
+
};
|
|
3799
|
+
const startNextJob = () => {
|
|
3800
|
+
clearPollTimer();
|
|
3801
|
+
if (stopped || signal?.aborted) return;
|
|
3802
|
+
while (nextJobIndex < jobPlans.length) {
|
|
3803
|
+
const plan = jobPlans[nextJobIndex++];
|
|
3804
|
+
if (!plan) continue;
|
|
3805
|
+
if (!plan.isPending(store.db)) continue;
|
|
3806
|
+
activePlan = plan;
|
|
3807
|
+
activeRunner = plan.createRunner();
|
|
3808
|
+
p.log.step(`${plan.name} backfill started`);
|
|
3809
|
+
activeRunner.start();
|
|
3810
|
+
schedulePoll(waitForCurrentJob);
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3813
|
+
p.log.step("All backfill jobs complete");
|
|
3814
|
+
};
|
|
3815
|
+
return {
|
|
3816
|
+
start: () => {
|
|
3817
|
+
if (stopped || signal?.aborted) return;
|
|
3818
|
+
const pendingCount = jobPlans.filter((plan) => plan.isPending(store.db)).length;
|
|
3819
|
+
if (pendingCount === 0) return;
|
|
3820
|
+
p.log.step(`${pendingCount} backfill job(s) pending — starting sequential runners`);
|
|
3821
|
+
startNextJob();
|
|
3822
|
+
},
|
|
3823
|
+
stop: async () => {
|
|
3824
|
+
stopped = true;
|
|
3825
|
+
clearPollTimer();
|
|
3826
|
+
const runner = activeRunner;
|
|
3827
|
+
activeRunner = null;
|
|
3828
|
+
activePlan = null;
|
|
3829
|
+
if (runner) await runner.stop();
|
|
3830
|
+
}
|
|
3831
|
+
};
|
|
3832
|
+
}
|
|
3738
3833
|
async function startForegroundViewer(invocation) {
|
|
3739
3834
|
const { createApp, createSyncApp, closeStore, getStore } = await import("@codemem/server");
|
|
3740
3835
|
const { serve } = await import("@hono/node-server");
|
|
@@ -3764,16 +3859,44 @@ async function startForegroundViewer(invocation) {
|
|
|
3764
3859
|
sweeper.start();
|
|
3765
3860
|
const syncAbort = new AbortController();
|
|
3766
3861
|
const retentionAbort = new AbortController();
|
|
3862
|
+
const backfillAbort = new AbortController();
|
|
3767
3863
|
const syncConfig = readCoordinatorSyncConfig(invocation.configPath ? readCodememConfigFileAtPath(invocation.configPath) : readCodememConfigFile());
|
|
3768
3864
|
const syncEnabled = syncConfig.syncEnabled;
|
|
3865
|
+
const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
|
|
3769
3866
|
const retentionRunner = new SyncRetentionRunner({
|
|
3770
|
-
dbPath
|
|
3867
|
+
dbPath,
|
|
3771
3868
|
signal: retentionAbort.signal
|
|
3772
3869
|
});
|
|
3773
|
-
const vectorMigrationRunner = new VectorModelMigrationRunner({ dbPath
|
|
3774
|
-
const
|
|
3775
|
-
|
|
3776
|
-
|
|
3870
|
+
const vectorMigrationRunner = new VectorModelMigrationRunner({ dbPath });
|
|
3871
|
+
const backfillCoordinator = createSequentialBackfillCoordinator(store, [
|
|
3872
|
+
{
|
|
3873
|
+
name: "Dedup-key",
|
|
3874
|
+
kind: DEDUP_KEY_BACKFILL_JOB,
|
|
3875
|
+
isPending: hasPendingDedupKeyBackfill,
|
|
3876
|
+
createRunner: () => new DedupKeyBackfillRunner({
|
|
3877
|
+
dbPath,
|
|
3878
|
+
signal: backfillAbort.signal
|
|
3879
|
+
})
|
|
3880
|
+
},
|
|
3881
|
+
{
|
|
3882
|
+
name: "Session-context",
|
|
3883
|
+
kind: SESSION_CONTEXT_BACKFILL_JOB,
|
|
3884
|
+
isPending: hasPendingSessionContextBackfill,
|
|
3885
|
+
createRunner: () => new SessionContextBackfillRunner({
|
|
3886
|
+
dbPath,
|
|
3887
|
+
signal: backfillAbort.signal
|
|
3888
|
+
})
|
|
3889
|
+
},
|
|
3890
|
+
{
|
|
3891
|
+
name: "Ref",
|
|
3892
|
+
kind: REF_BACKFILL_JOB,
|
|
3893
|
+
isPending: hasPendingRefBackfill,
|
|
3894
|
+
createRunner: () => new RefBackfillRunner({
|
|
3895
|
+
dbPath,
|
|
3896
|
+
signal: backfillAbort.signal
|
|
3897
|
+
})
|
|
3898
|
+
}
|
|
3899
|
+
], backfillAbort.signal);
|
|
3777
3900
|
const syncRuntimeStatus = {
|
|
3778
3901
|
phase: syncEnabled ? "starting" : "disabled",
|
|
3779
3902
|
detail: syncEnabled ? "Waiting for viewer startup to finish" : "Sync is disabled"
|
|
@@ -3785,7 +3908,7 @@ async function startForegroundViewer(invocation) {
|
|
|
3785
3908
|
getSyncRuntimeStatus: () => syncRuntimeStatus
|
|
3786
3909
|
};
|
|
3787
3910
|
const app = createApp(appOpts);
|
|
3788
|
-
const pidPath = pidFilePath(
|
|
3911
|
+
const pidPath = pidFilePath(dbPath);
|
|
3789
3912
|
let syncServer = null;
|
|
3790
3913
|
let syncListenerReady = false;
|
|
3791
3914
|
if (syncEnabled) {
|
|
@@ -3820,18 +3943,7 @@ async function startForegroundViewer(invocation) {
|
|
|
3820
3943
|
vectorMigrationRunner.start();
|
|
3821
3944
|
p.log.step("Vector maintenance runner started");
|
|
3822
3945
|
}
|
|
3823
|
-
|
|
3824
|
-
dedupKeyBackfillRunner.start();
|
|
3825
|
-
p.log.step("Dedup-key maintenance runner started");
|
|
3826
|
-
}
|
|
3827
|
-
if (hasPendingSessionContextBackfill(store.db)) {
|
|
3828
|
-
sessionContextBackfillRunner.start();
|
|
3829
|
-
p.log.step("Session-context backfill runner started");
|
|
3830
|
-
}
|
|
3831
|
-
if (hasPendingRefBackfill(store.db)) {
|
|
3832
|
-
refBackfillRunner.start();
|
|
3833
|
-
p.log.step("Ref backfill runner started");
|
|
3834
|
-
}
|
|
3946
|
+
backfillCoordinator.start();
|
|
3835
3947
|
if (syncConfig.syncRetentionEnabled) {
|
|
3836
3948
|
retentionRunner.start();
|
|
3837
3949
|
p.log.step("Retention maintenance runner started");
|
|
@@ -3889,12 +4001,11 @@ async function startForegroundViewer(invocation) {
|
|
|
3889
4001
|
clearInterval(walCheckpointTimer);
|
|
3890
4002
|
syncAbort.abort();
|
|
3891
4003
|
retentionAbort.abort();
|
|
4004
|
+
backfillAbort.abort();
|
|
3892
4005
|
await sweeper.stop();
|
|
3893
|
-
await sessionContextBackfillRunner.stop();
|
|
3894
|
-
await refBackfillRunner.stop();
|
|
3895
|
-
await dedupKeyBackfillRunner.stop();
|
|
3896
4006
|
await vectorMigrationRunner.stop();
|
|
3897
4007
|
await retentionRunner.stop();
|
|
4008
|
+
await backfillCoordinator.stop();
|
|
3898
4009
|
await new Promise((resolve) => {
|
|
3899
4010
|
let remaining = syncServer ? 2 : 1;
|
|
3900
4011
|
const done = () => {
|
|
@@ -3929,6 +4040,17 @@ async function startForegroundViewer(invocation) {
|
|
|
3929
4040
|
}
|
|
3930
4041
|
async function runServeInvocation(invocation) {
|
|
3931
4042
|
const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
|
|
4043
|
+
const runtimeConflict = await findRuntimeViewerConflict(dbPath, {
|
|
4044
|
+
host: invocation.host,
|
|
4045
|
+
port: invocation.port
|
|
4046
|
+
});
|
|
4047
|
+
if (runtimeConflict) {
|
|
4048
|
+
p.intro("codemem viewer");
|
|
4049
|
+
p.log.error(`Database runtime at ${dirname(dbPath)} is already managed by viewer ${runtimeConflict.host}:${runtimeConflict.port} (pid ${runtimeConflict.pid})`);
|
|
4050
|
+
p.log.info("Use the matching --host/--port, stop the existing viewer first, or use a separate db/runtime folder for another viewer.");
|
|
4051
|
+
process.exitCode = 1;
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
3932
4054
|
if (invocation.mode === "stop" || invocation.mode === "restart") {
|
|
3933
4055
|
const result = await stopExistingViewer(dbPath, {
|
|
3934
4056
|
host: invocation.host,
|