@codemem/server 0.24.0 → 0.25.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, coordinatorCreateInviteAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorStatusSnapshot, createCoordinatorReciprocalApproval, ensureDeviceIdentity, extractReplicationOps, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, 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, 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";
@@ -799,6 +799,30 @@ function memoryRoutes(getStore) {
799
799
  return c.json(pack);
800
800
  }
801
801
  });
802
+ app.post("/api/pack/trace", async (c) => {
803
+ const store = getStore();
804
+ const parsed = await c.req.json().catch(() => Symbol.for("invalid-json"));
805
+ if (parsed === Symbol.for("invalid-json")) return c.json({ error: "invalid json body" }, 400);
806
+ const body = parsed;
807
+ const context = typeof body?.context === "string" ? body.context.trim() : "";
808
+ if (!context) return c.json({ error: "context required" }, 400);
809
+ const limit = body?.limit == null ? 10 : typeof body.limit === "number" && Number.isInteger(body.limit) && body.limit >= 1 ? body.limit : null;
810
+ if (limit == null) return c.json({ error: "limit must be a positive int" }, 400);
811
+ let tokenBudget = null;
812
+ if (body?.token_budget != null) {
813
+ if (typeof body.token_budget !== "number" || !Number.isInteger(body.token_budget) || body.token_budget < 0) return c.json({ error: "token_budget must be int" }, 400);
814
+ tokenBudget = body.token_budget;
815
+ }
816
+ const project = typeof body?.project === "string" && body.project.trim() ? body.project : null;
817
+ if (body?.working_set_files != null && !Array.isArray(body.working_set_files)) return c.json({ error: "working_set_files must be an array of strings" }, 400);
818
+ if (Array.isArray(body?.working_set_files) && body.working_set_files.some((value) => typeof value !== "string")) return c.json({ error: "working_set_files must be an array of strings" }, 400);
819
+ const workingSetFiles = Array.isArray(body?.working_set_files) ? body.working_set_files : [];
820
+ const filters = {};
821
+ if (project) filters.project = project;
822
+ if (workingSetFiles.length > 0) filters.working_set_paths = workingSetFiles;
823
+ const trace = await store.buildMemoryPackTraceAsync(context, limit, tokenBudget, filters);
824
+ return c.json(trace);
825
+ });
802
826
  app.get("/api/memory", (c) => {
803
827
  const store = getStore();
804
828
  {
@@ -1214,6 +1238,17 @@ function rawEventsRoutes(getStore, sweeper) {
1214
1238
  *
1215
1239
  * Ports Python's viewer_routes/stats.py.
1216
1240
  */
1241
+ function maintenanceJobSortKey(job) {
1242
+ return [job.started_at ?? job.updated_at, job.kind];
1243
+ }
1244
+ function sortActiveMaintenanceJobs$1(jobs) {
1245
+ return [...jobs].sort((a, b) => {
1246
+ const [aTime, aKind] = maintenanceJobSortKey(a);
1247
+ const [bTime, bKind] = maintenanceJobSortKey(b);
1248
+ if (aTime !== bTime) return aTime.localeCompare(bTime);
1249
+ return aKind.localeCompare(bKind);
1250
+ });
1251
+ }
1217
1252
  /**
1218
1253
  * Create stats routes. The store factory is called per-request to get a
1219
1254
  * fresh connection (matching the Python viewer pattern).
@@ -1230,9 +1265,18 @@ function statsRoutes(getStore) {
1230
1265
  };
1231
1266
  app.get("/api/stats", (c) => {
1232
1267
  const store = getStore();
1268
+ const activeJobs = sortActiveMaintenanceJobs$1(listMaintenanceJobs(store.db).filter((job) => job.status === "pending" || job.status === "running" || job.status === "failed")).map((job) => ({
1269
+ kind: job.kind,
1270
+ title: job.title,
1271
+ status: job.status,
1272
+ message: job.message,
1273
+ progress: job.progress,
1274
+ error: job.error
1275
+ }));
1233
1276
  return c.json({
1234
1277
  ...store.stats(),
1235
- viewer_pid: process.pid
1278
+ viewer_pid: process.pid,
1279
+ maintenance_jobs: activeJobs
1236
1280
  });
1237
1281
  });
1238
1282
  app.get("/api/runtime", (c) => {
@@ -1318,6 +1362,31 @@ var SYNC_STALE_AFTER_SECONDS = 600;
1318
1362
  var SYNC_PROTOCOL_VERSION = "2";
1319
1363
  var LEGACY_SYNC_ACTOR_DISPLAY_NAME = "Legacy synced peer";
1320
1364
  var LEGACY_SHARED_WORKSPACE_ID = "shared:legacy";
1365
+ function sortActiveMaintenanceJobs(jobs) {
1366
+ return [...jobs].sort((a, b) => {
1367
+ const aTime = a.started_at ?? a.updated_at;
1368
+ const bTime = b.started_at ?? b.updated_at;
1369
+ if (aTime !== bTime) return aTime.localeCompare(bTime);
1370
+ return a.kind.localeCompare(b.kind);
1371
+ });
1372
+ }
1373
+ function summarizeMaintenanceJobs(jobs, showDiagnostics) {
1374
+ return sortActiveMaintenanceJobs(jobs.filter((job) => [
1375
+ "pending",
1376
+ "running",
1377
+ "failed"
1378
+ ].includes(String(job.status ?? "")))).map((job) => ({
1379
+ kind: job.kind,
1380
+ title: job.title,
1381
+ status: job.status,
1382
+ progress: job.progress,
1383
+ ...showDiagnostics ? {
1384
+ message: job.message,
1385
+ error: job.error,
1386
+ metadata: job.metadata
1387
+ } : {}
1388
+ }));
1389
+ }
1321
1390
  function syncKeysDir() {
1322
1391
  return process.env.CODEMEM_KEYS_DIR?.trim() || void 0;
1323
1392
  }
@@ -2238,6 +2307,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2238
2307
  const latestAttemptError = String(attemptsItems[0]?.error || "").trim();
2239
2308
  const statusBlock = {
2240
2309
  ...statusPayload,
2310
+ background_maintenance: summarizeMaintenanceJobs(listMaintenanceJobs(store.db), showDiag),
2241
2311
  peers: peersMap,
2242
2312
  pending: 0,
2243
2313
  sync: {},