@codemem/server 0.27.0 → 0.28.1

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";
@@ -188,6 +188,7 @@ var SECRET_CONFIG_KEYS = new Set([
188
188
  "observer_headers",
189
189
  "sync_coordinator_admin_secret"
190
190
  ]);
191
+ var REMOVED_CONFIG_KEYS = new Set(["observer_rich_openai_use_responses"]);
191
192
  var ALLOWED_KEYS = [
192
193
  "claude_command",
193
194
  "observer_base_url",
@@ -198,7 +199,6 @@ var ALLOWED_KEYS = [
198
199
  "observer_simple_temperature",
199
200
  "observer_rich_model",
200
201
  "observer_rich_temperature",
201
- "observer_rich_openai_use_responses",
202
202
  "observer_rich_reasoning_effort",
203
203
  "observer_rich_reasoning_summary",
204
204
  "observer_rich_max_output_tokens",
@@ -231,7 +231,6 @@ var DEFAULTS = {
231
231
  observer_runtime: "api_http",
232
232
  observer_auth_source: "auto",
233
233
  observer_tier_routing_enabled: false,
234
- observer_rich_openai_use_responses: false,
235
234
  observer_auth_command: [],
236
235
  observer_auth_timeout_ms: 1500,
237
236
  observer_auth_cache_ttl_s: 300,
@@ -275,6 +274,11 @@ function readConfigFile(configPath) {
275
274
  return {};
276
275
  }
277
276
  }
277
+ function withoutRemovedConfigKeys(configData) {
278
+ const next = { ...configData };
279
+ for (const key of REMOVED_CONFIG_KEYS) delete next[key];
280
+ return next;
281
+ }
278
282
  function getEffectiveConfig(configData) {
279
283
  const effective = {
280
284
  ...DEFAULTS,
@@ -406,7 +410,7 @@ function validateAndApplyUpdate(configData, key, value, providers) {
406
410
  configData[key] = value;
407
411
  return null;
408
412
  }
409
- if (key === "observer_tier_routing_enabled" || key === "observer_rich_openai_use_responses") {
413
+ if (key === "observer_tier_routing_enabled") {
410
414
  if (typeof value !== "boolean") return `${key} must be boolean`;
411
415
  configData[key] = value;
412
416
  return null;
@@ -446,7 +450,7 @@ function configRoutes(opts = {}) {
446
450
  const app = new Hono();
447
451
  app.get("/api/config", (c) => {
448
452
  const configPath = getConfigPath();
449
- const configData = readConfigFile(configPath);
453
+ const configData = withoutRemovedConfigKeys(readConfigFile(configPath));
450
454
  const effective = getEffectiveConfig(configData);
451
455
  return c.json({
452
456
  path: configPath,
@@ -469,7 +473,7 @@ function configRoutes(opts = {}) {
469
473
  if ("config" in payload && payload.config != null && (typeof payload.config !== "object" || Array.isArray(payload.config))) return c.json({ error: "config must be an object" }, 400);
470
474
  const updates = "config" in payload && payload.config != null && typeof payload.config === "object" && !Array.isArray(payload.config) ? payload.config : payload;
471
475
  const configPath = getCodememConfigPath();
472
- const beforeConfig = readCodememConfigFile();
476
+ const beforeConfig = withoutRemovedConfigKeys(readCodememConfigFile());
473
477
  const { allowedUpdates, error: protectedKeyError } = partitionViewerUpdates(updates, beforeConfig);
474
478
  if (protectedKeyError) return c.json({ error: protectedKeyError }, 403);
475
479
  const beforeEffective = getEffectiveConfig(beforeConfig);
@@ -1975,7 +1979,12 @@ function filterOpsForPeer(store, peerDeviceId, ops) {
1975
1979
  * When showDiag is false, sensitive fields (fingerprint, last_error, addresses)
1976
1980
  * are redacted.
1977
1981
  */
1978
- function mapPeerRow(store, row, showDiag) {
1982
+ function mapPeerRow(store, row, showDiag, recentOpsByPeer) {
1983
+ const peerId = String(row.peer_device_id ?? "");
1984
+ const recentOps = recentOpsByPeer?.get(peerId) ?? {
1985
+ in: 0,
1986
+ out: 0
1987
+ };
1979
1988
  return {
1980
1989
  peer_device_id: row.peer_device_id,
1981
1990
  name: row.name,
@@ -1989,9 +1998,33 @@ function mapPeerRow(store, row, showDiag) {
1989
1998
  claimed_local_actor: Boolean(row.claimed_local_actor),
1990
1999
  actor_id: row.actor_id ?? null,
1991
2000
  actor_display_name: row.actor_display_name ?? null,
1992
- project_scope: { ...currentProjectScope(row, readPeerProjectFilters(store, String(row.peer_device_id ?? ""))) }
2001
+ project_scope: { ...currentProjectScope(row, readPeerProjectFilters(store, String(row.peer_device_id ?? ""))) },
2002
+ recent_ops: {
2003
+ in: recentOps.in,
2004
+ out: recentOps.out
2005
+ }
1993
2006
  };
1994
2007
  }
2008
+ var SYNC_DIRECTION_WINDOW_SECONDS = 1440 * 60;
2009
+ function recentPeerOps(store) {
2010
+ const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_DIRECTION_WINDOW_SECONDS * 1e3)).toISOString();
2011
+ const rows = store.db.prepare(`SELECT peer_device_id,
2012
+ COALESCE(SUM(ops_in), 0) AS ops_in,
2013
+ COALESCE(SUM(ops_out), 0) AS ops_out
2014
+ FROM sync_attempts
2015
+ WHERE ok = 1 AND finished_at IS NOT NULL AND finished_at >= ?
2016
+ GROUP BY peer_device_id`).all(cutoff);
2017
+ const map = /* @__PURE__ */ new Map();
2018
+ for (const row of rows) {
2019
+ const id = String(row.peer_device_id ?? "").trim();
2020
+ if (!id) continue;
2021
+ map.set(id, {
2022
+ in: Number(row.ops_in ?? 0),
2023
+ out: Number(row.ops_out ?? 0)
2024
+ });
2025
+ }
2026
+ return map;
2027
+ }
1995
2028
  function isRecentIso(value, windowS = SYNC_STALE_AFTER_SECONDS) {
1996
2029
  const raw = String(value ?? "").trim();
1997
2030
  if (!raw) return false;
@@ -2361,8 +2394,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2361
2394
  statusPayload.daemon_running = runtimeStatus.phase === "starting" || daemonRunning;
2362
2395
  statusPayload.daemon_detail = runtimeStatus.detail ?? daemonDetail;
2363
2396
  }
2364
- const peersItems = traceSync("peerRows", () => store.db.prepare(PEERS_QUERY).all()).map((row) => {
2365
- const peer = mapPeerRow(store, row, showDiag);
2397
+ const peerRows = traceSync("peerRows", () => store.db.prepare(PEERS_QUERY).all());
2398
+ const recentOpsByPeer = traceSync("recentPeerOps", () => recentPeerOps(store));
2399
+ const peersItems = peerRows.map((row) => {
2400
+ const peer = mapPeerRow(store, row, showDiag, recentOpsByPeer);
2366
2401
  peer.status = peerStatus(peer);
2367
2402
  return peer;
2368
2403
  });
@@ -2447,7 +2482,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2447
2482
  const store = getStore();
2448
2483
  {
2449
2484
  const showDiag = queryBool(c.req.query("includeDiagnostics"));
2450
- const peers = store.db.prepare(PEERS_QUERY).all().map((row) => mapPeerRow(store, row, showDiag));
2485
+ const rows = store.db.prepare(PEERS_QUERY).all();
2486
+ const recentOpsByPeer = recentPeerOps(store);
2487
+ const peers = rows.map((row) => mapPeerRow(store, row, showDiag, recentOpsByPeer));
2451
2488
  return c.json({
2452
2489
  items: peers,
2453
2490
  redacted: !showDiag
@@ -3381,6 +3418,49 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
3381
3418
  }, 400);
3382
3419
  }
3383
3420
  });
3421
+ app.post("/api/coordinator/admin/devices/:device_id/enable", async (c) => {
3422
+ const config = readCoordinatorSyncConfig();
3423
+ const status = coordinatorAdminStatusPayload(config);
3424
+ if (status.readiness === "not_configured") return c.json({
3425
+ error: "coordinator_not_configured",
3426
+ status
3427
+ }, 400);
3428
+ if (!status.has_admin_secret) return c.json({
3429
+ error: "coordinator_admin_secret_missing",
3430
+ status
3431
+ }, 400);
3432
+ const deviceId = String(c.req.param("device_id") ?? "").trim();
3433
+ if (!deviceId) return c.json({
3434
+ error: "device_id required",
3435
+ status
3436
+ }, 400);
3437
+ const groupId = resolveCoordinatorAdminGroup(c.req.query("group_id"), status);
3438
+ if (!groupId) return c.json({
3439
+ error: "group_id required",
3440
+ status
3441
+ }, 400);
3442
+ try {
3443
+ if (!await coordinatorEnableDeviceAction({
3444
+ groupId,
3445
+ deviceId,
3446
+ remoteUrl: config.syncCoordinatorUrl || null,
3447
+ adminSecret: config.syncCoordinatorAdminSecret || null
3448
+ })) return c.json({
3449
+ error: "device_not_found",
3450
+ status
3451
+ }, 404);
3452
+ return c.json({
3453
+ ok: true,
3454
+ device_id: deviceId,
3455
+ status
3456
+ });
3457
+ } catch (error) {
3458
+ return c.json({
3459
+ error: error instanceof Error ? error.message : String(error),
3460
+ status
3461
+ }, 400);
3462
+ }
3463
+ });
3384
3464
  app.post("/api/coordinator/admin/devices/:device_id/remove", async (c) => {
3385
3465
  const config = readCoordinatorSyncConfig();
3386
3466
  const status = coordinatorAdminStatusPayload(config);