@codemem/server 0.40.1 → 0.41.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,14 +1,14 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, MemoryStore, RecipientPolicyEdgeRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildAuthHeaders, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coerceObserverCommand, commitRecipientPolicyEdges, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, inviteTokenDigest, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parseReassignScopePayload, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, recordNonce, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, rejectInboundScopeFailures, requestJson, resolveDbPath, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, stripPrivateObj, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
2
+ import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
3
+ import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, MemoryStore, RecipientPolicyEdgeRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildAuthHeaders, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, clonePromptPackAttempt, coerceObserverCommand, commitRecipientPolicyEdges, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, detectInstallKind, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, inviteTokenDigest, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parseReassignScopePayload, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, rejectInboundScopeFailures, requestJson, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, stripPrivateObj, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifyRecipientReviewedIntent, 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
8
  import { and, count, desc, eq, inArray, max, ne } from "drizzle-orm";
9
9
  import { drizzle } from "drizzle-orm/better-sqlite3";
10
+ import { homedir, hostname, networkInterfaces } from "node:os";
10
11
  import { createHash, randomUUID } from "node:crypto";
11
- import { hostname, networkInterfaces } from "node:os";
12
12
  //#region src/middleware.ts
13
13
  var LOOPBACK_HOSTS = new Set([
14
14
  "127.0.0.1",
@@ -510,6 +510,32 @@ function configRoutes(opts = {}) {
510
510
  return app;
511
511
  }
512
512
  //#endregion
513
+ //#region src/routes/health.ts
514
+ function databaseReachable(getStore) {
515
+ try {
516
+ getStore().db.pragma("schema_version", { simple: true });
517
+ return true;
518
+ } catch {
519
+ return false;
520
+ }
521
+ }
522
+ function healthRoutes(getStore) {
523
+ const app = new Hono();
524
+ app.get("/api/health", (c) => {
525
+ const reachable = databaseReachable(getStore);
526
+ c.header("Cache-Control", "no-store");
527
+ return c.json({
528
+ service: VIEWER_SERVICE_DISCRIMINATOR,
529
+ version: VERSION$1,
530
+ pid: process.pid,
531
+ uptime_ms: Math.max(0, Math.floor(process.uptime() * 1e3)),
532
+ ready: reachable,
533
+ database: { reachable }
534
+ });
535
+ });
536
+ return app;
537
+ }
538
+ //#endregion
513
539
  //#region src/helpers.ts
514
540
  /**
515
541
  * Shared helpers for viewer-server routes.
@@ -1051,6 +1077,363 @@ function observerStatusRoutes(deps) {
1051
1077
  return app;
1052
1078
  }
1053
1079
  //#endregion
1080
+ //#region src/routes/pack.ts
1081
+ var MAX_LEDGER_PAYLOAD_BYTES = 16 * 1024;
1082
+ var MAX_METADATA_FIELD_CHARS = 512;
1083
+ var MAX_WORKING_SET_FILES = 50;
1084
+ var MAX_WORKING_SET_PATH_CHARS = 512;
1085
+ var INVALID_JSON = Symbol("invalid-json");
1086
+ var PACK_KEYS = new Set([
1087
+ "context",
1088
+ "limit",
1089
+ "token_budget",
1090
+ "project",
1091
+ "cwd",
1092
+ "all_projects",
1093
+ "working_set_files",
1094
+ "compact",
1095
+ "compact_detail_count",
1096
+ "db_path",
1097
+ "identity_target",
1098
+ "attempt"
1099
+ ]);
1100
+ var LEDGER_KEYS = new Set([
1101
+ "action",
1102
+ "attempt_id",
1103
+ "started_at",
1104
+ "source",
1105
+ "stream_id",
1106
+ "source_session_id",
1107
+ "prompt_number",
1108
+ "request_id",
1109
+ "retrieval_status",
1110
+ "delivery_status",
1111
+ "failure_code",
1112
+ "failure_stage",
1113
+ "original_attempt_id",
1114
+ "db_path",
1115
+ "identity_target"
1116
+ ]);
1117
+ var IDENTITY_TARGET_KEYS = new Set([
1118
+ "device_id",
1119
+ "actor_id_present",
1120
+ "actor_id",
1121
+ "config_path",
1122
+ "runtime_root",
1123
+ "workspace_id",
1124
+ "home_dir",
1125
+ "pack_compression",
1126
+ "embedding_disabled",
1127
+ "embedding_model"
1128
+ ]);
1129
+ var BOOLEAN_IDENTITY_TARGET_KEYS = new Set(["actor_id_present", "embedding_disabled"]);
1130
+ var ATTEMPT_KEYS = new Set([
1131
+ "attempt_id",
1132
+ "started_at",
1133
+ "source",
1134
+ "stream_id",
1135
+ "source_session_id",
1136
+ "prompt_number",
1137
+ "request_id"
1138
+ ]);
1139
+ var FORBIDDEN_LEDGER_KEYS = new Set([
1140
+ "body",
1141
+ "context",
1142
+ "pack",
1143
+ "pack_text",
1144
+ "path",
1145
+ "preview",
1146
+ "prompt",
1147
+ "query",
1148
+ "raw_prompt",
1149
+ "title"
1150
+ ]);
1151
+ function invalidRequest(message) {
1152
+ return { error: {
1153
+ code: "invalid_request",
1154
+ message
1155
+ } };
1156
+ }
1157
+ function viewerDbMismatch() {
1158
+ return { error: {
1159
+ code: "viewer_db_mismatch",
1160
+ message: "viewer database does not match request"
1161
+ } };
1162
+ }
1163
+ function viewerIdentityMismatch() {
1164
+ return { error: {
1165
+ code: "viewer_identity_mismatch",
1166
+ message: "viewer identity does not match request"
1167
+ } };
1168
+ }
1169
+ function viewerContractUnsupported() {
1170
+ return { error: {
1171
+ code: "viewer_contract_unsupported",
1172
+ message: "viewer request contract is incompatible"
1173
+ } };
1174
+ }
1175
+ function isRecord(value) {
1176
+ return value != null && typeof value === "object" && !Array.isArray(value);
1177
+ }
1178
+ function isAbsolutePath(value) {
1179
+ return posix.isAbsolute(value) || win32.isAbsolute(value);
1180
+ }
1181
+ function normalizeWorkingSetPath(value) {
1182
+ const trimmed = value.trim();
1183
+ if (!trimmed || trimmed.length > MAX_WORKING_SET_PATH_CHARS || isAbsolutePath(trimmed)) return null;
1184
+ const normalized = posix.normalize(trimmed.replaceAll("\\", "/")).replace(/^\.\//, "");
1185
+ if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../")) return null;
1186
+ return normalized;
1187
+ }
1188
+ function requestedDbMatches(store, value) {
1189
+ if (value == null) return true;
1190
+ if (typeof value !== "string" || !value.trim()) return null;
1191
+ return resolve(resolveDbPath(value)) === resolve(store.dbPath);
1192
+ }
1193
+ function normalizeIdentityPath(value) {
1194
+ if (!value?.trim()) return null;
1195
+ return resolve(resolveDbPath(value.trim()));
1196
+ }
1197
+ function currentIdentityTarget() {
1198
+ return {
1199
+ device_id: process.env.CODEMEM_DEVICE_ID?.trim() || null,
1200
+ actor_id_present: Object.hasOwn(process.env, "CODEMEM_ACTOR_ID"),
1201
+ actor_id: process.env.CODEMEM_ACTOR_ID?.trim() || null,
1202
+ config_path: normalizeIdentityPath(process.env.CODEMEM_CONFIG),
1203
+ runtime_root: normalizeIdentityPath(process.env.CODEMEM_RUNTIME_ROOT),
1204
+ workspace_id: process.env.CODEMEM_WORKSPACE_ID?.trim() || null,
1205
+ home_dir: normalizeIdentityPath(process.env.HOME || homedir()),
1206
+ pack_compression: process.env.CODEMEM_PACK_COMPRESSION?.trim() || null,
1207
+ embedding_disabled: [
1208
+ "1",
1209
+ "true",
1210
+ "yes"
1211
+ ].includes(String(process.env.CODEMEM_EMBEDDING_DISABLED || "").toLowerCase()),
1212
+ embedding_model: process.env.CODEMEM_EMBEDDING_MODEL || "Xenova/bge-small-en-v1.5"
1213
+ };
1214
+ }
1215
+ function requestedIdentityMatches(value) {
1216
+ if (value == null) return true;
1217
+ if (!isRecord(value)) return null;
1218
+ if (Object.keys(value).length !== IDENTITY_TARGET_KEYS.size || Object.keys(value).some((key) => !IDENTITY_TARGET_KEYS.has(key))) return "unsupported";
1219
+ const expected = currentIdentityTarget();
1220
+ for (const key of IDENTITY_TARGET_KEYS) {
1221
+ const requested = value[key];
1222
+ if (BOOLEAN_IDENTITY_TARGET_KEYS.has(key)) {
1223
+ if (typeof requested !== "boolean") return null;
1224
+ } else if (requested !== null && typeof requested !== "string") return null;
1225
+ if (requested !== expected[key]) return false;
1226
+ }
1227
+ return true;
1228
+ }
1229
+ function validateMetadataFields(payload, allowedKeys) {
1230
+ if (Buffer.byteLength(JSON.stringify(payload), "utf8") > MAX_LEDGER_PAYLOAD_BYTES) return "ledger metadata exceeds 16384 bytes";
1231
+ for (const key of Object.keys(payload)) {
1232
+ if (FORBIDDEN_LEDGER_KEYS.has(key)) return `ledger metadata rejects sensitive field: ${key}`;
1233
+ if (!allowedKeys.has(key)) return `ledger metadata contains unsupported field: ${key}`;
1234
+ }
1235
+ if (typeof payload.attempt_id !== "string" || payload.attempt_id.length === 0) return "ledger metadata requires attempt_id";
1236
+ if (payload.prompt_number != null && (typeof payload.prompt_number !== "number" || !Number.isInteger(payload.prompt_number) || payload.prompt_number < 0)) return "ledger metadata prompt_number must be a non-negative integer";
1237
+ for (const key of [
1238
+ "attempt_id",
1239
+ "started_at",
1240
+ "source",
1241
+ "stream_id",
1242
+ "source_session_id",
1243
+ "request_id",
1244
+ "failure_code",
1245
+ "failure_stage",
1246
+ "original_attempt_id"
1247
+ ]) {
1248
+ const field = payload[key];
1249
+ if (field != null && (typeof field !== "string" || field.length > MAX_METADATA_FIELD_CHARS)) return `ledger metadata field ${key} is invalid`;
1250
+ if (typeof field === "string" && isAbsolutePath(field)) return `ledger metadata rejects absolute paths in field: ${key}`;
1251
+ }
1252
+ return null;
1253
+ }
1254
+ function attemptMetadata(payload) {
1255
+ return {
1256
+ attemptId: payload.attempt_id,
1257
+ startedAt: payload.started_at ?? (/* @__PURE__ */ new Date()).toISOString(),
1258
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1259
+ source: payload.source ?? "opencode",
1260
+ streamId: payload.stream_id ?? null,
1261
+ sourceSessionId: payload.source_session_id ?? null,
1262
+ promptNumber: payload.prompt_number ?? null,
1263
+ requestId: payload.request_id ?? null
1264
+ };
1265
+ }
1266
+ function validatePackRequest(value) {
1267
+ if (!isRecord(value)) return "request body must be an object";
1268
+ for (const key of Object.keys(value)) if (!PACK_KEYS.has(key)) return `request body contains unsupported field: ${key}`;
1269
+ const context = typeof value.context === "string" ? value.context.trim() : "";
1270
+ if (!context) return "context required";
1271
+ const limit = value.limit ?? 10;
1272
+ if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1) return "limit must be a positive int";
1273
+ const tokenBudget = value.token_budget ?? null;
1274
+ if (tokenBudget !== null && (typeof tokenBudget !== "number" || !Number.isInteger(tokenBudget) || tokenBudget < 0)) return "token_budget must be a non-negative int";
1275
+ if (value.all_projects != null && typeof value.all_projects !== "boolean") return "all_projects must be a boolean";
1276
+ if (value.project != null && (typeof value.project !== "string" || !value.project.trim())) return "project must be a non-empty string";
1277
+ if (value.cwd != null && (typeof value.cwd !== "string" || !value.cwd.trim() || !isAbsolute(value.cwd))) return "cwd must be an absolute path";
1278
+ if (value.working_set_files != null && !Array.isArray(value.working_set_files)) return "working_set_files must be an array of repository-relative strings";
1279
+ const workingSetValues = value.working_set_files ?? [];
1280
+ if (workingSetValues.length > MAX_WORKING_SET_FILES) return `working_set_files must contain at most ${MAX_WORKING_SET_FILES} entries`;
1281
+ const workingSetFiles = [];
1282
+ for (const entry of workingSetValues) {
1283
+ if (typeof entry !== "string") return "working_set_files must be an array of repository-relative strings";
1284
+ const normalized = normalizeWorkingSetPath(entry);
1285
+ if (!normalized) return "working_set_files contains an invalid repository-relative path";
1286
+ if (!workingSetFiles.includes(normalized)) workingSetFiles.push(normalized);
1287
+ }
1288
+ if (value.compact != null && typeof value.compact !== "boolean") return "compact must be a boolean";
1289
+ if (value.compact_detail_count != null && (typeof value.compact_detail_count !== "number" || !Number.isInteger(value.compact_detail_count) || value.compact_detail_count < 0)) return "compact_detail_count must be a non-negative int";
1290
+ let attempt;
1291
+ if (value.attempt != null) {
1292
+ if (!isRecord(value.attempt)) return "attempt must be an object";
1293
+ const attemptError = validateMetadataFields(value.attempt, ATTEMPT_KEYS);
1294
+ if (attemptError) return attemptError;
1295
+ attempt = value.attempt;
1296
+ }
1297
+ const filters = {};
1298
+ if (value.all_projects !== true) {
1299
+ const envProject = process.env.CODEMEM_PROJECT?.trim() || null;
1300
+ const requestCwd = value.cwd;
1301
+ const requestProject = value.project;
1302
+ const project = resolveProject(requestCwd ?? process.cwd(), requestProject ?? (requestCwd == null ? envProject : void 0));
1303
+ if (project) filters.project = project;
1304
+ }
1305
+ if (workingSetFiles.length > 0) filters.working_set_paths = workingSetFiles;
1306
+ let renderOptions;
1307
+ if (value.compact != null || value.compact_detail_count != null) renderOptions = {
1308
+ compact: value.compact === true || value.compact_detail_count != null,
1309
+ ...value.compact_detail_count != null ? { compactDetailCount: value.compact_detail_count } : {}
1310
+ };
1311
+ return {
1312
+ context,
1313
+ limit,
1314
+ tokenBudget,
1315
+ filters,
1316
+ renderOptions,
1317
+ attempt
1318
+ };
1319
+ }
1320
+ function ledgerFailureStatus(reason) {
1321
+ if (reason === "idempotency_conflict") return 409;
1322
+ if (reason === "attempt_not_found") return 422;
1323
+ if (reason === "storage_unavailable") return 503;
1324
+ return 400;
1325
+ }
1326
+ function dispatchLedger(store, payload) {
1327
+ const action = payload.action;
1328
+ if (action !== "record" && action !== "delivery" && action !== "cache_reuse") return "ledger action is invalid";
1329
+ const metadata = attemptMetadata(payload);
1330
+ if (action === "delivery") {
1331
+ const status = payload.delivery_status;
1332
+ if (status !== "handed_off" && status !== "failed" && status !== "unknown") return "delivery action requires a valid delivery_status";
1333
+ return tryUpdateRetrievalDelivery(store.db, metadata.attemptId, status);
1334
+ }
1335
+ if (action === "cache_reuse") {
1336
+ if (typeof payload.original_attempt_id !== "string" || !payload.original_attempt_id) return "cache_reuse action requires original_attempt_id";
1337
+ return clonePromptPackAttempt(store.db, payload.original_attempt_id, metadata);
1338
+ }
1339
+ if (payload.retrieval_status !== "skipped" && payload.retrieval_status !== "failed" || typeof payload.failure_code !== "string" || !payload.failure_code || typeof payload.failure_stage !== "string" || !payload.failure_stage) return "record action requires retrieval_status, failure_code, and failure_stage";
1340
+ return recordPromptPackTerminal(store.db, metadata, payload.retrieval_status, payload.failure_code, payload.failure_stage);
1341
+ }
1342
+ function packTransportRoutes(getStore) {
1343
+ const app = new Hono();
1344
+ app.get("/api/prompt-pack-profile", (c) => {
1345
+ try {
1346
+ const store = getStore();
1347
+ if (!store.hasCurrentIdentity()) return c.json(viewerIdentityMismatch(), 409);
1348
+ return c.json({
1349
+ service: "codemem-viewer",
1350
+ protocol_version: 1,
1351
+ db_path: resolve(store.dbPath),
1352
+ identity_target: currentIdentityTarget()
1353
+ });
1354
+ } catch {
1355
+ return c.json({ error: {
1356
+ code: "profile_failed",
1357
+ message: "viewer profile could not be read"
1358
+ } }, 500);
1359
+ }
1360
+ });
1361
+ app.post("/api/pack", async (c) => {
1362
+ const parsed = await c.req.json().catch(() => INVALID_JSON);
1363
+ if (parsed === INVALID_JSON) return c.json(invalidRequest("invalid json body"), 400);
1364
+ const request = validatePackRequest(parsed);
1365
+ if (typeof request === "string") {
1366
+ if (request.startsWith("request body contains unsupported field:")) return c.json(viewerContractUnsupported(), 409);
1367
+ return c.json(invalidRequest(request), 400);
1368
+ }
1369
+ try {
1370
+ const store = getStore();
1371
+ const dbMatches = requestedDbMatches(store, parsed.db_path);
1372
+ if (dbMatches == null) return c.json(invalidRequest("db_path must be a non-empty string"), 400);
1373
+ if (!dbMatches) return c.json(viewerDbMismatch(), 409);
1374
+ const identityMatches = requestedIdentityMatches(parsed.identity_target);
1375
+ if (identityMatches === "unsupported") return c.json(viewerContractUnsupported(), 409);
1376
+ if (identityMatches == null) return c.json(invalidRequest("identity_target is invalid"), 400);
1377
+ if (!identityMatches) return c.json(viewerIdentityMismatch(), 409);
1378
+ if (!store.hasCurrentIdentity()) return c.json(viewerIdentityMismatch(), 409);
1379
+ if (!request.attempt) {
1380
+ const pack = await store.buildMemoryPackAsync(request.context, request.limit, request.tokenBudget, request.filters, request.renderOptions);
1381
+ return c.json(pack);
1382
+ }
1383
+ const artifacts = await store.buildMemoryPackWithTraceAsync(request.context, request.limit, request.tokenBudget, request.filters, request.renderOptions);
1384
+ let ledgerArtifactFingerprint;
1385
+ try {
1386
+ ledgerArtifactFingerprint = promptPackArtifactFingerprint(store.db, request.context, request.filters, artifacts);
1387
+ } catch {}
1388
+ let ledgerOutcome;
1389
+ try {
1390
+ ledgerOutcome = recordPromptPackArtifacts(store.db, attemptMetadata(request.attempt), request.context, request.filters, artifacts);
1391
+ } catch {}
1392
+ return c.json({
1393
+ ...artifacts.response,
1394
+ ...ledgerArtifactFingerprint ? { ledger_artifact_fingerprint: ledgerArtifactFingerprint } : {},
1395
+ ...ledgerOutcome?.ok === false && ledgerOutcome.reason === "idempotency_conflict" ? { ledger_outcome: ledgerOutcome } : {}
1396
+ });
1397
+ } catch {
1398
+ return c.json({ error: {
1399
+ code: "pack_failed",
1400
+ message: "memory pack could not be built"
1401
+ } }, 500);
1402
+ }
1403
+ });
1404
+ app.post("/api/prompt-pack-ledger", async (c) => {
1405
+ const parsed = await c.req.json().catch(() => INVALID_JSON);
1406
+ if (parsed === INVALID_JSON) return c.json(invalidRequest("invalid json body"), 400);
1407
+ if (!isRecord(parsed)) return c.json(invalidRequest("request body must be an object"), 400);
1408
+ const validationError = validateMetadataFields(parsed, LEDGER_KEYS);
1409
+ if (validationError) {
1410
+ if (validationError.startsWith("ledger metadata contains unsupported field:")) return c.json(viewerContractUnsupported(), 409);
1411
+ return c.json(invalidRequest(validationError), 400);
1412
+ }
1413
+ try {
1414
+ const store = getStore();
1415
+ const dbMatches = requestedDbMatches(store, parsed.db_path);
1416
+ if (dbMatches == null) return c.json(invalidRequest("db_path must be a non-empty string"), 400);
1417
+ if (!dbMatches) return c.json(viewerDbMismatch(), 409);
1418
+ const identityMatches = requestedIdentityMatches(parsed.identity_target);
1419
+ if (identityMatches === "unsupported") return c.json(viewerContractUnsupported(), 409);
1420
+ if (identityMatches == null) return c.json(invalidRequest("identity_target is invalid"), 400);
1421
+ if (!identityMatches) return c.json(viewerIdentityMismatch(), 409);
1422
+ if (!store.hasCurrentIdentity()) return c.json(viewerIdentityMismatch(), 409);
1423
+ const outcome = dispatchLedger(store, parsed);
1424
+ if (typeof outcome === "string") return c.json(invalidRequest(outcome), 400);
1425
+ if (outcome.ok) return c.json(outcome);
1426
+ return c.json(outcome, ledgerFailureStatus(outcome.reason));
1427
+ } catch {
1428
+ return c.json({ error: {
1429
+ code: "ledger_failed",
1430
+ message: "prompt-pack ledger operation failed"
1431
+ } }, 500);
1432
+ }
1433
+ });
1434
+ return app;
1435
+ }
1436
+ //#endregion
1054
1437
  //#region src/routes/raw-events.ts
