@codemem/server 0.26.2 → 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 +92 -8
- package/dist/index.js.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +13942 -11319
- package/static/favicon.svg +4 -14
- package/static/index.html +931 -367
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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";
|
|
7
7
|
import { RateLimiter } from "limiter";
|
|
8
|
-
import { homedir } from "node:os";
|
|
8
|
+
import { homedir, networkInterfaces } from "node:os";
|
|
9
9
|
import { and, count, desc, eq, inArray, isNotNull, max, ne } from "drizzle-orm";
|
|
10
10
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -1436,6 +1436,13 @@ function coordinatorAdminStatusPayload(config = readCoordinatorSyncConfig()) {
|
|
|
1436
1436
|
has_groups: hasGroups
|
|
1437
1437
|
};
|
|
1438
1438
|
}
|
|
1439
|
+
function pairingAdvertiseAddresses(config = readCoordinatorSyncConfig()) {
|
|
1440
|
+
const advertise = String(config.syncAdvertise || "auto").trim().toLowerCase();
|
|
1441
|
+
if (advertise && advertise !== "auto" && advertise !== "default") return mergeAddresses([], String(config.syncAdvertise || "").split(",").map((item) => item.trim()).filter(Boolean));
|
|
1442
|
+
if (config.syncHost && config.syncHost !== "0.0.0.0") return [`${config.syncHost}:${config.syncPort}`];
|
|
1443
|
+
const addresses = Object.values(networkInterfaces()).flatMap((entries) => entries ?? []).filter((entry) => !entry.internal).map((entry) => entry.address).filter((address) => address && address !== "127.0.0.1" && address !== "::1").map((address) => `${address}:${config.syncPort}`);
|
|
1444
|
+
return [...new Set(addresses)];
|
|
1445
|
+
}
|
|
1439
1446
|
function resolveCoordinatorAdminGroup(requestedGroup, status) {
|
|
1440
1447
|
const explicit = String(requestedGroup ?? "").trim();
|
|
1441
1448
|
if (explicit) return explicit;
|
|
@@ -1968,7 +1975,12 @@ function filterOpsForPeer(store, peerDeviceId, ops) {
|
|
|
1968
1975
|
* When showDiag is false, sensitive fields (fingerprint, last_error, addresses)
|
|
1969
1976
|
* are redacted.
|
|
1970
1977
|
*/
|
|
1971
|
-
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
|
+
};
|
|
1972
1984
|
return {
|
|
1973
1985
|
peer_device_id: row.peer_device_id,
|
|
1974
1986
|
name: row.name,
|
|
@@ -1982,9 +1994,33 @@ function mapPeerRow(store, row, showDiag) {
|
|
|
1982
1994
|
claimed_local_actor: Boolean(row.claimed_local_actor),
|
|
1983
1995
|
actor_id: row.actor_id ?? null,
|
|
1984
1996
|
actor_display_name: row.actor_display_name ?? null,
|
|
1985
|
-
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
|
+
}
|
|
1986
2002
|
};
|
|
1987
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
|
+
}
|
|
1988
2024
|
function isRecentIso(value, windowS = SYNC_STALE_AFTER_SECONDS) {
|
|
1989
2025
|
const raw = String(value ?? "").trim();
|
|
1990
2026
|
if (!raw) return false;
|
|
@@ -2354,8 +2390,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2354
2390
|
statusPayload.daemon_running = runtimeStatus.phase === "starting" || daemonRunning;
|
|
2355
2391
|
statusPayload.daemon_detail = runtimeStatus.detail ?? daemonDetail;
|
|
2356
2392
|
}
|
|
2357
|
-
const
|
|
2358
|
-
|
|
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);
|
|
2359
2397
|
peer.status = peerStatus(peer);
|
|
2360
2398
|
return peer;
|
|
2361
2399
|
});
|
|
@@ -2440,7 +2478,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2440
2478
|
const store = getStore();
|
|
2441
2479
|
{
|
|
2442
2480
|
const showDiag = queryBool(c.req.query("includeDiagnostics"));
|
|
2443
|
-
const
|
|
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));
|
|
2444
2484
|
return c.json({
|
|
2445
2485
|
items: peers,
|
|
2446
2486
|
redacted: !showDiag
|
|
@@ -2484,6 +2524,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2484
2524
|
redacted: true,
|
|
2485
2525
|
pairing_filter_hint: PAIRING_FILTER_HINT
|
|
2486
2526
|
});
|
|
2527
|
+
const config = readCoordinatorSyncConfig();
|
|
2487
2528
|
const d = drizzle(store.db, { schema });
|
|
2488
2529
|
const deviceRow = d.select({
|
|
2489
2530
|
device_id: schema.syncDevice.device_id,
|
|
@@ -2511,7 +2552,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2511
2552
|
fingerprint,
|
|
2512
2553
|
public_key: publicKey ?? null,
|
|
2513
2554
|
pairing_filter_hint: PAIRING_FILTER_HINT,
|
|
2514
|
-
addresses:
|
|
2555
|
+
addresses: pairingAdvertiseAddresses(config)
|
|
2515
2556
|
});
|
|
2516
2557
|
}
|
|
2517
2558
|
});
|
|
@@ -3373,6 +3414,49 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3373
3414
|
}, 400);
|
|
3374
3415
|
}
|
|
3375
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
|
+
});
|
|
3376
3460
|
app.post("/api/coordinator/admin/devices/:device_id/remove", async (c) => {
|
|
3377
3461
|
const config = readCoordinatorSyncConfig();
|
|
3378
3462
|
const status = coordinatorAdminStatusPayload(config);
|