@codemem/server 0.31.3 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, deleteProjectScopeSettingsMapping, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, 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";
@@ -1707,6 +1707,89 @@ function coordinatorAdminStatusPayload(config = readCoordinatorSyncConfig()) {
1707
1707
  has_groups: hasGroups
1708
1708
  };
1709
1709
  }
1710
+ async function ensureDefaultSpaceForTeam(opts) {
1711
+ const coordinatorId = opts.config.syncCoordinatorUrl || null;
1712
+ if (!coordinatorId) throw new Error("coordinator_not_configured");
1713
+ const scopeId = defaultSpaceScopeIdForGroup(opts.groupId);
1714
+ const label = opts.displayName || opts.groupId;
1715
+ const scope = (await coordinatorListScopesAction({
1716
+ groupId: opts.groupId,
1717
+ includeInactive: false,
1718
+ remoteUrl: opts.config.syncCoordinatorUrl || null,
1719
+ adminSecret: opts.config.syncCoordinatorAdminSecret || null
1720
+ })).find((scope) => scope.scope_id === scopeId) ?? null ?? await coordinatorCreateScopeAction({
1721
+ groupId: opts.groupId,
1722
+ scopeId,
1723
+ label,
1724
+ kind: "team_default",
1725
+ authorityType: "coordinator",
1726
+ coordinatorId,
1727
+ membershipEpoch: 1,
1728
+ status: "active",
1729
+ remoteUrl: opts.config.syncCoordinatorUrl || null,
1730
+ adminSecret: opts.config.syncCoordinatorAdminSecret || null
1731
+ });
1732
+ const [deviceId] = ensureDeviceIdentity(opts.store.db);
1733
+ return {
1734
+ scope,
1735
+ membership: await coordinatorGrantScopeMembershipAction({
1736
+ groupId: opts.groupId,
1737
+ scopeId,
1738
+ deviceId,
1739
+ role: "admin",
1740
+ membershipEpoch: 1,
1741
+ coordinatorId,
1742
+ remoteUrl: opts.config.syncCoordinatorUrl || null,
1743
+ adminSecret: opts.config.syncCoordinatorAdminSecret || null
1744
+ }),
1745
+ preferences: upsertCoordinatorGroupPreference(opts.store.db, {
1746
+ coordinator_id: coordinatorId,
1747
+ group_id: opts.groupId,
1748
+ default_space_scope_id: scopeId,
1749
+ auto_grant_default_space_on_join: true
1750
+ })
1751
+ };
1752
+ }
1753
+ async function maybeGrantDefaultSpaceOnJoin(opts) {
1754
+ const coordinatorId = opts.config.syncCoordinatorUrl || null;
1755
+ if (!coordinatorId) return null;
1756
+ const preferences = getCoordinatorGroupPreference(opts.store.db, coordinatorId, opts.groupId);
1757
+ if (!preferences?.auto_grant_default_space_on_join) return null;
1758
+ const scopeId = preferences.default_space_scope_id?.trim();
1759
+ if (!scopeId) return null;
1760
+ if (scopeId !== defaultSpaceScopeIdForGroup(opts.groupId)) return null;
1761
+ const defaultScope = (await coordinatorListScopesAction({
1762
+ groupId: opts.groupId,
1763
+ includeInactive: false,
1764
+ remoteUrl: opts.config.syncCoordinatorUrl || null,
1765
+ adminSecret: opts.config.syncCoordinatorAdminSecret || null
1766
+ })).find((scope) => scope.scope_id === scopeId) ?? null;
1767
+ if (!defaultScope || defaultScope.kind !== "team_default") return null;
1768
+ return await coordinatorGrantScopeMembershipAction({
1769
+ groupId: opts.groupId,
1770
+ scopeId,
1771
+ deviceId: opts.deviceId,
1772
+ role: "member",
1773
+ membershipEpoch: 1,
1774
+ coordinatorId,
1775
+ remoteUrl: opts.config.syncCoordinatorUrl || null,
1776
+ adminSecret: opts.config.syncCoordinatorAdminSecret || null
1777
+ });
1778
+ }
1779
+ function removeConfiguredCoordinatorGroup(groupId) {
1780
+ const targetGroup = groupId.trim();
1781
+ if (!targetGroup) return [];
1782
+ const config = readCodememConfigFile();
1783
+ const rawGroups = config.sync_coordinator_groups;
1784
+ const groups = Array.isArray(rawGroups) ? rawGroups.map((group) => String(group).trim()).filter(Boolean) : typeof rawGroups === "string" ? rawGroups.split(",").map((group) => group.trim()).filter(Boolean) : typeof config.sync_coordinator_group === "string" && config.sync_coordinator_group.trim() ? [config.sync_coordinator_group.trim()] : [];
1785
+ const nextGroups = groups.filter((group) => group !== targetGroup);
1786
+ if (nextGroups.length === groups.length) return groups;
1787
+ config.sync_coordinator_groups = nextGroups;
1788
+ if (nextGroups.length) config.sync_coordinator_group = nextGroups[0];
1789
+ else delete config.sync_coordinator_group;
1790
+ writeCodememConfigFile(config);
1791
+ return nextGroups;
1792
+ }
1710
1793
  function coordinatorAdminUnavailable(status) {
1711
1794
  if (status.readiness === "not_configured") return {
1712
1795
  body: {
@@ -3907,11 +3990,31 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
3907
3990
  remoteUrl: config.syncCoordinatorUrl || null,
3908
3991
  adminSecret: config.syncCoordinatorAdminSecret || null
3909
3992
  });
3910
- return c.json({
3911
- ok: true,
3912
- group,
3913
- status
3914
- });
3993
+ try {
3994
+ const defaultSpace = await ensureDefaultSpaceForTeam({
3995
+ store: getStore(),
3996
+ config,
3997
+ groupId,
3998
+ displayName: displayName || group.display_name || null
3999
+ });
4000
+ return c.json({
4001
+ ok: true,
4002
+ group,
4003
+ default_space: defaultSpace,
4004
+ status
4005
+ });
4006
+ } catch (setupError) {
4007
+ return c.json({
4008
+ ok: true,
4009
+ group,
4010
+ default_space: null,
4011
+ setup_warning: {
4012
+ step: "default_space",
4013
+ error: setupError instanceof Error ? setupError.message : String(setupError)
4014
+ },
4015
+ status
4016
+ });
4017
+ }
3915
4018
  } catch (error) {
3916
4019
  return c.json({
3917
4020
  error: error instanceof Error ? error.message : String(error),
@@ -3998,10 +4101,13 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
3998
4101
  error: "group_not_found_or_already_archived",
3999
4102
  status
4000
4103
  }, 404);
4104
+ const groups = removeConfiguredCoordinatorGroup(groupId);
4001
4105
  return c.json({
4002
4106
  ok: true,
4003
4107
  group,
4004
- status
4108
+ status: coordinatorAdminStatusPayload(),
4109
+ disconnected_group_id: groupId,
4110
+ groups
4005
4111
  });
4006
4112
  } catch (error) {
4007
4113
  return c.json({
@@ -4470,9 +4576,26 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4470
4576
  error: "join request not found",
4471
4577
  status
4472
4578
  }, 404);
4579
+ let defaultSpaceMembership = null;
4580
+ let setupWarning = null;
4581
+ try {
4582
+ defaultSpaceMembership = await maybeGrantDefaultSpaceOnJoin({
4583
+ store: getStore(),
4584
+ config,
4585
+ groupId: result.group_id,
4586
+ deviceId: result.device_id
4587
+ });
4588
+ } catch (grantError) {
4589
+ setupWarning = {
4590
+ step: "default_space_grant",
4591
+ error: grantError instanceof Error ? grantError.message : String(grantError)
4592
+ };
4593
+ }
4473
4594
  return c.json({
4474
4595
  ok: true,
4475
4596
  request: result,
4597
+ default_space_membership: defaultSpaceMembership,
4598
+ setup_warning: setupWarning,
4476
4599
  status
4477
4600
  });
4478
4601
  } catch (error) {
@@ -4842,6 +4965,8 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4842
4965
  projects_include: null,
4843
4966
  projects_exclude: null,
4844
4967
  auto_seed_scope: true,
4968
+ default_space_scope_id: null,
4969
+ auto_grant_default_space_on_join: false,
4845
4970
  updated_at: null
4846
4971
  },
