@codemem/server 0.46.0-alpha.1 → 0.46.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
3
- import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, CodememConfigMutationError, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MEMORY_FILTER_FIELD_TYPES, MEMORY_FILTER_NAMES, MEMORY_KIND_DESCRIPTIONS, MemoryStore, ObserverClient, PROMPT_TRANSPORT_PROTOCOL_RANGE, REMEMBER_MEMORY_KINDS, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, automaticRecallHealth, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, containLegacyTeamSetupCompletionConflict, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateLegacyTeamCompletionAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGetLegacyTeamCompletionAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListLegacyTeamCompletionsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, dedupeOrderedIds, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isAutomaticRecallMeasurement, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, judgeDistillReport, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, memoryFilterValueMatchesType, mergeAddresses, migrateRecipientPolicyIntent, mutateCodememConfigFile, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectMatchesFilter, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordAutomaticRecall, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveProject, resolveProjectRoot, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, storeVectors, summarizeInboundScopeRejections, summaryLikeSqlPredicate, supportsSyncFeature, syncScopeResetRequiredPayload, toJson, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, validateRawEvents, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature } from "@codemem/core";
3
+ import { ACCESS_CLEANUP_OP_TYPE, CodememConfigMutationError, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MEMORY_FILTER_FIELD_TYPES, MEMORY_FILTER_NAMES, MEMORY_KIND_DESCRIPTIONS, MemoryStore, ObserverClient, PROMPT_TRANSPORT_PROTOCOL_RANGE, REMEMBER_MEMORY_KINDS, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, automaticRecallHealth, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildFilterClausesWithContext, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, containLegacyTeamSetupCompletionConflict, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateLegacyTeamCompletionAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGetLegacyTeamCompletionAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListLegacyTeamCompletionsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, dedupeOrderedIds, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrideValues, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isAutomaticRecallMeasurement, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, judgeDistillReport, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, memoryFilterValueMatchesType, mergeAddresses, migrateRecipientPolicyIntent, mutateCodememConfigFile, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectMatchesFilter, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordAutomaticRecall, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveObserverRuntime, resolveProject, resolveProjectRoot, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, storeVectors, summarizeInboundScopeRejections, summaryLikeSqlPredicate, supportsSyncFeature, syncScopeResetRequiredPayload, toJson, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, validateRawEvents, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature } 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";
@@ -284,7 +284,7 @@ var DEFAULTS = {
284
284
  sync_host: "0.0.0.0",
285
285
  sync_port: 7337,
286
286
  sync_interval_s: 120,
287
- sync_mdns: true,
287
+ sync_mdns: false,
288
288
  sync_coordinator_timeout_s: 3,
289
289
  sync_coordinator_presence_ttl_s: 180,
290
290
  raw_events_sweeper_interval_s: 30
@@ -303,14 +303,18 @@ function getEffectiveConfig(configData) {
303
303
  ...configData
304
304
  };
305
305
  for (const key of ["claude_command", "codex_command"]) effective[key] = coerceObserverCommand(effective[key]) ?? DEFAULTS[key];
306
- for (const [key, envVar] of Object.entries(CODEMEM_CONFIG_ENV_OVERRIDES)) {
307
- const val = process.env[envVar];
308
- if (val == null || val === "") continue;
309
- if (key === "claude_command" || key === "codex_command") effective[key] = coerceObserverCommand(val) ?? effective[key];
310
- else effective[key] = val;
311
- }
306
+ Object.assign(effective, getCodememEnvOverrideValues());
312
307
  return effective;
313
308
  }
