@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 +102 -3
- package/dist/index.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/stats.d.ts.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +3 -3
- package/static/app.js +513 -91
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 === "
|
|
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: {},
|