codemem 0.25.2 → 0.26.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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, MemoryStore, ObserverClient, RawEventSweeper, 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, exportMemories, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, getExtractionBenchmarkProfile, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSessionExtractionEval, getSessionExtractionEvalScenario, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, 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 { 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";
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";
@@ -536,18 +536,6 @@ var MAX_FILES_MODIFIED = 64;
536
536
  var MAX_WORKING_SET_PATHS = 8;
537
537
  var MAX_QUERY_CHARS = 500;
538
538
  var MAX_QUERY_FILE_BASENAMES = 5;
539
- var MUTATING_TOOL_NAMES = new Set([
540
- "edit",
541
- "write",
542
- "multiedit",
543
- "notebookedit",
544
- "apply_patch"
545
- ]);
546
- var APPLY_PATCH_PATH_PREFIXES = [
547
- "*** Add File: ",
548
- "*** Update File: ",
549
- "*** Delete File: "
550
- ];
551
539
  function defaultSessionState() {
552
540
  return {
553
541
  first_prompt: "",
@@ -629,14 +617,6 @@ function clearSessionState(sessionId) {
629
617
  rmSync(path, { force: true });
630
618
  } catch {}
631
619
  }
632
- function extractApplyPatchPaths(patchText) {
633
- const out = [];
634
- for (const line of patchText.split("\n")) for (const prefix of APPLY_PATCH_PATH_PREFIXES) if (line.startsWith(prefix)) {
635
- const path = line.slice(prefix.length).trim();
636
- if (path) out.push(path);
637
- }
638
- return out;
639
- }
640
620
  function extractModifiedPathsFromHook(payload) {
641
621
  const toolName = String(payload.tool_name ?? "").trim().toLowerCase();
642
622
  if (!MUTATING_TOOL_NAMES.has(toolName)) return [];
@@ -771,11 +751,19 @@ function emitStructuredError$1(errorCode, message) {
771
751
  }
772
752
  /** Try to POST the hook payload to the running viewer server.
773
753
  *
774
- * Returns `ok: true` only when the viewer accepted the payload AND
775
- * actually inserted it. The viewer may legitimately accept a request
776
- * but report `skipped > 0` when the payload was deduped or otherwise
777
- * rejected after parse those cases must trigger the locked
778
- * fallback so the durability layer can decide whether to spool.
754
+ * Returns `ok: true` whenever the viewer accepts the request and
755
+ * returns a well-shaped JSON body with numeric `inserted` / `skipped`
756
+ * fields. That includes the `{inserted: 0, skipped: 1}` response the
757
+ * viewer emits when the payload maps to a null envelope (Stop with no
758
+ * assistant text, UserPromptSubmit with empty prompt, etc.) that
759
+ * determination is deterministic, so retrying via the direct fallback
760
+ * would produce the exact same null envelope and the same skip. We
761
+ * accept those as benign no-ops instead of triggering the durability
762
+ * dance pointlessly.
763
+ *
764
+ * If a future server change adds a new `skipped` reason that IS
765
+ * transient, we'll need a reason field in the response and updated
766
+ * client handling — not an unconditional fail-over.
779
767
  */
780
768
  async function tryHttpIngest(payload, host, port) {
781
769
  const url = `http://${host}:${port}/api/claude-hooks`;
@@ -821,14 +809,6 @@ async function tryHttpIngest(payload, host, port) {
821
809
  skipped: 0
822
810
  };
823
811
  }
824
- if (obj.skipped > 0) {
825
- logHookFailure("codemem claude-hook-ingest HTTP accepted but skipped payload");
826
- return {
827
- ok: false,
828
- inserted: obj.inserted,
829
- skipped: obj.skipped
830
- };
831
- }
832
812
  return {
833
813
  ok: true,
834
814
  inserted: obj.inserted,
@@ -856,6 +836,7 @@ function directEnqueue(payload, dbPath) {
856
836
  try {
857
837
  loadSqliteVec(db);
858
838
  } catch {}
839
+ ensureSchemaBootstrapped(db);
859
840
  const strippedPayload = stripPrivateObj(envelope.payload);
860
841
  if (db.prepare("SELECT 1 FROM raw_events WHERE source = ? AND stream_id = ? AND event_id = ? LIMIT 1").get(envelope.source, envelope.session_stream_id, envelope.event_id)) return {
861
842
  inserted: 0,
@@ -3460,7 +3441,7 @@ function resolveLegacyServeInvocation(opts) {
3460
3441
  configPath: opts.config ?? null,
3461
3442
  host: opts.host,
3462
3443
  port: parsePort(opts.port),
3463
- background: opts.restart ? true : opts.background ? true : false
3444
+ background: Boolean(opts.restart || opts.background)
3464
3445
  };
3465
3446
  }
3466
3447
  function resolveServeInvocation(action, opts) {
@@ -3791,6 +3772,8 @@ async function startForegroundViewer(invocation) {
3791
3772
  });
3792
3773
  const vectorMigrationRunner = new VectorModelMigrationRunner({ dbPath: resolveDbPath(invocation.dbPath ?? void 0) });
3793
3774
  const dedupKeyBackfillRunner = new DedupKeyBackfillRunner({ dbPath: resolveDbPath(invocation.dbPath ?? void 0) });
3775
+ const sessionContextBackfillRunner = new SessionContextBackfillRunner({ dbPath: resolveDbPath(invocation.dbPath ?? void 0) });
3776
+ const refBackfillRunner = new RefBackfillRunner({ dbPath: resolveDbPath(invocation.dbPath ?? void 0) });
3794
3777
  const syncRuntimeStatus = {
3795
3778
  phase: syncEnabled ? "starting" : "disabled",
3796
3779
  detail: syncEnabled ? "Waiting for viewer startup to finish" : "Sync is disabled"
@@ -3841,6 +3824,14 @@ async function startForegroundViewer(invocation) {
3841
3824
  dedupKeyBackfillRunner.start();
3842
3825
  p.log.step("Dedup-key maintenance runner started");
3843
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
+ }
3844
3835
  if (syncConfig.syncRetentionEnabled) {
3845
3836
  retentionRunner.start();
3846
3837
  p.log.step("Retention maintenance runner started");
@@ -3899,6 +3890,8 @@ async function startForegroundViewer(invocation) {
3899
3890
  syncAbort.abort();
3900
3891
  retentionAbort.abort();
3901
3892
  await sweeper.stop();
3893
+ await sessionContextBackfillRunner.stop();
3894
+ await refBackfillRunner.stop();
3902
3895
  await dedupKeyBackfillRunner.stop();
3903
3896
  await vectorMigrationRunner.stop();
3904
3897
  await retentionRunner.stop();
@@ -4821,12 +4814,14 @@ statusCmd.action((opts) => {
4821
4814
  last_sync_at: schema.syncPeers.last_sync_at,
4822
4815
  last_error: schema.syncPeers.last_error
4823
4816
  }).from(schema.syncPeers).all();
4817
+ const semanticIndex = getSemanticIndexDiagnostics(store.db);
4824
4818
  if (opts.json) {
4825
4819
  console.log(JSON.stringify({
4826
4820
  enabled: config.sync_enabled === true,
4827
4821
  host: config.sync_host ?? "0.0.0.0",
4828
4822
  port: config.sync_port ?? 7337,
4829
4823
  interval_s: config.sync_interval_s ?? 120,
4824
+ semantic_index: semanticIndex,
4830
4825
  device_id: deviceRow?.device_id ?? null,
4831
4826
  fingerprint: deviceRow?.fingerprint ?? null,
4832
4827
  coordinator_url: config.sync_coordinator_url ?? null,
@@ -4854,6 +4849,13 @@ statusCmd.action((opts) => {
4854
4849
  const label = peer.name || peer.peer_device_id;
4855
4850
  p.log.message(` ${label}: last_sync=${peer.last_sync_at ?? "never"}, status=${peer.last_error ?? "ok"}`);
4856
4851
  }
4852
+ p.log.info([
4853
+ "Semantic index:",
4854
+ ` State: ${semanticIndex.state}`,
4855
+ ` Summary: ${semanticIndex.summary}`,
4856
+ ` Coverage: ${semanticIndex.indexed_memory_count}/${semanticIndex.embeddable_memory_count}`,
4857
+ ` Mode: ${semanticIndex.mode === "semantic" ? `semantic (${semanticIndex.semantic_search_model ?? semanticIndex.current_model})` : "keyword-only"}`
4858
+ ].join("\n"));
4857
4859
  p.outro(`${peers.length} peer(s)`);
4858
4860
  } finally {
4859
4861
  store.close();