309
+ function observerRuntimeMetadata(configData) {
310
+ return {
311
+ resolved_observer_runtime: resolveObserverRuntime(configData),
312
+ observer_runtime_by_auth_source: Object.fromEntries([...AUTH_SOURCES].map((source) => [source, resolveObserverRuntime({
313
+ ...configData,
314
+ observer_auth_source: source
315
+ })]))
316
+ };
317
+ }
314
318
  function redactConfigValue(key, value) {
315
319
  if (value == null || !SECRET_CONFIG_KEYS.has(key)) return value;
316
320
  if (Array.isArray(value)) return value.length > 0 ? REDACTED_VALUE : [];
@@ -530,6 +534,7 @@ function viewerConfigSavePayload(saved, opts) {
530
534
  path: savedPath,
531
535
  config: sanitizeConfigForResponse(nextConfig),
532
536
  effective: sanitizeConfigForResponse(afterEffective),
537
+ ...observerRuntimeMetadata(nextConfig),
533
538
  protected_keys: [...PROTECTED_WRITE_KEYS].sort(),
534
539
  effects: {
535
540
  saved_keys: savedChangedKeys,
@@ -563,6 +568,7 @@ function configRoutes(opts = {}) {
563
568
  config: sanitizeConfigForResponse(configData),
564
569
  defaults: DEFAULTS,
565
570
  effective: sanitizeConfigForResponse(effective),
571
+ ...observerRuntimeMetadata(configData),
566
572
  env_overrides: getCodememEnvOverrides(),
567
573
  protected_keys: [...PROTECTED_WRITE_KEYS].sort(),
568
574
  providers: loadProviderOptions()
@@ -1594,7 +1600,7 @@ function memoryRoutes(getStore) {
1594
1600
  }
1595
1601
  const memoryId = parseStrictInteger(typeof body.memory_id === "string" ? body.memory_id : String(body.memory_id ?? ""));
1596
1602
  if (memoryId == null || memoryId <= 0) return c.json({ error: "memory_id must be int" }, 400);
1597
- if (!store.get(memoryId)) return c.json({ error: "memory not found" }, 404);
1603
+ if (!forgetTargetExists(store, memoryId, forgetSafetyFilters(body))) return c.json(...forgetNotFound(forgetSafetyFilters(body)));
1598
1604
  const row = drizzle(store.db, { schema }).select().from(schema.memoryItems).where(eq(schema.memoryItems.id, memoryId)).get();
1599
1605
  if (!row) return c.json({ error: "memory not found" }, 404);
1600
1606
  if (!store.memoryOwnedBySelf(row)) return c.json({ error: "memory not owned by this device" }, 403);
@@ -1612,6 +1618,19 @@ function memoryRoutes(getStore) {
1612
1618
  });
1613
1619
  return app;
1614
1620
  }
1621
+ function forgetNotFound(filters) {
1622
+ return [{ error: Object.keys(filters).length > 0 ? "not_found" : "memory not found" }, 404];
1623
+ }
1624
+ function forgetSafetyFilters(body) {
1625
+ const filters = {};
1626
+ if (typeof body.kind === "string" && body.kind.trim()) filters.kind = body.kind.trim();
1627
+ if (typeof body.project === "string" && body.project.trim()) filters.project = body.project.trim();
1628
+ return filters;
1629
+ }
1630
+ function forgetTargetExists(store, memoryId, filters) {
1631
+ if (Object.keys(filters).length > 0) return store.timeline(null, memoryId, 0, 0, filters).some((row) => row.id === memoryId);
1632
+ return store.get(memoryId) != null;
1633
+ }
1615
1634
  //#endregion
1616
1635
  //#region src/routes/memory-tools.ts
1617
1636
  /**
@@ -2129,6 +2148,15 @@ function buildFailureImpact(latestFailure, queueTotals, authBackoff) {
2129
2148
  if (queueTotals.pending > 0) return `${queueTotals.pending} queued raw events across ${queueTotals.sessions} session(s) are waiting on a successful flush.`;
2130
2149
  return "Failed flush batches are pending retry.";
2131
2150
  }
2151
+ function readCaptureEnabled() {
2152
+ const value = process.env.CODEMEM_RAW_EVENTS?.trim().toLowerCase();
2153
+ if (!value) return null;
2154
+ return ![
2155
+ "0",
2156
+ "false",
2157
+ "off"
2158
+ ].includes(value);
2159
+ }
2132
2160
  function observerStatusRoutes(deps) {
2133
2161
  const app = new Hono();
2134
2162
  app.get("/api/observer-status", (c) => {
@@ -2138,6 +2166,7 @@ function observerStatusRoutes(deps) {
2138
2166
  if (!store || typeof store.rawEventBacklogTotals !== "function") return c.json({
2139
2167
  active: null,
2140
2168
  available_credentials: {},
2169
+ capture_enabled: readCaptureEnabled(),
2141
2170
  latest_failure: null,
2142
2171
  queue: {
2143
2172
  pending: 0,
@@ -2161,6 +2190,7 @@ function observerStatusRoutes(deps) {
2161
2190
  return c.json({
2162
2191
  active,
2163
2192
  available_credentials: availableCredentials,
2193
+ capture_enabled: readCaptureEnabled(),
2164
2194
  latest_failure: failureWithImpact,
2165
2195
  queue: {
2166
2196
  ...queueTotals,
@@ -2240,6 +2270,34 @@ function validateViewerTarget(store, payload, options = {}) {
2240
2270
  if ((options.requireCurrentIdentity || payload.identity_target != null) && !store.hasCurrentIdentity()) return conflict("viewer_identity_mismatch", "viewer identity does not match request");
2241
2271
  return { ok: true };
2242
2272
  }
2273
+ /**
2274
+ * Middleware read of db_path / identity_target from the query string.
2275
+ * Native tools (pi extension) send targets as query params so a pre-handler
2276
+ * guard can reject a foreign viewer without consuming POST bodies. Lenient:
2277
+ * clients that omit targets (UI, MCP, curl) are unaffected.
2278
+ */
2279
+ function viewerTargetQueryGuard(getStore) {
2280
+ return async (c, next) => {
2281
+ const dbPath = c.req.query("db_path");
2282
+ const rawIdentity = c.req.query("identity_target");
2283
+ if (dbPath == null && rawIdentity == null) return next();
2284
+ let identityTarget;
2285
+ if (rawIdentity != null) try {
2286
+ identityTarget = JSON.parse(rawIdentity);
2287
+ } catch {
2288
+ return c.json({ error: {
2289
+ code: "invalid_request",
2290
+ message: "identity_target must be JSON"
2291
+ } }, 400);
2292
+ }
2293
+ const target = validateViewerTarget(getStore(), {
2294
+ db_path: dbPath ?? void 0,
2295
+ identity_target: identityTarget
2296
+ });
2297
+ if (!target.ok) return c.json(target.body, target.status);
2298
+ return next();
2299
+ };
2300
+ }
2243
2301
  //#endregion
2244
2302
  //#region src/routes/pack.ts
2245
2303
  var MAX_LEDGER_PAYLOAD_BYTES = 16384;
@@ -2675,21 +2733,26 @@ function resolveRawEventInboxDirectory(dbPath, homeDir = homedir()) {
2675
2733
  const target = createHash("sha256").update(resolve(dbPath)).digest("hex").slice(0, 24);
2676
2734
  return join(homeDir, ".codemem", INBOX_DIRECTORY_NAME, target);
2677
2735
  }
2678
- function entryContent(request, flushBoundary) {
2736
+ function entryContent(request, flushBoundary, boundary) {
2679
2737
  return {
2680
2738
  version: 1,
2681
2739
  request,
2682
- flush_boundary: flushBoundary
2740
+ flush_boundary: flushBoundary,
2741
+ ...boundary ? { boundary: {
2742
+ source: boundary.source,
2743
+ stream_id: boundary.streamId,
2744
+ ...boundary.id ? { id: boundary.id } : {}
2745
+ } } : {}
2683
2746
  };
2684
2747
  }
2685
- function storedEntry(request, flushBoundary, enqueueOrder) {
2748
+ function storedEntry(request, flushBoundary, boundary, enqueueOrder) {
2686
2749
  return {
2687
- ...entryContent(request, flushBoundary),
2750
+ ...entryContent(request, flushBoundary, boundary),
2688
2751
  enqueue_order: enqueueOrder.toString()
2689
2752
  };
2690
2753
  }
2691
2754
  function entryContentId(entry) {
2692
- return contentId(JSON.stringify(entryContent(entry.request, entry.flushBoundary)));
2755
+ return contentId(JSON.stringify(entryContent(entry.request, entry.flushBoundary, entry.boundary)));
2693
2756
  }
2694
2757
  function inboxFileName(enqueueOrder, id) {
2695
2758
  return `${enqueueOrder.toString().padStart(24, "0")}-${id}.json`;
@@ -2749,10 +2812,17 @@ async function ensurePrivateDirectory(path) {
2749
2812
  function parseStoredEntry(serialized) {
2750
2813
  const parsed = JSON.parse(serialized);
2751
2814
  if (parsed.version !== 1 || typeof parsed.enqueue_order !== "string" || !/^[1-9]\d*$/u.test(parsed.enqueue_order) || parsed.request == null || typeof parsed.request !== "object" || Array.isArray(parsed.request) || typeof parsed.flush_boundary !== "boolean") throw new Error("invalid raw-event inbox entry");
2815
+ const storedBoundary = parsed.boundary;
2816
+ if (storedBoundary !== void 0 && (typeof storedBoundary.source !== "string" || storedBoundary.source.trim() === "" || typeof storedBoundary.stream_id !== "string" || storedBoundary.stream_id.trim() === "" || storedBoundary.id !== void 0 && (typeof storedBoundary.id !== "string" || storedBoundary.id.trim() === ""))) throw new Error("invalid raw-event inbox boundary");
2752
2817
  return {
2753
2818
  entry: {
2754
2819
  request: parsed.request,
2755
- flushBoundary: parsed.flush_boundary
2820
+ flushBoundary: parsed.flush_boundary,
2821
+ ...storedBoundary ? { boundary: {
2822
+ source: storedBoundary.source,
2823
+ streamId: storedBoundary.stream_id,
2824
+ ...storedBoundary.id ? { id: storedBoundary.id } : {}
2825
+ } } : {}
2756
2826
  },
2757
2827
  enqueueOrder: BigInt(parsed.enqueue_order)
2758
2828
  };
@@ -2803,7 +2873,8 @@ var FileRawEventInbox = class {
2803
2873
  const flushBoundary = options.flushBoundary === true;
2804
2874
  const id = entryContentId({
2805
2875
  request,
2806
- flushBoundary
2876
+ flushBoundary,
2877
+ boundary: options.boundary
2807
2878
  });
2808
2879
  await ensurePrivateDirectory(this.directory);
2809
2880
  const entries = await readdir(this.directory, { withFileTypes: true });
@@ -2823,7 +2894,7 @@ var FileRawEventInbox = class {
2823
2894
  throw error;
2824
2895
  }
2825
2896
  const enqueueOrder = await this.allocateEnqueueOrder(entries);
2826
- const serialized = JSON.stringify(storedEntry(request, flushBoundary, enqueueOrder));
2897
+ const serialized = JSON.stringify(storedEntry(request, flushBoundary, options.boundary, enqueueOrder));
2827
2898
  const destination = join(this.directory, inboxFileName(enqueueOrder, id));
2828
2899
  const temporary = join(this.directory, `.${id}.${process.pid}.${randomUUID()}.tmp`);
2829
2900
  try {
@@ -3024,10 +3095,11 @@ function createViewerRawEventInbox(options) {
3024
3095
  onCorruptEntries: options.onCorruptEntries,
3025
3096
  onBacklog: options.onBacklog,
3026
3097
  onBacklogRecovered: options.onBacklogRecovered,
3027
- processEntry: async ({ request, flushBoundary }) => {
3098
+ processEntry: async ({ request, flushBoundary, boundary }) => {
3028
3099
  const result = ingestRawEvents(store, request);
3029
3100
  nudgeRawEventSessions(options.sweeper, result.sessions);
3030
- if (flushBoundary && isClaudeBoundaryEnvelope(request)) await flushRawEventBoundarySessions(options.sweeper, result.sessions);
3101
+ if (boundary) await options.sweeper?.flushBoundary(boundary.streamId, boundary.source);
3102
+ else if (flushBoundary && isClaudeBoundaryEnvelope(request)) await flushRawEventBoundarySessions(options.sweeper, result.sessions);
3031
3103
  }
3032
3104
  });
3033
3105
  return {
@@ -3155,15 +3227,94 @@ async function enqueueRawEventRequest(options) {
3155
3227
  if (!target.ok) return options.c.json(target.body, target.status);
3156
3228
  const validation = validateRawEvents(options.request);
3157
3229
  try {
3158
- await options.inbox.enqueue(validation.request, { flushBoundary: options.flushBoundary });
3230
+ await options.inbox.enqueue(validation.request, options.boundary ? {
3231
+ flushBoundary: options.flushBoundary,
3232
+ boundary: options.boundary
3233
+ } : { flushBoundary: options.flushBoundary });
3159
3234
  } catch (error) {
3160
3235
  return boundedInboxErrorResponse(options.c, error);
3161
3236
  }
3237
+ const accepted = options.acceptedCount ?? validation.received;
3162
3238
  return options.c.json({
3163
- accepted: validation.received,
3164
- queued: validation.received
3239
+ accepted,
3240
+ queued: accepted
3165
3241
  }, 202);
3166
3242
  }
3243
+ function piEventName(payload) {
3244
+ for (const key of [
3245
+ "piEvent",
3246
+ "pi_event",
3247
+ "event",
3248
+ "type"
3249
+ ]) {
3250
+ const value = payload[key];
3251
+ if (typeof value === "string" && value.trim()) return value.trim();
3252
+ }
3253
+ return "";
3254
+ }
3255
+ function piInboxBoundary(payload, envelope, signal) {
3256
+ if (!["session_before_compact", "session_shutdown"].includes(piEventName(payload))) return;
3257
+ const streamId = envelope?.session_stream_id ?? signal?.session_id;
3258
+ if (!streamId) return;
3259
+ const envelopeTimestamp = envelope?.payload.timestamp;
3260
+ const id = typeof envelopeTimestamp === "string" ? envelopeTimestamp : signal?.ts;
3261
+ return {
3262
+ source: "pi",
3263
+ streamId,
3264
+ ...id ? { id } : {}
3265
+ };
3266
+ }
3267
+ async function postPiHookRequest(c, getStore, sweeper, inbox, inboxTarget) {
3268
+ const result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);
3269
+ if (result instanceof Response) return result;
3270
+ const payload = result;
3271
+ try {
3272
+ const target = validateViewerTarget(inbox && inboxTarget ? inboxTarget : getStore(), payload, { requirePairedTargets: true });
3273
+ if (!target.ok) return c.json(target.body, target.status);
3274
+ const untargeted = untargetedPayload(payload);
3275
+ const envelope = buildRawEventEnvelopeFromPiEvent(untargeted);
3276
+ const signal = buildPiFlushSignalFromEvent(untargeted);
3277
+ const boundary = piInboxBoundary(untargeted, envelope, signal);
3278
+ if (envelope === null && signal === null) return c.json({
3279
+ inserted: 0,
3280
+ skipped: 1
3281
+ });
3282
+ if (inbox) return await enqueueRawEventRequest({
3283
+ c,
3284
+ getStore,
3285
+ inbox,
3286
+ inboxTarget,
3287
+ payload,
3288
+ request: envelope ? {
3289
+ ...envelope,
3290
+ source: "pi"
3291
+ } : {
3292
+ source: "pi",
3293
+ session_stream_id: signal?.session_id,
3294
+ events: []
3295
+ },
3296
+ flushBoundary: false,
3297
+ ...boundary ? {
3298
+ boundary,
3299
+ acceptedCount: 1
3300
+ } : {}
3301
+ });
3302
+ if (envelope === null) return c.json({
3303
+ inserted: 0,
3304
+ skipped: 1
3305
+ });
3306
+ const ingestResult = await ingestNormalizedEnvelope(getStore(), sweeper, {
3307
+ ...envelope,
3308
+ source: "pi"
3309
+ });
3310
+ return c.json({
3311
+ inserted: ingestResult.inserted,
3312
+ skipped: ingestResult.skipped
3313
+ });
3314
+ } catch (error) {
3315
+ return boundedIngestErrorResponse(c, error);
3316
+ }
3317
+ }
3167
3318
  async function postRawEventRequest(c, getStore, sweeper, inbox, inboxTarget) {
3168
3319
  const result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);
3169
3320
  if (result instanceof Response) return result;
@@ -3197,6 +3348,81 @@ async function ingestNormalizedEnvelope(store, sweeper, envelope, flushBoundary
3197
3348
  if (flushBoundary && isClaudeBoundaryEnvelope(envelope)) await flushRawEventBoundarySessions(sweeper, result.sessions);
3198
3349
  return result;
3199
3350
  }
3351
+ async function postClaudeHookRequest(c, getStore, sweeper, inbox, inboxTarget, transcriptDiagnostics) {
3352
+ const result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);
3353
+ if (result instanceof Response) return result;
3354
+ const payload = result;
3355
+ try {
3356
+ const target = validateViewerTarget(inbox && inboxTarget ? inboxTarget : getStore(), payload, { requirePairedTargets: true });
3357
+ if (!target.ok) return c.json(target.body, target.status);
3358
+ let transcriptOutcome = null;
3359
+ const envelope = buildRawEventEnvelopeFromHook(untargetedPayload(payload), {
3360
+ transcriptPolicy: {
3361
+ trust: "restricted",
3362
+ approvedRoots: [claudeTranscriptRoot()]
3363
+ },
3364
+ onTranscriptOutcome: (outcome) => {
3365
+ transcriptOutcome = outcome;
3366
+ transcriptDiagnostics.record("claude", outcome);
3367
+ }
3368
+ });
3369
+ if (envelope === null) return c.json(transcriptSkipResponse(transcriptOutcome));
3370
+ const flushBoundary = c.req.header("x-codemem-boundary-flush") === "1";
3371
+ if (inbox) return await enqueueRawEventRequest({
3372
+ c,
3373
+ getStore,
3374
+ inbox,
3375
+ inboxTarget,
3376
+ payload,
3377
+ request: { ...envelope },
3378
+ flushBoundary
3379
+ });
3380
+ const ingestResult = await ingestNormalizedEnvelope(getStore(), sweeper, envelope, flushBoundary);
3381
+ return c.json({
3382
+ inserted: ingestResult.inserted,
3383
+ skipped: ingestResult.skipped
3384
+ });
3385
+ } catch (error) {
3386
+ return boundedIngestErrorResponse(c, error);
3387
+ }
3388
+ }
3389
+ async function postCodexHookRequest(c, getStore, sweeper, inbox, inboxTarget, transcriptDiagnostics) {
3390
+ const result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);
3391
+ if (result instanceof Response) return result;
3392
+ const payload = result;
3393
+ try {
3394
+ const target = validateViewerTarget(inbox && inboxTarget ? inboxTarget : getStore(), payload, { requirePairedTargets: true });
3395
+ if (!target.ok) return c.json(target.body, target.status);
3396
+ let transcriptOutcome = null;
3397
+ const envelope = buildRawEventEnvelopeFromCodexHook(untargetedPayload(payload), {
3398
+ transcriptPolicy: {
3399
+ trust: "restricted",
3400
+ approvedRoots: [codexTranscriptRoot()]
3401
+ },
3402
+ onTranscriptOutcome: (outcome) => {
3403
+ transcriptOutcome = outcome;
3404
+ transcriptDiagnostics.record("codex", outcome);
3405
+ }
3406
+ });
3407
+ if (envelope === null) return c.json(transcriptSkipResponse(transcriptOutcome));
3408
+ if (inbox) return await enqueueRawEventRequest({
3409
+ c,
3410
+ getStore,
3411
+ inbox,
3412
+ inboxTarget,
3413
+ payload,
3414
+ request: { ...envelope },
3415
+ flushBoundary: false
3416
+ });
3417
+ const ingestResult = await ingestNormalizedEnvelope(getStore(), sweeper, envelope);
3418
+ return c.json({
3419
+ inserted: ingestResult.inserted,
3420
+ skipped: ingestResult.skipped
3421
+ });
3422
+ } catch (error) {
3423
+ return boundedIngestErrorResponse(c, error);
3424
+ }
3425
+ }
3200
3426
  function rawEventsRoutes(getStore, sweeper, inbox, inboxTarget) {
3201
3427
  const app = new Hono();
3202
3428
  const transcriptDiagnostics = createTranscriptDiagnostics();
@@ -3248,89 +3474,9 @@ function rawEventsRoutes(getStore, sweeper, inbox, inboxTarget) {
3248
3474
  });
3249
3475
  });
3250
3476
  app.post("/api/raw-events", (c) => postRawEventRequest(c, getStore, sweeper, inbox, inboxTarget));
3251
- app.post("/api/claude-hooks", async (c) => {
3252
- const result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);
3253
- if (result instanceof Response) return result;
3254
- const payload = result;
3255
- try {
3256
- const store = getStore();
3257
- const target = validateViewerTarget(store, payload, { requirePairedTargets: true });
3258
- if (!target.ok) return c.json(target.body, target.status);
3259
- let transcriptOutcome = null;
3260
- const envelope = buildRawEventEnvelopeFromHook(untargetedPayload(payload), {
3261
- transcriptPolicy: {
3262
- trust: "restricted",
3263
- approvedRoots: [claudeTranscriptRoot()]
3264
- },
3265
- onTranscriptOutcome: (outcome) => {
3266
- transcriptOutcome = outcome;
3267
- transcriptDiagnostics.record("claude", outcome);
3268
- }
3269
- });
3270
- if (envelope === null) return c.json(transcriptSkipResponse(transcriptOutcome));
3271
- const ingestResult = await ingestNormalizedEnvelope(store, sweeper, envelope, c.req.header("x-codemem-boundary-flush") === "1");
3272
- return c.json({
3273
- inserted: ingestResult.inserted,
3274
- skipped: ingestResult.skipped
3275
- });
3276
- } catch (err) {
3277
- return boundedIngestErrorResponse(c, err);
3278
- }
3279
- });
3280
- app.post("/api/codex-hooks", async (c) => {
3281
- const result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);
3282
- if (result instanceof Response) return result;
3283
- const payload = result;
3284
- try {
3285
- const store = getStore();
3286
- const target = validateViewerTarget(store, payload, { requirePairedTargets: true });
3287
- if (!target.ok) return c.json(target.body, target.status);
3288
- let transcriptOutcome = null;
3289
- const envelope = buildRawEventEnvelopeFromCodexHook(untargetedPayload(payload), {
3290
- transcriptPolicy: {
3291
- trust: "restricted",
3292
- approvedRoots: [codexTranscriptRoot()]
3293
- },
3294
- onTranscriptOutcome: (outcome) => {
3295
- transcriptOutcome = outcome;
3296
- transcriptDiagnostics.record("codex", outcome);
3297
- }
3298
- });
3299
- if (envelope === null) return c.json(transcriptSkipResponse(transcriptOutcome));
3300
- const ingestResult = await ingestNormalizedEnvelope(store, sweeper, envelope);
3301
- return c.json({
3302
- inserted: ingestResult.inserted,
3303
- skipped: ingestResult.skipped
3304
- });
3305
- } catch (err) {
3306
- return boundedIngestErrorResponse(c, err);
3307
- }
3308
- });
3309
- app.post("/api/pi-hooks", async (c) => {
3310
- const result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);
3311
- if (result instanceof Response) return result;
3312
- const payload = result;
3313
- try {
3314
- const store = getStore();
3315
- const target = validateViewerTarget(store, payload, { requirePairedTargets: true });
3316
- if (!target.ok) return c.json(target.body, target.status);
3317
- const envelope = buildRawEventEnvelopeFromPiEvent(untargetedPayload(payload));
3318
- if (envelope === null) return c.json({
3319
- inserted: 0,
3320
- skipped: 1
3321
- });
3322
- const ingestResult = await ingestNormalizedEnvelope(store, sweeper, {
3323
- ...envelope,
3324
- source: "pi"
3325
- });
3326
- return c.json({
3327
- inserted: ingestResult.inserted,
3328
- skipped: ingestResult.skipped
3329
- });
3330
- } catch (err) {
3331
- return boundedIngestErrorResponse(c, err);
3332
- }
3333
- });
3477
+ app.post("/api/claude-hooks", (c) => postClaudeHookRequest(c, getStore, sweeper, inbox, inboxTarget, transcriptDiagnostics));
3478
+ app.post("/api/codex-hooks", (c) => postCodexHookRequest(c, getStore, sweeper, inbox, inboxTarget, transcriptDiagnostics));
3479
+ app.post("/api/pi-hooks", (c) => postPiHookRequest(c, getStore, sweeper, inbox, inboxTarget));
3334
3480
  return app;
3335
3481
  }
3336
3482
  //#endregion
@@ -4231,6 +4377,40 @@ var PEERS_QUERY$1 = `
4231
4377
  ORDER BY name, peer_device_id
4232
4378
  `;
4233
4379
  //#endregion
4380
+ //#region src/snapshot-source.ts
4381
+ function snapshotGeneration(raw) {
4382
+ if (!raw?.trim()) return null;
4383
+ return Number.parseInt(raw, 10);
4384
+ }
4385
+ function snapshotSourceDevice(requested, localDeviceId) {
4386
+ if (requested !== void 0 && requested !== localDeviceId) throw new Error("retirement_snapshot_source_required");
4387
+ return requested;
4388
+ }
4389
+ function snapshotErrorStatus(message) {
4390
+ if ([
4391
+ "retirement_snapshot_source_required",
4392
+ "missing_generation",
4393
+ "missing_snapshot_id"
4394
+ ].includes(message)) return 400;
4395
+ if (message === "generation_mismatch" || message === "boundary_mismatch") return 409;
4396
+ return null;
4397
+ }
4398
+ function snapshotPageRequest(query, localDeviceId) {
4399
+ const generation = snapshotGeneration(query("generation"));
4400
+ const snapshotId = query("snapshot_id");
4401
+ if (generation === null || !Number.isFinite(generation)) throw new Error("missing_generation");
4402
+ if (!snapshotId) throw new Error("missing_snapshot_id");
4403
+ const rawLimit = Number.parseInt(query("limit") ?? "200", 10);
4404
+ return {
4405
+ generation,
4406
+ snapshotId,
4407
+ baselineCursor: query("baseline_cursor") ?? null,
4408
+ pageToken: query("page_token") ?? null,
4409
+ sourceDeviceId: snapshotSourceDevice(query("source_device_id"), localDeviceId),
4410
+ limit: Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 5e3)) : 200
4411
+ };
4412
+ }
4413
+ //#endregion
4234
4414
  //#region src/routes/team-setup-view.ts
