@codemem/server 0.23.0 → 0.25.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, 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";
@@ -193,6 +193,15 @@ var ALLOWED_KEYS = [
193
193
  "observer_base_url",
194
194
  "observer_provider",
195
195
  "observer_model",
196
+ "observer_tier_routing_enabled",
197
+ "observer_simple_model",
198
+ "observer_simple_temperature",
199
+ "observer_rich_model",
200
+ "observer_rich_temperature",
201
+ "observer_rich_openai_use_responses",
202
+ "observer_rich_reasoning_effort",
203
+ "observer_rich_reasoning_summary",
204
+ "observer_rich_max_output_tokens",
196
205
  "observer_runtime",
197
206
  "observer_auth_source",
198
207
  "observer_auth_file",
@@ -221,6 +230,8 @@ var DEFAULTS = {
221
230
  claude_command: ["claude"],
222
231
  observer_runtime: "api_http",
223
232
  observer_auth_source: "auto",
233
+ observer_tier_routing_enabled: false,
234
+ observer_rich_openai_use_responses: false,
224
235
  observer_auth_command: [],
225
236
  observer_auth_timeout_ms: 1500,
226
237
  observer_auth_cache_ttl_s: 300,
@@ -319,6 +330,13 @@ function parsePositiveInt(value, allowZero = false) {
319
330
  if (allowZero) return parsed >= 0 ? parsed : null;
320
331
  return parsed > 0 ? parsed : null;
321
332
  }
333
+ function parseFiniteNumber(value, allowZero = true) {
334
+ if (typeof value === "boolean") return null;
335
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim().length > 0 ? Number(value.trim()) : NaN;
336
+ if (!Number.isFinite(parsed)) return null;
337
+ if (allowZero) return parsed >= 0 ? parsed : null;
338
+ return parsed > 0 ? parsed : null;
339
+ }
322
340
  function asStringMap(value) {
323
341
  if (value == null || typeof value !== "object" || Array.isArray(value)) return null;
324
342
  const parsed = {};
@@ -388,13 +406,24 @@ function validateAndApplyUpdate(configData, key, value, providers) {
388
406
  configData[key] = value;
389
407
  return null;
390
408
  }
391
- if (key === "observer_base_url" || key === "observer_model" || key === "observer_auth_file" || key === "sync_host" || key === "sync_coordinator_url" || key === "sync_coordinator_group") {
409
+ if (key === "observer_tier_routing_enabled" || key === "observer_rich_openai_use_responses") {
410
+ if (typeof value !== "boolean") return `${key} must be boolean`;
411
+ configData[key] = value;
412
+ return null;
413
+ }
414
+ if (key === "observer_base_url" || key === "observer_model" || key === "observer_simple_model" || key === "observer_rich_model" || key === "observer_rich_reasoning_effort" || key === "observer_rich_reasoning_summary" || key === "observer_auth_file" || key === "sync_host" || key === "sync_coordinator_url" || key === "sync_coordinator_group") {
392
415
  if (typeof value !== "string") return `${key} must be string`;
393
416
  const trimmed = value.trim();
394
417
  if (!trimmed) delete configData[key];
395
418
  else configData[key] = trimmed;
396
419
  return null;
397
420
  }
421
+ if (key === "observer_simple_temperature" || key === "observer_rich_temperature") {
422
+ const parsed = parseFiniteNumber(value, true);
423
+ if (parsed == null) return `${key} must be non-negative number`;
424
+ configData[key] = parsed;
425
+ return null;
426
+ }
398
427
  const allowZero = key === "observer_auth_cache_ttl_s";
399
428
  const parsed = parsePositiveInt(value, allowZero);
400
429
  if (parsed == null) return `${key} must be ${allowZero ? "non-negative int" : "int"}`;
@@ -770,6 +799,30 @@ function memoryRoutes(getStore) {
770
799
  return c.json(pack);
771
800
  }
772
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
+ });
773
826
  app.get("/api/memory", (c) => {
774
827
  const store = getStore();
775
828
  {
@@ -1185,6 +1238,17 @@ function rawEventsRoutes(getStore, sweeper) {
1185
1238
  *
1186
1239
  * Ports Python's viewer_routes/stats.py.
1187
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
+ }
1188
1252
  /**
1189
1253
  * Create stats routes. The store factory is called per-request to get a
1190
1254
  * fresh connection (matching the Python viewer pattern).
@@ -1201,9 +1265,18 @@ function statsRoutes(getStore) {
1201
1265
  };
1202
1266
  app.get("/api/stats", (c) => {
1203
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
+ }));
1204
1276
  return c.json({
1205
1277
  ...store.stats(),
1206
- viewer_pid: process.pid
1278
+ viewer_pid: process.pid,
1279
+ maintenance_jobs: activeJobs
1207
1280
  });
1208
1281
  });
1209
1282
  app.get("/api/runtime", (c) => {
@@ -1289,6 +1362,31 @@ var SYNC_STALE_AFTER_SECONDS = 600;
1289
1362
  var SYNC_PROTOCOL_VERSION = "2";
1290
1363
  var LEGACY_SYNC_ACTOR_DISPLAY_NAME = "Legacy synced peer";
1291
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
+ }
1292
1390
  function syncKeysDir() {
1293
1391
  return process.env.CODEMEM_KEYS_DIR?.trim() || void 0;
1294
1392
  }
@@ -2209,6 +2307,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
2209
2307
  const latestAttemptError = String(attemptsItems[0]?.error || "").trim();
2210
2308
  const statusBlock = {
2211
2309
  ...statusPayload,
2310
+ background_maintenance: summarizeMaintenanceJobs(listMaintenanceJobs(store.db), showDiag),
2212
2311
  peers: peersMap,
2213
2312
  pending: 0,
2214
2313
  sync: {},