@codemem/server 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,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, MemoryStore, VERSION, VERSION as VERSION$1, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, cleanupNonces, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorStatusSnapshot, createCoordinatorReciprocalApproval, ensureDeviceIdentity, extractReplicationOps, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listMaintenanceJobs, listObserverProviderOptions, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, parseStrictInteger, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, recordNonce, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, MemoryStore, VERSION, VERSION as VERSION$1, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, cleanupNonces, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorStatusSnapshot, createCoordinatorReciprocalApproval, ensureDeviceIdentity, extractReplicationOps, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listMaintenanceJobs, listObserverProviderOptions, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, parseStrictInteger, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, recordNonce, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, verifySignature, writeCodememConfigFile } from "@codemem/core";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import { Hono } from "hono";
6
6
  import { createMiddleware } from "hono/factory";
@@ -553,6 +553,39 @@ function queryBool(value) {
553
553
  }
554
554
  //#endregion
555
555
  //#region src/routes/memory.ts
556
+ function cleanOwnershipValue(value) {
557
+ if (typeof value !== "string") return null;
558
+ const trimmed = value.trim();
559
+ return trimmed ? trimmed : null;
560
+ }
561
+ function buildOwnershipContext(store) {
562
+ const claimedPeerIds = new Set(store.sameActorPeerIds());
563
+ return {
564
+ actorId: store.actorId,
565
+ claimedPeerIds,
566
+ deviceId: store.deviceId,
567
+ legacyActorIds: new Set(Array.from(claimedPeerIds, (peerId) => `legacy-sync:${peerId}`))
568
+ };
569
+ }
570
+ function memoryOwnedBySelf(row, ownership, metadata = fromJson(row.metadata_json ?? null)) {
571
+ const meta = metadata;
572
+ const actorId = cleanOwnershipValue(row.actor_id) ?? cleanOwnershipValue(meta.actor_id);
573
+ if (actorId === ownership.actorId) return true;
574
+ const deviceId = cleanOwnershipValue(row.origin_device_id) ?? cleanOwnershipValue(meta.origin_device_id);
575
+ if (deviceId === ownership.deviceId) return true;
576
+ if (deviceId && ownership.claimedPeerIds.has(deviceId)) return true;
577
+ if (actorId && ownership.legacyActorIds.has(actorId)) return true;
578
+ return false;
579
+ }
580
+ function serializeMemoryRow(ownership, row) {
581
+ const metadata = fromJson(row.metadata_json ?? null);
582
+ return {
583
+ ...row,
584
+ kind: canonicalMemoryKind(row.kind ?? null, row.metadata_json),
585
+ metadata_json: metadata,
586
+ owned_by_self: memoryOwnedBySelf(row, ownership, metadata)
587
+ };
588
+ }
556
589
  /**
557
590
  * Attach session project/cwd fields to memory items.
558
591
  */
@@ -615,14 +648,12 @@ function queryMemoryPage(store, options) {
615
648
  });
616
649
  const where = ["memory_items.active = 1", ...filterResult.clauses].join(" AND ");
617
650
  const from = filterResult.joinSessions ? "memory_items JOIN sessions ON sessions.id = memory_items.session_id" : "memory_items";
618
- return store.db.prepare(`SELECT memory_items.* FROM ${from}
651
+ const rows = store.db.prepare(`SELECT memory_items.* FROM ${from}
619
652
  WHERE ${where}
620
653
  ORDER BY memory_items.created_at DESC
621
- LIMIT ? OFFSET ?`).all(...filterResult.params, options.limit + 1, options.offset).map((row) => ({
622
- ...row,
623
- kind: canonicalMemoryKind(row.kind ?? null, row.metadata_json),
624
- metadata_json: fromJson(row.metadata_json ?? null)
625
- }));
654
+ LIMIT ? OFFSET ?`).all(...filterResult.params, options.limit + 1, options.offset);
655
+ const ownership = buildOwnershipContext(store);
656
+ return rows.map((row) => serializeMemoryRow(ownership, row));
626
657
  }