4235
4415
  var enabled = {
4236
4416
  enabled: true,
@@ -6176,6 +6356,26 @@ function tryParsePairingPayload(value) {
6176
6356
  addresses
6177
6357
  };
6178
6358
  }
6359
+ async function inspectPairingInvite(c, next) {
6360
+ const body = await parseViewerJsonBody(c);
6361
+ if (typeof body?.invite !== "string") return c.json({ error: "invite_invalid" }, 400);
6362
+ try {
6363
+ const pairing = tryParsePairingPayload(body.invite.match(/echo\s+['"]([A-Za-z0-9+/=]+)['"]\s*\|\s*base64\s+-d\s*\|\s*codemem/)?.[1]?.trim() || body.invite.trim());
6364
+ if (pairing?.kind === "invalid-pair") return c.json({ error: pairing.error }, 400);
6365
+ if (pairing?.kind !== "pair") {
6366
+ decodeInvitePayload(extractInvitePayload(body.invite));
6367
+ return next();
6368
+ }
6369
+ return c.json({
6370
+ kind: "pair",
6371
+ device_id: pairing.device_id,
6372
+ fingerprint: pairing.fingerprint,
6373
+ addresses: pairing.addresses
6374
+ });
6375
+ } catch (error) {
6376
+ return c.json({ error: safeRecipientInviteError(error) }, 400);
6377
+ }
6378
+ }
6179
6379
  function coordinatorAdminStatusPayload(config = readCoordinatorSyncConfig()) {
6180
6380
  const groups = config.syncCoordinatorGroups;
6181
6381
  const activeGroup = groups[0] ?? null;
@@ -8750,6 +8950,42 @@ function negotiatedSyncCapability(c) {
8750
8950
  * network-accessible. All requests are auth-gated via signature
8751
8951
  * verification so unauthenticated callers are rejected.
8752
8952
  */
8953
+ function serveAuthorizedSnapshot(c, store, peerDeviceId) {
8954
+ try {
8955
+ const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
8956
+ const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
8957
+ const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0, {
8958
+ db: store.db,
8959
+ localDeviceId,
8960
+ negotiatedCapability: negotiatedSyncCapability(c),
8961
+ peerDeviceId
8962
+ });
8963
+ if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY, null), 409);
8964
+ const pageRequest = snapshotPageRequest((name) => c.req.query(name), localDeviceId);
8965
+ const result = loadMemorySnapshotPageForPeer(store.db, {
8966
+ ...pageRequest,
8967
+ peerDeviceId,
8968
+ scopeId: scopeRequest.mode === "scoped" ? scopeRequest.scope_id : void 0
8969
+ });
8970
+ return c.json({
8971
+ source_device_id: pageRequest.sourceDeviceId,
8972
+ scope_id: scopeRequest.scope_id,
8973
+ generation: result.boundary.generation,
8974
+ snapshot_id: result.boundary.snapshot_id,
8975
+ baseline_cursor: result.boundary.baseline_cursor,
8976
+ retained_floor_cursor: result.boundary.retained_floor_cursor,
8977
+ sync_capability: LOCAL_SYNC_CAPABILITY,
8978
+ items: result.items,
8979
+ next_page_token: result.nextPageToken,
8980
+ has_more: result.hasMore
8981
+ });
8982
+ } catch (err) {
8983
+ const message = err instanceof Error ? err.message : "";
8984
+ const status = snapshotErrorStatus(message);
8985
+ if (status) return c.json({ error: message }, status);
8986
+ return c.json({ error: "internal_error" }, 500);
8987
+ }
8988
+ }
8753
8989
  function syncProtocolRoutes(getStore, opts = {}) {
8754
8990
  const app = new Hono();
8755
8991
  const routeRateLimit = opts.routeRateLimit ?? null;
@@ -8916,51 +9152,7 @@ function syncProtocolRoutes(getStore, opts = {}) {
8916
9152
  }
8917
9153
  const limited = rateLimitedResponse(c, auth.deviceId, true);
8918
9154
  if (limited) return limited;
8919
- try {
8920
- const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
8921
- const negotiated = negotiatedSyncCapability(c);
8922
- const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
8923
- const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0, {
8924
- db: store.db,
8925
- localDeviceId,
8926
- negotiatedCapability: negotiated,
8927
- peerDeviceId: auth.deviceId
8928
- });
8929
- if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY, null), 409);
8930
- const rawGeneration = c.req.query("generation");
8931
- const generation = rawGeneration != null && rawGeneration.trim().length > 0 ? Number.parseInt(rawGeneration, 10) : null;
8932
- const snapshotId = c.req.query("snapshot_id") ?? null;
8933
- const baselineCursor = c.req.query("baseline_cursor") ?? null;
8934
- const pageToken = c.req.query("page_token") ?? null;
8935
- const rawLimit = Number.parseInt(c.req.query("limit") ?? "200", 10);
8936
- const limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 5e3)) : 200;
8937
- if (generation == null || !Number.isFinite(generation)) return c.json({ error: "missing_generation" }, 400);
8938
- if (!snapshotId) return c.json({ error: "missing_snapshot_id" }, 400);
8939
- const result = loadMemorySnapshotPageForPeer(store.db, {
8940
- generation,
8941
- snapshotId,
8942
- baselineCursor,
8943
- pageToken,
8944
- limit,
8945
- peerDeviceId: auth.deviceId,
8946
- scopeId: scopeRequest.mode === "scoped" ? scopeRequest.scope_id : void 0
8947
- });
8948
- return c.json({
8949
- scope_id: scopeRequest.scope_id,
8950
- generation: result.boundary.generation,
8951
- snapshot_id: result.boundary.snapshot_id,
8952
- baseline_cursor: result.boundary.baseline_cursor,
8953
- retained_floor_cursor: result.boundary.retained_floor_cursor,
8954
- sync_capability: LOCAL_SYNC_CAPABILITY,
8955
- items: result.items,
8956
- next_page_token: result.nextPageToken,
8957
- has_more: result.hasMore
8958
- });
8959
- } catch (err) {
8960
- const message = err instanceof Error ? err.message : "";
8961
- if (message === "generation_mismatch" || message === "boundary_mismatch") return c.json({ error: message }, 409);
8962
- return c.json({ error: "internal_error" }, 500);
8963
- }
9155
+ return serveAuthorizedSnapshot(c, store, auth.deviceId);
8964
9156
  })();
