@codemem/server 0.27.0 → 0.28.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, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorDisableDeviceAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, 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, syncProjectAllowedByFilters, syncVisibilityAllowed, 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, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, 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, syncProjectAllowedByFilters, syncVisibilityAllowed, 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";
@@ -1975,7 +1975,12 @@ function filterOpsForPeer(store, peerDeviceId, ops) {
1975
1975
  * When showDiag is false, sensitive fields (fingerprint, last_error, addresses)
1976
1976
  * are redacted.
1977
1977
  */
1978
- function mapPeerRow(store, row, showDiag) {
1978
+ function mapPeerRow(store, row, showDiag, recentOpsByPeer) {
1979
+ const peerId = String(row.peer_device_id ?? "");
1980
+ const recentOps = recentOpsByPeer?.get(peerId) ?? {
1981
+ in: 0,
1982
+ out: 0
1983
+ };
1979
1984
  return {
1980
1985
  peer_device_id: row.peer_device_id,
1981
1986
  name: row.name,
@@ -1989,9 +1994,33 @@ function mapPeerRow(store, row, showDiag) {
1989
1994
  claimed_local_actor: Boolean(row.claimed_local_actor),
1990
1995
  actor_id: row.actor_id ?? null,
1991
1996
  actor_display_name: row.actor_display_name ?? null,
1992
- project_scope: { ...currentProjectScope(row, readPeerProjectFilters(store, String(row.peer_device_id ?? ""))) }
1997
+ project_scope: { ...currentProjectScope(row, readPeerProjectFilters(store, String(row.peer_device_id ?? ""))) },
1998
+ recent_ops: {
1999
+ in: recentOps.in,
2000
+ out: recentOps.out
2001
+ }
1993
2002
  };
1994
2003
  }
2004
+ var SYNC_DIRECTION_WINDOW_SECONDS = 1440 * 60;
2005
+ function recentPeerOps(store) {
2006
+ const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_DIRECTION_WINDOW_SECONDS * 1e3)).toISOString();
2007
+ const rows = store.db.prepare(`SELECT peer_device_id,
2008
+ COALESCE(SUM(ops_in), 0) AS ops_in,
2009
+ COALESCE(SUM(ops_out), 0) AS ops_out
2010
+ FROM sync_attempts
2011
+ WHERE ok = 1 AND finished_at IS NOT NULL AND finished_at >= ?
2012
+ GROUP BY peer_device_id`).all(cutoff);
2013
+ const map = /* @__PURE__ */ new Map();
2014
+ for (const row of rows) {
2015
+ const id = String(row.peer_device_id ?? "").trim();
2016
+ if (!id) continue;
2017
+ map.set(id, {
2018
+ in: Number(row.ops_in ?? 0),
2019
+ out: Number(row.ops_out ?? 0)
2020
+ });
2021
+ }
2022
+ return map;
2023
+ }
1995
2024
  function isRecentIso(value, windowS = SYNC_STALE_AFTER_SECONDS) {
1996
2025
  const raw = String(value ?? "").trim();
1997
2026
  if (!raw) return false;
@@ -2361,8 +2390,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2361
2390
  statusPayload.daemon_running = runtimeStatus.phase === "starting" || daemonRunning;
2362
2391
  statusPayload.daemon_detail = runtimeStatus.detail ?? daemonDetail;
2363
2392
  }
2364
- const peersItems = traceSync("peerRows", () => store.db.prepare(PEERS_QUERY).all()).map((row) => {
2365
- const peer = mapPeerRow(store, row, showDiag);
2393
+ const peerRows = traceSync("peerRows", () => store.db.prepare(PEERS_QUERY).all());
2394
+ const recentOpsByPeer = traceSync("recentPeerOps", () => recentPeerOps(store));
2395
+ const peersItems = peerRows.map((row) => {
2396
+ const peer = mapPeerRow(store, row, showDiag, recentOpsByPeer);
2366
2397
  peer.status = peerStatus(peer);
2367
2398
  return peer;
2368
2399
  });
@@ -2447,7 +2478,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2447
2478
  const store = getStore();
2448
2479
  {
2449
2480
  const showDiag = queryBool(c.req.query("includeDiagnostics"));
2450
- const peers = store.db.prepare(PEERS_QUERY).all().map((row) => mapPeerRow(store, row, showDiag));
2481
+ const rows = store.db.prepare(PEERS_QUERY).all();
2482
+ const recentOpsByPeer = recentPeerOps(store);
2483
+ const peers = rows.map((row) => mapPeerRow(store, row, showDiag, recentOpsByPeer));
2451
2484
  return c.json({
2452
2485
  items: peers,
2453
2486
  redacted: !showDiag
@@ -3381,6 +3414,49 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
3381
3414
  }, 400);
3382
3415
  }
3383
3416
  });
3417
+ app.post("/api/coordinator/admin/devices/:device_id/enable", async (c) => {
3418
+ const config = readCoordinatorSyncConfig();
3419
+ const status = coordinatorAdminStatusPayload(config);
3420
+ if (status.readiness === "not_configured") return c.json({
3421
+ error: "coordinator_not_configured",
3422
+ status
3423
+ }, 400);
3424
+ if (!status.has_admin_secret) return c.json({
3425
+ error: "coordinator_admin_secret_missing",
3426
+ status
3427
+ }, 400);
3428
+ const deviceId = String(c.req.param("device_id") ?? "").trim();
3429
+ if (!deviceId) return c.json({
3430
+ error: "device_id required",
3431
+ status
3432
+ }, 400);
3433
+ const groupId = resolveCoordinatorAdminGroup(c.req.query("group_id"), status);
3434
+ if (!groupId) return c.json({
3435
+ error: "group_id required",
3436
+ status
3437
+ }, 400);
3438
+ try {
3439
+ if (!await coordinatorEnableDeviceAction({
3440
+ groupId,
3441
+ deviceId,
3442
+ remoteUrl: config.syncCoordinatorUrl || null,
3443
+ adminSecret: config.syncCoordinatorAdminSecret || null
3444
+ })) return c.json({
3445
+ error: "device_not_found",
3446
+ status
3447
+ }, 404);
3448
+ return c.json({
3449
+ ok: true,
3450
+ device_id: deviceId,
3451
+ status
3452
+ });
3453
+ } catch (error) {
3454
+ return c.json({
3455
+ error: error instanceof Error ? error.message : String(error),
3456
+ status
3457
+ }, 400);
3458
+ }
3459
+ });
3384
3460
  app.post("/api/coordinator/admin/devices/:device_id/remove", async (c) => {
3385
3461
  const config = readCoordinatorSyncConfig();
3386
3462
  const status = coordinatorAdminStatusPayload(config);