@codemem/server 0.33.0-alpha.3 → 0.34.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 +146 -6
- package/dist/index.js.map +1 -1
- package/dist/routes/stats.d.ts.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +485 -533
- package/static/index.html +340 -447
- package/static/themes.css +143 -0
- package/static/tokens.css +156 -0
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_CAPABILITY_HEADER, 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, formatHostPort, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeAddress, 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 { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_CAPABILITY_HEADER, 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, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeAddress, 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";
|
|
@@ -1528,6 +1528,57 @@ function totalUsageEvents(rows) {
|
|
|
1528
1528
|
});
|
|
1529
1529
|
}
|
|
1530
1530
|
/**
|
|
1531
|
+
* Short-lived cache for the computed /api/usage payload.
|
|
1532
|
+
*
|
|
1533
|
+
* The health dot polls /api/usage on every 5s refresh regardless of the
|
|
1534
|
+
* active tab, and the computation scans the full usage_events table to apply
|
|
1535
|
+
* scope visibility before aggregating. The resulting token totals are
|
|
1536
|
+
* cumulative dashboard figures, so a few seconds of staleness is acceptable
|
|
1537
|
+
* in exchange for collapsing the repeated full-table scan on the polling
|
|
1538
|
+
* loop. Keyed by db path + scope identity + project filter so distinct
|
|
1539
|
+
* stores/projects never share an entry.
|
|
1540
|
+
*/
|
|
1541
|
+
var usagePayloadCache = /* @__PURE__ */ new Map();
|
|
1542
|
+
var USAGE_PAYLOAD_CACHE_MS = 1e4;
|
|
1543
|
+
/**
|
|
1544
|
+
* Cheap fingerprint of the state that drives scope visibility
|
|
1545
|
+
* (`scope_memberships` / `replication_scopes`). Folding this into the usage
|
|
1546
|
+
* cache key ensures a membership/scope status change — e.g. a revocation —
|
|
1547
|
+
* invalidates any cached payload immediately instead of letting a device keep
|
|
1548
|
+
* reading a scope it can no longer see for up to the TTL. These tables are
|
|
1549
|
+
* small, so the aggregate is negligible next to the usage_events scan the
|
|
1550
|
+
* cache exists to avoid. Fails safe: any error forces a unique value so the
|
|
1551
|
+
* request bypasses the cache and recomputes visibility from scratch.
|
|
1552
|
+
*/
|
|
1553
|
+
function scopeVisibilityGeneration(store) {
|
|
1554
|
+
try {
|
|
1555
|
+
const row = store.db.prepare(`SELECT
|
|
1556
|
+
(SELECT COUNT(*) FROM scope_memberships) AS m_count,
|
|
1557
|
+
(SELECT COUNT(*) FROM scope_memberships WHERE status = 'active') AS m_active,
|
|
1558
|
+
(SELECT COALESCE(MAX(updated_at), '') FROM scope_memberships) AS m_updated,
|
|
1559
|
+
(SELECT COALESCE(MAX(membership_epoch), 0) FROM scope_memberships) AS m_epoch,
|
|
1560
|
+
(SELECT COUNT(*) FROM replication_scopes) AS s_count,
|
|
1561
|
+
(SELECT COUNT(*) FROM replication_scopes WHERE status = 'active') AS s_active,
|
|
1562
|
+
(SELECT COALESCE(MAX(updated_at), '') FROM replication_scopes) AS s_updated`).get();
|
|
1563
|
+
if (!row) return "no-scope-state";
|
|
1564
|
+
return [
|
|
1565
|
+
row.m_count,
|
|
1566
|
+
row.m_active,
|
|
1567
|
+
row.m_updated,
|
|
1568
|
+
row.m_epoch,
|
|
1569
|
+
row.s_count,
|
|
1570
|
+
row.s_active,
|
|
1571
|
+
row.s_updated
|
|
1572
|
+
].join(":");
|
|
1573
|
+
} catch {
|
|
1574
|
+
return `bypass-${Date.now()}-${Math.random()}`;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function usageCacheKey(store, projectFilter) {
|
|
1578
|
+
const visibilityGeneration = scopeVisibilityGeneration(store);
|
|
1579
|
+
return `${store.dbPath}|${store.actorId}|${store.deviceId}|${projectFilter ?? ""}|${visibilityGeneration}`;
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1531
1582
|
* Create stats routes. The store factory is called per-request to get a
|
|
1532
1583
|
* fresh connection (matching the Python viewer pattern).
|
|
1533
1584
|
*/
|
|
@@ -1575,6 +1626,10 @@ function statsRoutes(getStore) {
|
|
|
1575
1626
|
const store = getStore();
|
|
1576
1627
|
{
|
|
1577
1628
|
const projectFilter = c.req.query("project") || null;
|
|
1629
|
+
const cacheKey = usageCacheKey(store, projectFilter);
|
|
1630
|
+
const nowMs = Date.now();
|
|
1631
|
+
const cached = usagePayloadCache.get(cacheKey);
|
|
1632
|
+
if (cached && nowMs < cached.expiresAtMs) return c.json(cached.payload);
|
|
1578
1633
|
const loadUsageRows = (project) => {
|
|
1579
1634
|
return (project ? store.db.prepare(`SELECT usage_events.id, usage_events.session_id, usage_events.event,
|
|
1580
1635
|
usage_events.tokens_read, usage_events.tokens_written, usage_events.tokens_saved,
|
|
@@ -1602,7 +1657,7 @@ function statsRoutes(getStore) {
|
|
|
1602
1657
|
...row,
|
|
1603
1658
|
metadata_json: sanitizePackUsageMetadata(row.metadata_json, recentVisibility)
|
|
1604
1659
|
}));
|
|
1605
|
-
|
|
1660
|
+
const payload = {
|
|
1606
1661
|
project: projectFilter,
|
|
1607
1662
|
events: projectFilter ? eventsFiltered : eventsGlobal,
|
|
1608
1663
|
totals: projectFilter ? totalsFiltered : totalsGlobal,
|
|
@@ -1611,7 +1666,12 @@ function statsRoutes(getStore) {
|
|
|
1611
1666
|
events_filtered: eventsFiltered,
|
|
1612
1667
|
totals_filtered: totalsFiltered,
|
|
1613
1668
|
recent_packs: recentPacks
|
|
1669
|
+
};
|
|
1670
|
+
usagePayloadCache.set(cacheKey, {
|
|
1671
|
+
payload,
|
|
1672
|
+
expiresAtMs: Date.now() + USAGE_PAYLOAD_CACHE_MS
|
|
1614
1673
|
});
|
|
1674
|
+
return c.json(payload);
|
|
1615
1675
|
}
|
|
1616
1676
|
});
|
|
1617
1677
|
return app;
|
|
@@ -2616,6 +2676,7 @@ function mapSyncAttemptRow(row, showDiag, addresses) {
|
|
|
2616
2676
|
};
|
|
2617
2677
|
}
|
|
2618
2678
|
var SYNC_SCOPE_REJECTION_WINDOW_SECONDS = 1440 * 60;
|
|
2679
|
+
var CLEANUP_DIAGNOSTICS_MAX_ROWS = 250;
|
|
2619
2680
|
function recentScopeRejectionsByPeer(store) {
|
|
2620
2681
|
const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_SCOPE_REJECTION_WINDOW_SECONDS * 1e3)).toISOString();
|
|
2621
2682
|
const summaries = summarizeInboundScopeRejections(store.db, { sinceIso: cutoff });
|
|
@@ -2627,6 +2688,48 @@ function recentScopeRejectionsByPeer(store) {
|
|
|
2627
2688
|
}
|
|
2628
2689
|
return map;
|
|
2629
2690
|
}
|
|
2691
|
+
function cleanupDiagnostics(store, localDeviceId, showDiag) {
|
|
2692
|
+
const opRows = store.db.prepare(`SELECT
|
|
2693
|
+
SUM(CASE WHEN device_id = ? THEN 1 ELSE 0 END) AS source_authored,
|
|
2694
|
+
SUM(CASE WHEN device_id != ? THEN 1 ELSE 0 END) AS applied,
|
|
2695
|
+
MAX(created_at) AS latest_at
|
|
2696
|
+
FROM replication_ops
|
|
2697
|
+
WHERE op_type = ?`).get(localDeviceId ?? "", localDeviceId ?? "", ACCESS_CLEANUP_OP_TYPE);
|
|
2698
|
+
const stale = localDeviceId && showDiag ? diagnoseStalePeerReceivedRows(store.db, {
|
|
2699
|
+
localDeviceId,
|
|
2700
|
+
maxRows: CLEANUP_DIAGNOSTICS_MAX_ROWS
|
|
2701
|
+
}) : {
|
|
2702
|
+
ambiguous: [],
|
|
2703
|
+
checked: 0,
|
|
2704
|
+
retained: 0,
|
|
2705
|
+
would_delete: 0,
|
|
2706
|
+
would_delete_memory_ids: []
|
|
2707
|
+
};
|
|
2708
|
+
const ambiguousByReason = {};
|
|
2709
|
+
for (const item of stale.ambiguous) ambiguousByReason[item.reason] = (ambiguousByReason[item.reason] ?? 0) + 1;
|
|
2710
|
+
const sourceAuthored = Number(opRows?.source_authored ?? 0);
|
|
2711
|
+
const applied = Number(opRows?.applied ?? 0);
|
|
2712
|
+
const ambiguous = stale.ambiguous.length;
|
|
2713
|
+
return {
|
|
2714
|
+
state: stale.would_delete ? "cleanup_pending" : ambiguous ? "needs_review" : applied ? "cleanup_applied" : sourceAuthored ? "cleanup_announced" : "clear",
|
|
2715
|
+
access_cleanup_ops: {
|
|
2716
|
+
source_authored: sourceAuthored,
|
|
2717
|
+
applied,
|
|
2718
|
+
latest_at: opRows?.latest_at ?? null
|
|
2719
|
+
},
|
|
2720
|
+
stale_peer_rows: {
|
|
2721
|
+
checked: stale.checked,
|
|
2722
|
+
diagnostic_limit: showDiag ? CLEANUP_DIAGNOSTICS_MAX_ROWS : 0,
|
|
2723
|
+
scan_skipped: !showDiag || !localDeviceId,
|
|
2724
|
+
would_remove: stale.would_delete,
|
|
2725
|
+
ambiguous,
|
|
2726
|
+
ambiguous_by_reason: ambiguousByReason,
|
|
2727
|
+
retained: stale.retained,
|
|
2728
|
+
items: showDiag ? stale.ambiguous.slice(0, 25) : void 0
|
|
2729
|
+
},
|
|
2730
|
+
redacted: !showDiag
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2630
2733
|
function legacySharedReviewSummary(store) {
|
|
2631
2734
|
const rows = legacySharedReviewRows(store);
|
|
2632
2735
|
const memoryCount = rows.length;
|
|
@@ -2653,6 +2756,7 @@ function legacySharedReviewSummary(store) {
|
|
|
2653
2756
|
if (isOwnedBySelf) existing.reassignable_memory_count += 1;
|
|
2654
2757
|
else existing.peer_owned_memory_count += 1;
|
|
2655
2758
|
if (rowUpdatedAt && (!existing.last_updated_at || rowUpdatedAt > existing.last_updated_at)) existing.last_updated_at = rowUpdatedAt;
|
|
2759
|
+
if (existing.memory_samples.length < LEGACY_SHARED_REVIEW_SAMPLE_LIMIT) existing.memory_samples.push(legacySharedReviewMemorySample(row, isOwnedBySelf));
|
|
2656
2760
|
continue;
|
|
2657
2761
|
}
|
|
2658
2762
|
groupsByIdentity.set(identity.value, {
|
|
@@ -2663,6 +2767,7 @@ function legacySharedReviewSummary(store) {
|
|
|
2663
2767
|
reassignable_memory_count: isOwnedBySelf ? 1 : 0,
|
|
2664
2768
|
peer_owned_memory_count: isOwnedBySelf ? 0 : 1,
|
|
2665
2769
|
last_updated_at: rowUpdatedAt,
|
|
2770
|
+
memory_samples: [legacySharedReviewMemorySample(row, isOwnedBySelf)],
|
|
2666
2771
|
suggested_scope_id: suggestedScope ?? null,
|
|
2667
2772
|
suggestion_reason: suggestedScope ? candidate?.suggestion_reason ?? "Existing project mapping can be reviewed as a destination, but legacy data is not promoted automatically." : null
|
|
2668
2773
|
});
|
|
@@ -2684,13 +2789,38 @@ function legacySharedReviewSummary(store) {
|
|
|
2684
2789
|
target_scopes: targetScopes
|
|
2685
2790
|
};
|
|
2686
2791
|
}
|
|
2792
|
+
var LEGACY_SHARED_REVIEW_SAMPLE_LIMIT = 3;
|
|
2793
|
+
var LEGACY_SHARED_REVIEW_BODY_PREVIEW_SQL_LIMIT = 360;
|
|
2794
|
+
function previewText(value, limit = 180) {
|
|
2795
|
+
const normalized = value?.replace(/\s+/g, " ").trim();
|
|
2796
|
+
if (!normalized) return null;
|
|
2797
|
+
return normalized.length > limit ? `${normalized.slice(0, limit - 1)}…` : normalized;
|
|
2798
|
+
}
|
|
2799
|
+
function legacySharedReviewMemorySample(row, isOwnedBySelf) {
|
|
2800
|
+
return {
|
|
2801
|
+
id: row.id,
|
|
2802
|
+
kind: row.kind,
|
|
2803
|
+
title: previewText(row.title, 120) ?? `Memory ${row.id}`,
|
|
2804
|
+
body_preview: previewText(row.body_text),
|
|
2805
|
+
created_at: row.created_at ?? null,
|
|
2806
|
+
updated_at: row.updated_at ?? null,
|
|
2807
|
+
ownership: isOwnedBySelf ? "local" : "peer",
|
|
2808
|
+
project: row.project ?? null,
|
|
2809
|
+
cwd: row.cwd ?? null,
|
|
2810
|
+
git_remote: row.git_remote ?? null
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2687
2813
|
function legacySharedReviewRows(store) {
|
|
2688
2814
|
return store.db.prepare(`SELECT m.id,
|
|
2689
2815
|
m.rev,
|
|
2690
2816
|
m.active,
|
|
2691
2817
|
m.deleted_at,
|
|
2818
|
+
m.created_at,
|
|
2692
2819
|
m.updated_at,
|
|
2693
2820
|
m.scope_id,
|
|
2821
|
+
m.kind,
|
|
2822
|
+
m.title,
|
|
2823
|
+
substr(m.body_text, 1, ?) AS body_text,
|
|
2694
2824
|
s.project,
|
|
2695
2825
|
s.cwd,
|
|
2696
2826
|
s.git_remote,
|
|
@@ -2703,7 +2833,8 @@ function legacySharedReviewRows(store) {
|
|
|
2703
2833
|
LEFT JOIN sessions s ON s.id = m.session_id
|
|
2704
2834
|
WHERE m.scope_id = ?
|
|
2705
2835
|
AND m.active = 1
|
|
2706
|
-
AND m.deleted_at IS NULL
|
|
2836
|
+
AND m.deleted_at IS NULL
|
|
2837
|
+
ORDER BY COALESCE(m.updated_at, m.created_at, '') DESC, m.id DESC`).all(LEGACY_SHARED_REVIEW_BODY_PREVIEW_SQL_LIMIT, LEGACY_SHARED_REVIEW_SCOPE_ID);
|
|
2707
2838
|
}
|
|
2708
2839
|
function legacySharedReviewConfirmationToken(input) {
|
|
2709
2840
|
const ownedBySelf = input.store.buildOwnershipPredicate();
|
|
@@ -2834,7 +2965,7 @@ function legacySharedReviewReassignmentPreview(store, input) {
|
|
|
2834
2965
|
scope_id: input.scopeId,
|
|
2835
2966
|
skipped_memory_count: rows.length - reassignableRows.length,
|
|
2836
2967
|
target_scope_label: targetScope.label || targetScope.scope_id,
|
|
2837
|
-
warning: "This
|
|
2968
|
+
warning: "This device can reassign only locally owned memories. Peer-owned copies must be fixed on their source device. Online compatible peers should converge after syncing, but offline devices, backups, copied databases, malicious peers, or old versions may retain old copies.",
|
|
2838
2969
|
workspace_identity: input.workspaceIdentity
|
|
2839
2970
|
};
|
|
2840
2971
|
}
|
|
@@ -3291,6 +3422,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3291
3422
|
include: config.syncProjectsInclude,
|
|
3292
3423
|
exclude: config.syncProjectsExclude
|
|
3293
3424
|
},
|
|
3425
|
+
cleanup_diagnostics: cleanupDiagnostics(store, deviceRow?.device_id ?? null, showDiag),
|
|
3294
3426
|
redacted: !showDiag
|
|
3295
3427
|
};
|
|
3296
3428
|
if (showDiag) {
|
|
@@ -3697,7 +3829,11 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3697
3829
|
guardrail_warnings: analysis.warnings
|
|
3698
3830
|
}, 409);
|
|
3699
3831
|
}
|
|
3700
|
-
const
|
|
3832
|
+
const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
3833
|
+
const mapping = upsertProjectScopeSettingsMapping(store.db, {
|
|
3834
|
+
deviceId,
|
|
3835
|
+
...mappingInput
|
|
3836
|
+
});
|
|
3701
3837
|
return c.json({
|
|
3702
3838
|
ok: true,
|
|
3703
3839
|
mapping,
|
|
@@ -3736,7 +3872,11 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3736
3872
|
guardrail_warnings: analyses.flatMap((analysis) => analysis.warnings)
|
|
3737
3873
|
}, 409);
|
|
3738
3874
|
}
|
|
3739
|
-
const
|
|
3875
|
+
const [deviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
3876
|
+
const saveMappings = store.db.transaction(() => mappingInputs.map((mappingInput) => upsertProjectScopeSettingsMapping(store.db, {
|
|
3877
|
+
deviceId,
|
|
3878
|
+
...mappingInput
|
|
3879
|
+
})));
|
|
3740
3880
|
return c.json({
|
|
3741
3881
|
ok: true,
|
|
3742
3882
|
mappings: saveMappings(),
|