8965
9157
  });
8966
9158
  app.post("/v1/ops", async (c) => {
@@ -9038,7 +9230,7 @@ function syncProtocolRoutes(getStore, opts = {}) {
9038
9230
  }
9039
9231
  async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9040
9232
  const config = readCoordinatorSyncConfig();
9041
- const hasCoordinatorConfiguration = config.syncCoordinatorUrl.length > 0 || config.syncCoordinatorGroups.length > 0;
9233
+ const hasCoordinatorConfiguration = config.syncCoordinatorUrl.length > 0 || config.syncCoordinatorGroups.length > 0 || config.syncCoordinatorAdminSecret.length > 0;
9042
9234
  const remoteUrl = config.syncCoordinatorUrl.trim();
9043
9235
  const adminSecret = config.syncCoordinatorAdminSecret.trim();
9044
9236
  const groupIds = [...new Set(config.syncCoordinatorGroups.map((groupId) => groupId.trim()))].filter(Boolean);
@@ -9049,7 +9241,7 @@ async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9049
9241
  };
9050
9242
  if (!remoteUrl || !adminSecret || groupIds.length === 0) return {
9051
9243
  availability: "unavailable",
9052
- safeErrorCode: "coordinator_unavailable",
9244
+ safeErrorCode: "coordinator_configuration_required",
9053
9245
  enrollments: []
9054
9246
  };
9055
9247
  if (groupIds.length > 25) return {
@@ -9082,6 +9274,61 @@ async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9082
9274
  };
9083
9275
  }
