@codemem/server 0.40.2 → 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.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +483 -24
- package/dist/index.js.map +1 -1
- package/dist/routes/health.d.ts +6 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/pack.d.ts +6 -0
- package/dist/routes/pack.d.ts.map +1 -0
- package/dist/routes/sync.d.ts +7 -0
- package/dist/routes/sync.d.ts.map +1 -1
- package/dist/routes/update-status.d.ts +7 -0
- package/dist/routes/update-status.d.ts.map +1 -0
- package/package.json +2 -2
- package/static/app.js +34 -34
- package/static/app.js.br +0 -0
- package/static/app.js.gz +0 -0
- package/static/index.html +53 -0
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, {
|
|
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)
|
|
2840
|
-
const [
|
|
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
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
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, {
|
|
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, {
|
|
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");
|