4847
4972
  status
@@ -4880,6 +5005,12 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4880
5005
  return cleaned.length === 0 ? null : cleaned;
4881
5006
  };
4882
5007
  const autoSeed = typeof body.auto_seed_scope === "boolean" ? body.auto_seed_scope : void 0;
5008
+ const autoGrantDefaultSpace = typeof body.auto_grant_default_space_on_join === "boolean" ? body.auto_grant_default_space_on_join : void 0;
5009
+ const defaultSpaceScopeId = body.default_space_scope_id === void 0 ? void 0 : optionalViewerString(body, "default_space_scope_id");
5010
+ if (autoGrantDefaultSpace === true && !defaultSpaceScopeId) return c.json({
5011
+ error: "default_space_scope_id_required_for_auto_grant",
5012
+ status
5013
+ }, 400);
4883
5014
  const store = getStore();
4884
5015
  try {
4885
5016
  const preferences = upsertCoordinatorGroupPreference(store.db, {
@@ -4887,7 +5018,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
4887
5018
  group_id: groupId,
4888
5019
  projects_include: normalizeList(body.projects_include),
4889
5020
  projects_exclude: normalizeList(body.projects_exclude),
4890
- auto_seed_scope: autoSeed
5021
+ auto_seed_scope: autoSeed,
5022
+ default_space_scope_id: defaultSpaceScopeId,
5023
+ auto_grant_default_space_on_join: autoGrantDefaultSpace
4891
5024
  });
4892
5025
  return c.json({
4893
5026
  preferences,