9084
9276
  }
9277
+ var ORIGIN_DEVICE_NAME_LOOKUP_BATCH_SIZE = 400;
9278
+ function originDeviceNameKey(value) {
9279
+ return value.normalize("NFKC").toLowerCase().toUpperCase().toLowerCase();
9280
+ }
9281
+ function originDeviceDisplayName(value, deviceId) {
9282
+ if (typeof value !== "string") return null;
9283
+ try {
9284
+ const displayName = normalizeHumanPresentationName(value, "display_name");
9285
+ let normalizedDeviceId = deviceId;
9286
+ try {
9287
+ normalizedDeviceId = normalizeHumanPresentationName(deviceId, "device_id");
9288
+ } catch {}
9289
+ return originDeviceNameKey(displayName) === originDeviceNameKey(normalizedDeviceId) ? null : displayName;
9290
+ } catch {
9291
+ return null;
9292
+ }
9293
+ }
9294
+ function resolveOriginDeviceDisplayNames(store, projects) {
9295
+ const deviceIds = [...new Set(projects.flatMap((project) => project.origin_devices.map((device) => device.device_id)))];
9296
+ const names = /* @__PURE__ */ new Map();
9297
+ for (let offset = 0; offset < deviceIds.length; offset += ORIGIN_DEVICE_NAME_LOOKUP_BATCH_SIZE) {
9298
+ const batch = deviceIds.slice(offset, offset + ORIGIN_DEVICE_NAME_LOOKUP_BATCH_SIZE);
9299
+ const placeholders = batch.map(() => "?").join(", ");
9300
+ const peers = store.db.prepare(`SELECT peer_device_id AS device_id, name AS display_name
9301
+ FROM sync_peers
9302
+ WHERE peer_device_id IN (${placeholders})
9303
+ AND TRIM(COALESCE(pinned_fingerprint, '')) <> ''`).all(...batch);
9304
+ for (const peer of peers) {
9305
+ const displayName = originDeviceDisplayName(peer.display_name, peer.device_id);
9306
+ if (displayName) names.set(peer.device_id, displayName);
9307
+ }
9308
+ const devices = store.db.prepare(`SELECT device_id, display_name
9309
+ FROM identity_devices
9310
+ WHERE device_id IN (${placeholders}) AND status = 'active'`).all(...batch);
9311
+ for (const device of devices) {
9312
+ const displayName = originDeviceDisplayName(device.display_name, device.device_id);
9313
+ if (displayName) names.set(device.device_id, displayName);
9314
+ }
9315
+ }
9316
+ return names;
9317
+ }
9318
+ function serializeProjectScopeInventory(store, inventory, sharingByProject) {
9319
+ const originDeviceNames = resolveOriginDeviceDisplayNames(store, inventory.projects);
9320
+ return {
9321
+ ...inventory,
9322
+ projects: inventory.projects.map((project) => ({
9323
+ ...project,
9324
+ origin_devices: project.origin_devices.map(({ device_id }) => ({
9325
+ device_id,
9326
+ display_name: originDeviceNames.get(device_id) ?? null
9327
+ })),
9328
+ sharing: sharingByProject.get(project.workspace_identity) ?? []
9329
+ }))
9330
+ };
9331
+ }
9085
9332
  /**
9086
9333
  * Viewer-facing sync management routes (/api/sync/*).
9087
9334
  *
@@ -9090,7 +9337,7 @@ async function loadConfiguredDeviceIdentityCoordinatorEvidence() {
9090
9337
  * local viewer.
9091
9338
  */