1055
1438
  /**
1056
1439
  * Raw events routes — GET & POST /api/raw-events, GET /api/raw-events/status,
@@ -2127,7 +2510,7 @@ function recipientInviteOnboardingPreview(store, body, invitationId) {
2127
2510
  invitationId,
2128
2511
  identityId: targetIdentityId,
2129
2512
  ...binding
2130
- });
2513
+ }, { addDeviceTeamEligibility: "prospective_device" });
2131
2514
  }
2132
2515
  function recipientInviteReviewedIntent(store, kind, targetId, preview) {
2133
2516
  if (kind === "team_member") {
@@ -2213,6 +2596,7 @@ async function peerSupportsSyncRequirements(store, deviceId, requirements) {
2213
2596
  headers: {
2214
2597
  ...buildAuthHeaders({
2215
2598
  deviceId: localDeviceId,
2599
+ dbPath: store.dbPath,
2216
2600
  method: "GET",
2217
2601
  url,
2218
2602
  bodyBytes: Buffer.alloc(0),
@@ -2549,10 +2933,14 @@ async function executeProjectShareProvisioning(store, operationId) {
2549
2933
  supportsReassignScope: (deviceId) => peerSupportsReassignScope(store, deviceId),
2550
2934
  refreshAuthorization: async (expectedGroupId) => {
2551
2935
  if (expectedGroupId !== groupId) throw new Error("operation_scope_mismatch");
2552
- if ((await refreshConfiguredScopeMembershipCache(store.db, config, { keysDir: syncKeysDir() })).groups.find((item) => item.groupId === groupId)?.status !== "refreshed") throw new Error("authorization_refresh_failed");
2936
+ if ((await refreshConfiguredScopeMembershipCache(store.db, config, {
2937
+ keysDir: syncKeysDir(),
2938
+ dbPath: store.dbPath
2939
+ })).groups.find((item) => item.groupId === groupId)?.status !== "refreshed") throw new Error("authorization_refresh_failed");
2553
2940
  },
2554
2941
  runInitialSync: (recipientDeviceId) => runSyncPass(store.db, recipientDeviceId, {
2555
2942
  keysDir: syncKeysDir(),
2943
+ dbPath: store.dbPath,
2556
2944
  scanner: store.scanner,
2557
2945
  refreshAuthorization: true
2558
2946
  })
@@ -2809,6 +3197,30 @@ async function advancePendingProjectShares(store, options = {}) {
2809
3197
  }
2810
3198
  return result;
2811
3199
  }
3200
+ function coordinatorEnrollmentFailureCode(error) {
3201
+ const message = (error instanceof Error ? error.message : String(error)).trim();
3202
+ const httpStatus = message.match(/\((\d{3})(?::|\))/u)?.[1];
3203
+ if (httpStatus) return `http_${httpStatus}`;
3204
+ if (new Set([
3205
+ "coordinator_consumed_team_invite_invalid",
3206
+ "coordinator_device_list_malformed",
3207
+ "coordinator_group_id_invalid",
3208
+ "coordinator_id_invalid",
3209
+ "coordinator_invite_group_mismatch",
3210
+ "coordinator_invite_list_malformed",
3211
+ "reconciliation_time_invalid"
3212
+ ]).has(message)) return message;
3213
+ if (/timeout|timed out|abort/iu.test(message)) return "request_timeout";
3214
+ if (/fetch failed|network|econn|enotfound/iu.test(message)) return "network_error";
3215
+ return "unexpected_error";
3216
+ }
3217
+ function coordinatorEnrollmentFailure(groupId, stage, error) {
3218
+ return {
3219
+ groupId: /^[A-Za-z0-9._-]{1,64}$/u.test(groupId) ? groupId : `group_${createHash("sha256").update(groupId).digest("hex").slice(0, 12)}`,
3220
+ stage,
3221
+ code: coordinatorEnrollmentFailureCode(error)
3222
+ };
3223
+ }
2812
3224
  async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
2813
3225
  const config = options.config ?? readCoordinatorSyncConfig();
2814
3226
  const remoteUrl = config.syncCoordinatorUrl.trim();
@@ -2817,6 +3229,7 @@ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
2817
3229
  skipped: true,
2818
3230
  groupsProcessed: 0,
2819
3231
  failedGroups: 0,
3232
+ failures: [],
2820
3233
  devicesAdded: 0,
2821
3234
  membershipsAdded: 0,
2822
3235
  identitiesAdded: 0,
@@ -2830,14 +3243,15 @@ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
2830
3243
  skipped: false,
2831
3244
  groupsProcessed: 0,
2832
3245
  failedGroups: 0,
3246
+ failures: [],
2833
3247
  devicesAdded: 0,
2834
3248
  membershipsAdded: 0,
2835
3249
  identitiesAdded: 0,
2836
3250
  unchanged: 0,
2837
3251
  issues: 0
2838
3252
  };
2839
- for (const groupId of config.syncCoordinatorGroups) try {
2840
- const [enrollments, consumedTeamInvites] = await Promise.all([listDevices({
3253
+ for (const groupId of config.syncCoordinatorGroups) {
3254
+ const [enrollmentsResult, consumedTeamInvitesResult] = await Promise.allSettled([listDevices({
2841
3255
  groupId,
2842
3256
  remoteUrl,
2843
3257
  adminSecret
@@ -2846,21 +3260,30 @@ async function reconcileConfiguredCoordinatorEnrollment(store, options = {}) {
2846
3260
  remoteUrl,
2847
3261
  adminSecret
2848
3262
  })]);
2849
- const result = reconcileSnapshot({
2850
- coordinatorId: buildBaseUrl(remoteUrl),
2851
- groupId,
2852
- enrollments,
2853
- consumedTeamInvites,
2854
- localDeviceId: store.deviceId
2855
- });
2856
- total.groupsProcessed += 1;
2857
- total.devicesAdded += result.devicesAdded;
2858
- total.membershipsAdded += result.membershipsAdded;
2859
- total.identitiesAdded += result.identitiesAdded;
2860
- total.unchanged += result.unchanged;
2861
- total.issues += result.issues.length;
2862
- } catch {
2863
- total.failedGroups += 1;
3263
+ if (enrollmentsResult.status === "rejected") total.failures.push(coordinatorEnrollmentFailure(groupId, "list_devices", enrollmentsResult.reason));
3264
+ if (consumedTeamInvitesResult.status === "rejected") total.failures.push(coordinatorEnrollmentFailure(groupId, "list_consumed_team_invites", consumedTeamInvitesResult.reason));
3265
+ if (enrollmentsResult.status === "rejected" || consumedTeamInvitesResult.status === "rejected") {
3266
+ total.failedGroups += 1;
3267
+ continue;
3268
+ }
3269
+ try {
3270
+ const result = reconcileSnapshot({
3271
+ coordinatorId: buildBaseUrl(remoteUrl),
3272
+ groupId,
3273
+ enrollments: enrollmentsResult.value,
3274
+ consumedTeamInvites: consumedTeamInvitesResult.value,
3275
+ localDeviceId: store.deviceId
3276
+ });
3277
+ total.groupsProcessed += 1;
3278
+ total.devicesAdded += result.devicesAdded;
3279
+ total.membershipsAdded += result.membershipsAdded;
3280
+ total.identitiesAdded += result.identitiesAdded;
3281
+ total.unchanged += result.unchanged;
3282
+ total.issues += result.issues.length;
3283
+ } catch (error) {
3284
+ total.failedGroups += 1;
3285
+ total.failures.push(coordinatorEnrollmentFailure(groupId, "reconcile_snapshot", error));
3286
+ }
2864
3287
  }
2865
3288
  return total;
2866
3289
  }
@@ -3057,7 +3480,10 @@ function createRecipientPolicyReconcilerEffects(store, options = {}) {
3057
3480
  },
3058
3481
  refresh: async ({ scopeId }) => {
3059
3482
  const boundary = target(scopeId);
3060
- if ((await refreshConfiguredScopeMembershipCache(store.db, config, { keysDir: syncKeysDir() })).groups.find((item) => item.groupId === boundary.groupId)?.status !== "refreshed") throw new Error("recipient_policy_effect_failed");
3483
+ if ((await refreshConfiguredScopeMembershipCache(store.db, config, {
3484
+ keysDir: syncKeysDir(),
3485
+ dbPath: store.dbPath
3486
+ })).groups.find((item) => item.groupId === boundary.groupId)?.status !== "refreshed") throw new Error("recipient_policy_effect_failed");
3061
3487
  await refreshAuthorizedCoordinatorPeerTrust(store, config);
3062
3488
  }
3063
3489
  };
@@ -3382,6 +3808,7 @@ async function authorizeBootstrapGrantRequest(store, request, body) {
3382
3808
  const [status, signedPayload] = await requestJson("GET", lookupUrl, {
3383
3809
  headers: buildAuthHeaders({
3384
3810
  deviceId: localDeviceId,
3811
+ dbPath: store.dbPath,
3385
3812
  method: "GET",
3386
3813
  url: lookupUrl,
3387
3814
  bodyBytes: Buffer.alloc(0),
@@ -3916,6 +4343,8 @@ function mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer
3916
4343
  last_seen_at: row.last_seen_at,
3917
4344
  last_sync_at: row.last_sync_at,
3918
4345
  last_error: showDiag ? row.last_error : null,
4346
+ runtime_version: row.runtime_version ?? null,
4347
+ runtime_version_observed_at: row.runtime_version_observed_at ?? null,
3919
4348
  has_error: Boolean(row.last_error),
3920
4349
  claimed_local_actor: Boolean(row.claimed_local_actor),
3921
4350
  claimed_local_actor_scope: claimedLocalActorScopeStatus(store, row),
@@ -4329,6 +4758,7 @@ function readViewerBinding(dbPath) {
4329
4758
  var PEERS_QUERY = `
4330
4759
  SELECT p.peer_device_id, p.name, p.pinned_fingerprint, p.addresses_json,
4331
4760
  p.last_seen_at, p.last_sync_at, p.last_error,
4761
+ p.runtime_version, p.runtime_version_observed_at,
4332
4762
  p.projects_include_json, p.projects_exclude_json, p.claimed_local_actor,
4333
4763
  p.actor_id, p.discovered_via_coordinator_id, p.discovered_via_group_id,
4334
4764
  a.display_name AS actor_display_name
@@ -4408,7 +4838,10 @@ function syncProtocolRoutes(getStore, opts = {}) {
4408
4838
  const limited = rateLimitedResponse(c, auth.deviceId, true);
4409
4839
  if (limited) return limited;
4410
4840
  try {
4411
- if (c.req.header(SYNC_AUTHORIZATION_REFRESH_HEADER) === "1" && supportsSyncFeature(c.req.header(SYNC_FEATURES_HEADER), "reassign_scope")) await refreshConfiguredScopeMembershipCache(store.db, void 0, { keysDir: syncKeysDir() });
4841
+ if (c.req.header(SYNC_AUTHORIZATION_REFRESH_HEADER) === "1" && supportsSyncFeature(c.req.header(SYNC_FEATURES_HEADER), "reassign_scope")) await refreshConfiguredScopeMembershipCache(store.db, void 0, {
4842
+ keysDir: syncKeysDir(),
4843
+ dbPath: store.dbPath
4844
+ });
4412
4845
  const [deviceId, fingerprint] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
4413
4846
  const syncReset = getSyncResetState(store.db);
4414
4847
  const authorizedScopes = isScopedSyncCapability(negotiatedSyncCapability(c)) ? listAuthorizedScopesForPeer(store.db, {
@@ -4419,6 +4852,7 @@ function syncProtocolRoutes(getStore, opts = {}) {
4419
4852
  device_id: deviceId,
4420
4853
  protocol_version: SYNC_PROTOCOL_VERSION,
4421
4854
  fingerprint,
4855
+ runtime_version: VERSION$1,
4422
4856
  sync_reset: addSyncScopeToBoundary(syncReset, null),
4423
4857
  sync_capability: LOCAL_SYNC_CAPABILITY,
4424
4858
  sync_features: LOCAL_SYNC_FEATURES
@@ -4988,6 +5422,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4988
5422
  for (const peerId of peerIds) {
4989
5423
  const result = await runSyncPass(store.db, peerId, {
4990
5424
  keysDir: syncKeysDir(),
5425
+ dbPath: store.dbPath,
4991
5426
  scanner: store.scanner
4992
5427
  });
4993
5428
  items.push({
@@ -7247,6 +7682,27 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
7247
7682
  return app;
7248
7683
  }
7249
7684
  //#endregion
7685
+ //#region src/routes/update-status.ts
7686
+ function updateStatusRoutes(options = {}) {
7687
+ const getUpdateStatus$1 = options.getUpdateStatus ?? getUpdateStatus;
7688
+ const app = new Hono();
7689
+ app.get("/api/update-status", async (c) => {
7690
+ try {
7691
+ const status = await getUpdateStatus$1({
7692
+ currentVersion: VERSION$1,
7693
+ installKind: detectInstallKind({
7694
+ entryPath: process.argv[1] ?? "",
7695
+ env: process.env
7696
+ })
7697
+ });
7698
+ return c.json(status);
7699
+ } catch {
7700
+ return c.json({ error: "Update status unavailable." }, 503);
7701
+ }
7702
+ });
7703
+ return app;
7704
+ }
7705
+ //#endregion
7250
7706
  //#region src/index.ts
7251
7707
  /**
7252
7708
  * @codemem/server — HTTP server (viewer, sync, API).
@@ -7277,8 +7733,10 @@ function createApp(opts) {
7277
7733
  const app = new Hono();
7278
7734
  app.use("*", preflightHandler());
7279
7735
  app.use("*", originGuard());
7736
+ app.route("/", healthRoutes(storeFactory));
7280
7737
  app.route("/", statsRoutes(storeFactory));
7281
7738
  app.route("/", memoryRoutes(storeFactory));
7739
+ app.route("/", packTransportRoutes(storeFactory));
7282
7740
  app.route("/", observerStatusRoutes({
7283
7741
  getStore: storeFactory,
7284
7742
  getSweeper: () => sweeper,
@@ -7287,6 +7745,7 @@ function createApp(opts) {
7287
7745
  app.route("/", configRoutes({ getSweeper: () => sweeper }));
7288
7746
  app.route("/", rawEventsRoutes(storeFactory, sweeper));
7289
7747
  app.route("/", syncRoutes(storeFactory, getSyncRuntimeStatus));
7748
+ app.route("/", updateStatusRoutes({ getUpdateStatus: opts?.getUpdateStatus }));
7290
7749
  const staticRoot = process.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? ".", "../static");
7291
7750
  app.use("/assets/*", async (c, next) => {
7292
7751
  c.header("Cache-Control", "no-cache");