627
658
  function isSummaryLikeMemory$1(item) {
628
659
  return isSummaryLikeMemory({
@@ -870,6 +901,31 @@ function memoryRoutes(getStore) {
870
901
  return c.json({ error: msg }, 400);
871
902
  }
872
903
  });
904
+ app.post("/api/memories/forget", async (c) => {
905
+ const store = getStore();
906
+ let body;
907
+ try {
908
+ body = await c.req.json();
909
+ } catch {
910
+ return c.json({ error: "invalid JSON" }, 400);
911
+ }
912
+ const memoryId = parseStrictInteger(typeof body.memory_id === "string" ? body.memory_id : String(body.memory_id ?? ""));
913
+ if (memoryId == null || memoryId <= 0) return c.json({ error: "memory_id must be int" }, 400);
914
+ const row = drizzle(store.db, { schema }).select().from(schema.memoryItems).where(eq(schema.memoryItems.id, memoryId)).get();
915
+ if (!row) return c.json({ error: "memory not found" }, 404);
916
+ if (!memoryOwnedBySelf(row, buildOwnershipContext(store))) return c.json({ error: "memory not owned by this device" }, 403);
917
+ if (Number(row.active ?? 1) === 0 || row.deleted_at != null) return c.json({ status: "ok" });
918
+ try {
919
+ store.forget(memoryId);
920
+ return c.json({ status: "ok" });
921
+ } catch (err) {
922
+ const msg = err instanceof Error ? err.message : String(err);
923
+ if (msg.includes("not found")) return c.json({ error: msg }, 404);
924
+ if (msg.includes("not owned")) return c.json({ error: msg }, 403);
925
+ if (msg.includes("sync_rebootstrap_in_progress")) return c.json({ error: msg }, 409);
926
+ return c.json({ error: msg }, 400);
927
+ }
928
+ });
873
929
  return app;
874
930
  }
875
931
  //#endregion
@@ -1387,6 +1443,19 @@ function summarizeMaintenanceJobs(jobs, showDiagnostics) {
1387
1443
  } : {}
1388
1444
  }));
1389
1445
  }
1446
+ function redactSemanticIndexDiagnostics(diagnostics, showDiagnostics) {
1447
+ if (showDiagnostics || !diagnostics.maintenance_job) return diagnostics;
1448
+ const summary = diagnostics.state === "pending" ? "Semantic-index catch-up is pending" : diagnostics.state === "failed" ? "Semantic-index catch-up failed" : diagnostics.state === "degraded" ? "Semantic index is running in degraded mode" : diagnostics.summary;
1449
+ return {
1450
+ ...diagnostics,
1451
+ summary,
1452
+ maintenance_job: {
1453
+ ...diagnostics.maintenance_job,
1454
+ message: null,
1455
+ error: null
1456
+ }
1457
+ };
1458
+ }
1390
1459
  function syncKeysDir() {
1391
1460
  return process.env.CODEMEM_KEYS_DIR?.trim() || void 0;
1392
1461
  }
@@ -2222,6 +2291,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2222
2291
  retentionState = void 0;
2223
2292
  }
2224
2293
  const lastSyncRow = d.select({ last_sync_at: max(schema.syncPeers.last_sync_at) }).from(schema.syncPeers).get();
2294
+ const semanticIndex = redactSemanticIndexDiagnostics(getSemanticIndexDiagnostics(store.db), showDiag);
2225
2295
  const lastError = daemonState?.last_error;
2226
2296
  const lastErrorAt = daemonState?.last_error_at;
2227
2297
  const lastOkAt = daemonState?.last_ok_at;
@@ -2248,6 +2318,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2248
2318
  last_error: retentionState?.last_error ?? null,
2249
2319
  last_error_at: retentionState?.last_error_at ?? null
2250
2320
  },
2321
+ semantic_index: semanticIndex,
2251
2322
  peer_count: Number(peerCountRow?.total ?? 0),
2252
2323
  last_sync_at: lastSyncRow?.last_sync_at ?? null,
2253
2324
  daemon_state: daemonStateValue,