9092
9339
  function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9093
- const app = new Hono();
9340
+ const app = new Hono().post("/api/sync/invites/inspect", inspectPairingInvite);
9094
9341
  const readCoordinatorConfig = options.readCoordinatorConfig ?? readCoordinatorSyncConfig;
9095
9342
  const renameCoordinatorGroup = options.renameCoordinatorGroup ?? coordinatorRenameGroupAction;
9096
9343
  const loadCoordinatorEvidence = options.loadDeviceIdentityCoordinatorEvidence ?? loadConfiguredDeviceIdentityCoordinatorEvidence;
@@ -9735,13 +9982,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus, options = {}) {
9735
9982
  });
9736
9983
  sharingByProject.set(item.canonical_project_identity, current);
9737
9984
  }
9738
- return c.json({
9739
- ...inventory,
9740
- projects: inventory.projects.map((project) => ({
9741
- ...project,
9742
- sharing: sharingByProject.get(project.workspace_identity) ?? []
9743
- }))
9744
- });
9985
+ return c.json(serializeProjectScopeInventory(store, inventory, sharingByProject));
9745
9986
  });
9746
9987
  app.post("/api/sync/projects/reassign-project", async (c) => {
9747
9988
  const store = getStore();
@@ -11940,6 +12181,9 @@ function createApp(opts) {
11940
12181
  const app = new Hono();
11941
12182
  app.use("*", preflightHandler());
11942
12183
  app.use("*", originGuard({ unsafeGetPathPrefixes: [TEAM_SETUP_ROUTE_PREFIX] }));
12184
+ const viewerTargetGuard = viewerTargetQueryGuard(storeFactory);
12185
+ app.use("/api/memories/*", viewerTargetGuard);
12186
+ app.use("/api/memory", viewerTargetGuard);
11943
12187
  app.route("/", healthRoutes(storeFactory));
11944
12188
  app.route("/", diagnosticsRoutes(storeFactory));
11945
12189
  app.route("/", statsRoutes(storeFactory));