shieldcortex 3.0.3 → 3.1.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.
Files changed (135) hide show
  1. package/README.md +5 -2
  2. package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
  3. package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
  4. package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
  5. package/dashboard/.next/standalone/dashboard/.next/required-server-files.json +4 -4
  6. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
  7. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
  8. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +2 -2
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +3 -3
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +3 -3
  26. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  27. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +2 -2
  28. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  29. package/dashboard/.next/standalone/dashboard/.next/server/app/page/react-loadable-manifest.json +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  31. package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
  32. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
  33. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  34. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  35. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  36. package/dashboard/.next/standalone/dashboard/.next/static/chunks/0a69eb25d08447ee.js +1 -0
  37. package/dashboard/.next/standalone/dashboard/.next/static/chunks/9232a2d99b47b21f.js +3 -0
  38. package/dashboard/.next/standalone/dashboard/.next/static/chunks/97537d3db46c8467.css +3 -0
  39. package/dashboard/.next/standalone/dashboard/.next/static/chunks/aa6e9b8a52353969.js +9 -0
  40. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  41. package/dashboard/.next/standalone/{node_modules/@img/sharp-linux-x64 → dashboard/node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  42. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/README.md +2 -2
  43. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/glib-2.0/include/glibconfig.h +8 -9
  44. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 → sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib} +0 -0
  45. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  46. package/dashboard/.next/standalone/dashboard/server.js +1 -1
  47. package/dashboard/.next/standalone/{dashboard/node_modules/@img/sharp-linux-x64 → node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  48. package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  49. package/dist/api/routes/admin.d.ts +12 -0
  50. package/dist/api/routes/admin.js +502 -0
  51. package/dist/api/routes/graph.d.ts +4 -0
  52. package/dist/api/routes/graph.js +333 -0
  53. package/dist/api/routes/incidents.d.ts +2 -0
  54. package/dist/api/routes/incidents.js +32 -0
  55. package/dist/api/routes/memories.d.ts +4 -0
  56. package/dist/api/routes/memories.js +659 -0
  57. package/dist/api/routes/recall.d.ts +4 -0
  58. package/dist/api/routes/recall.js +36 -0
  59. package/dist/api/routes/system.d.ts +9 -0
  60. package/dist/api/routes/system.js +266 -0
  61. package/dist/api/visualization-server.js +31 -1913
  62. package/dist/cloud/cli.d.ts +1 -0
  63. package/dist/cloud/cli.js +40 -0
  64. package/dist/cloud/config.d.ts +10 -0
  65. package/dist/cloud/config.js +54 -0
  66. package/dist/cloud/graph-sync.d.ts +45 -0
  67. package/dist/cloud/graph-sync.js +257 -0
  68. package/dist/cloud/memory-sync.d.ts +36 -0
  69. package/dist/cloud/memory-sync.js +183 -0
  70. package/dist/cloud/sync-queue.d.ts +24 -0
  71. package/dist/cloud/sync-queue.js +126 -7
  72. package/dist/database/init.js +24 -0
  73. package/dist/graph/backfill.js +3 -5
  74. package/dist/graph/resolve.d.ts +10 -0
  75. package/dist/graph/resolve.js +63 -1
  76. package/dist/index.d.ts +2 -0
  77. package/dist/index.js +61 -4
  78. package/dist/memory/search.d.ts +37 -0
  79. package/dist/memory/search.js +143 -0
  80. package/dist/memory/store.js +47 -171
  81. package/dist/memory/types.d.ts +2 -0
  82. package/dist/service/install.d.ts +1 -0
  83. package/dist/service/install.js +43 -1
  84. package/dist/tools/recall.d.ts +1 -1
  85. package/hooks/openclaw/cortex-memory/handler.ts +5 -141
  86. package/hooks/openclaw/cortex-memory/runtime.mjs +129 -0
  87. package/package.json +8 -4
  88. package/plugins/openclaw/dist/index.js +5 -39
  89. package/scripts/run-jest.mjs +25 -1
  90. package/dashboard/.next/standalone/dashboard/.next/static/chunks/be6970da20a17c0b.js +0 -9
  91. package/dashboard/.next/standalone/dashboard/.next/static/chunks/e63d2228780629dd.css +0 -3
  92. package/dashboard/.next/standalone/dashboard/.next/static/chunks/f69fd1c5e71fbbfd.js +0 -1
  93. package/dashboard/.next/standalone/dashboard/.next/static/chunks/fa5217550a8ab9a6.js +0 -3
  94. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  95. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  96. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  97. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  98. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  99. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  100. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  101. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  102. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  103. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsc.js +0 -133818
  104. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsserver.js +0 -659
  105. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  106. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  107. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  108. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  109. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  110. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  111. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  112. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  113. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  114. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  115. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  116. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  117. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsc.js +0 -8
  118. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserver.js +0 -8
  119. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  120. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typesMap.json +0 -497
  121. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typescript.js +0 -200276
  122. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typingsInstaller.js +0 -8
  123. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/watchGuard.js +0 -53
  124. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  125. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  126. package/dashboard/.next/standalone/dashboard/node_modules/typescript/package.json +0 -120
  127. package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  128. package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  129. package/scripts/start-dashboard.sh +0 -41
  130. package/scripts/stop-dashboard.sh +0 -21
  131. /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_buildManifest.js +0 -0
  132. /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_clientMiddlewareManifest.json +0 -0
  133. /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_ssgManifest.js +0 -0
  134. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/index.js +0 -0
  135. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/versions.json +0 -0
@@ -1 +1,2 @@
1
1
  export declare function handleCloudConfig(args: string[]): void;
2
+ export declare function handleCloudCommand(args: string[]): Promise<void>;
package/dist/cloud/cli.js CHANGED
@@ -1,4 +1,9 @@
1
1
  import { getCloudConfig, setCloudConfig, getDefenceMode, setDefenceMode, getVerifyConfig, setVerifyConfig, getOpenClawAutoMemory, setOpenClawAutoMemory, } from './config.js';
2
+ import { syncAllGraphToCloud } from './graph-sync.js';
3
+ import { syncAllMemoriesToCloud } from './memory-sync.js';
4
+ import { isFeatureEnabled } from '../license/gate.js';
5
+ import { initDatabase } from '../database/init.js';
6
+ import { reconcileSyncQueue } from './sync-queue.js';
2
7
  const VALID_MODES = ['strict', 'balanced', 'permissive'];
3
8
  const VALID_VERIFY_MODES = ['advisory', 'enforce'];
4
9
  export function handleCloudConfig(args) {
@@ -128,3 +133,38 @@ export function handleCloudConfig(args) {
128
133
  console.log(' --verify-timeout <ms> Set verify timeout in ms (1000-30000)');
129
134
  }
130
135
  }
136
+ export async function handleCloudCommand(args) {
137
+ const action = args[0];
138
+ if (action === 'sync' && args.includes('--full')) {
139
+ const config = getCloudConfig();
140
+ if (!config.cloudEnabled || !config.cloudApiKey) {
141
+ console.error('Cloud sync is not configured. Set an API key and enable cloud sync first.');
142
+ process.exit(1);
143
+ }
144
+ if (!isFeatureEnabled('cloud_sync')) {
145
+ console.error('Cloud memory sync requires a Team or higher licence.');
146
+ process.exit(1);
147
+ }
148
+ initDatabase();
149
+ console.log('Syncing local memories and graph to ShieldCortex Cloud...');
150
+ const memoryResult = await syncAllMemoriesToCloud();
151
+ const graphResult = await syncAllGraphToCloud();
152
+ let reconciled = 0;
153
+ if (memoryResult.failed === 0 && graphResult.failedBatches === 0) {
154
+ reconciled = reconcileSyncQueue({
155
+ kinds: ['memory', 'graph'],
156
+ statuses: ['pending', 'failed'],
157
+ maxCreatedAt: new Date().toISOString(),
158
+ }).removed;
159
+ }
160
+ console.log(`Finished. ${memoryResult.synced}/${memoryResult.total} memories synced` +
161
+ `${memoryResult.failed > 0 ? `, ${memoryResult.failed} queued for retry` : ''}.`);
162
+ console.log(`Graph replica: ${graphResult.entities} entities, ${graphResult.triples} relationships, ${graphResult.memoryEntities} memory links` +
163
+ `${graphResult.failedBatches > 0 ? `, ${graphResult.failedBatches} batch${graphResult.failedBatches === 1 ? '' : 'es'} queued for retry` : ''}.`);
164
+ if (reconciled > 0) {
165
+ console.log(`Reconciled ${reconciled} stale sync queue entr${reconciled === 1 ? 'y' : 'ies'}.`);
166
+ }
167
+ return;
168
+ }
169
+ console.log('Usage: shieldcortex cloud sync --full');
170
+ }
@@ -3,12 +3,22 @@ export interface CloudConfig {
3
3
  cloudBaseUrl: string;
4
4
  cloudEnabled: boolean;
5
5
  }
6
+ export interface CloudSyncControls {
7
+ projectMode: 'all' | 'include' | 'exclude';
8
+ projects: string[];
9
+ contentMode: 'full' | 'metadata';
10
+ excludeSensitive: boolean;
11
+ }
6
12
  /** Returns true if config file tampering was detected. */
7
13
  export declare function isConfigTampered(): boolean;
8
14
  export declare function getCloudConfig(): CloudConfig;
9
15
  export declare function setCloudConfig(updates: Partial<CloudConfig>): void;
10
16
  /** Reset the in-memory cache (useful for testing) */
11
17
  export declare function clearCloudConfigCache(): void;
18
+ export declare function getCloudSyncControls(): CloudSyncControls;
19
+ export declare function setCloudSyncControls(updates: Partial<CloudSyncControls>): void;
20
+ export declare function shouldSyncProject(project: string | null | undefined, controls?: CloudSyncControls): boolean;
21
+ export declare function isSensitiveLevel(level: string | null | undefined): boolean;
12
22
  export declare function readRawConfig(): Record<string, unknown>;
13
23
  export declare function getTrustedSkills(): string[];
14
24
  export declare function addTrustedSkill(path: string): void;
@@ -7,6 +7,12 @@ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
7
7
  const SIG_FILE = join(CONFIG_DIR, '.config-sig');
8
8
  const INTEGRITY_KEY_FILE = join(CONFIG_DIR, '.integrity-key');
9
9
  const DEFAULT_BASE_URL = 'https://api.shieldcortex.ai';
10
+ const DEFAULT_SYNC_CONTROLS = {
11
+ projectMode: 'all',
12
+ projects: [],
13
+ contentMode: 'full',
14
+ excludeSensitive: false,
15
+ };
10
16
  // Cache to avoid repeated file reads
11
17
  let cachedConfig = null;
12
18
  // ── Config Integrity (HMAC) ──────────────────────────────
@@ -114,6 +120,54 @@ export function setCloudConfig(updates) {
114
120
  export function clearCloudConfigCache() {
115
121
  cachedConfig = null;
116
122
  }
123
+ function normalizeProjectList(value) {
124
+ if (!Array.isArray(value))
125
+ return [];
126
+ return [...new Set(value
127
+ .filter((entry) => typeof entry === 'string')
128
+ .map((entry) => entry.trim())
129
+ .filter(Boolean))].sort((a, b) => a.localeCompare(b));
130
+ }
131
+ export function getCloudSyncControls() {
132
+ const raw = readRawConfig();
133
+ const projectMode = raw.cloudSyncProjectMode;
134
+ const contentMode = raw.cloudSyncContentMode;
135
+ return {
136
+ projectMode: projectMode === 'include' || projectMode === 'exclude'
137
+ ? projectMode
138
+ : DEFAULT_SYNC_CONTROLS.projectMode,
139
+ projects: normalizeProjectList(raw.cloudSyncProjects),
140
+ contentMode: contentMode === 'metadata' ? 'metadata' : DEFAULT_SYNC_CONTROLS.contentMode,
141
+ excludeSensitive: typeof raw.cloudSyncExcludeSensitive === 'boolean'
142
+ ? raw.cloudSyncExcludeSensitive
143
+ : DEFAULT_SYNC_CONTROLS.excludeSensitive,
144
+ };
145
+ }
146
+ export function setCloudSyncControls(updates) {
147
+ const raw = readRawConfig();
148
+ if (updates.projectMode !== undefined)
149
+ raw.cloudSyncProjectMode = updates.projectMode;
150
+ if (updates.projects !== undefined)
151
+ raw.cloudSyncProjects = normalizeProjectList(updates.projects);
152
+ if (updates.contentMode !== undefined)
153
+ raw.cloudSyncContentMode = updates.contentMode;
154
+ if (updates.excludeSensitive !== undefined)
155
+ raw.cloudSyncExcludeSensitive = updates.excludeSensitive;
156
+ writeRawConfig(raw);
157
+ }
158
+ export function shouldSyncProject(project, controls = getCloudSyncControls()) {
159
+ const normalized = (project ?? '').trim();
160
+ if (controls.projectMode === 'all')
161
+ return true;
162
+ const included = controls.projects.includes(normalized);
163
+ return controls.projectMode === 'include' ? included : !included;
164
+ }
165
+ export function isSensitiveLevel(level) {
166
+ if (!level)
167
+ return false;
168
+ const normalized = level.trim().toUpperCase();
169
+ return normalized.length > 0 && normalized !== 'PUBLIC' && normalized !== 'INTERNAL';
170
+ }
117
171
  // ── Trusted Skills ──────────────────────────────────────
118
172
  export function readRawConfig() {
119
173
  try {
@@ -0,0 +1,45 @@
1
+ import type { Memory } from '../memory/types.js';
2
+ export interface SyncedGraphEntityRecord {
3
+ external_id: string;
4
+ local_id: number;
5
+ name: string;
6
+ type: string;
7
+ aliases: string[];
8
+ first_seen: string | null;
9
+ memory_count: number;
10
+ }
11
+ export interface SyncedGraphTripleRecord {
12
+ external_id: string;
13
+ local_id: number;
14
+ subject_external_id: string;
15
+ predicate: string;
16
+ object_external_id: string;
17
+ source_memory_external_id: string | null;
18
+ confidence: number | null;
19
+ created_at: string | null;
20
+ }
21
+ export interface SyncedGraphMemoryEntityRecord {
22
+ memory_external_id: string;
23
+ entity_external_id: string;
24
+ role: string;
25
+ }
26
+ export interface GraphSyncEnvelope {
27
+ device: {
28
+ device_id: string;
29
+ device_name: string;
30
+ platform: string;
31
+ };
32
+ entities: SyncedGraphEntityRecord[];
33
+ triples: SyncedGraphTripleRecord[];
34
+ memory_entities: SyncedGraphMemoryEntityRecord[];
35
+ prune_memory_external_ids?: string[];
36
+ }
37
+ export declare function syncGraphForMemoryToCloud(memoryId: number): void;
38
+ export declare function syncGraphDeleteForMemoryToCloud(memory: Memory): void;
39
+ export declare function syncAllGraphToCloud(): Promise<{
40
+ entities: number;
41
+ triples: number;
42
+ memoryEntities: number;
43
+ syncedBatches: number;
44
+ failedBatches: number;
45
+ }>;
@@ -0,0 +1,257 @@
1
+ import { getDatabase } from '../database/init.js';
2
+ import { getCloudConfig, getCloudSyncControls, getDeviceId, getDeviceName, isSensitiveLevel, shouldSyncProject, updateLastSyncAt, } from './config.js';
3
+ import { enqueueFailedGraphSync } from './sync-queue.js';
4
+ function safeJsonParse(value, fallback) {
5
+ if (!value)
6
+ return fallback;
7
+ try {
8
+ return JSON.parse(value);
9
+ }
10
+ catch {
11
+ return fallback;
12
+ }
13
+ }
14
+ function entityExternalId(id) {
15
+ return `entity:${id}`;
16
+ }
17
+ function tripleExternalId(id) {
18
+ return `triple:${id}`;
19
+ }
20
+ function getAllowedMemoryExternalIds() {
21
+ const db = getDatabase();
22
+ const rows = db.prepare('SELECT uuid, project, sensitivity_level FROM memories').all();
23
+ return buildAllowedMemoryExternalIdSet(rows);
24
+ }
25
+ function buildAllowedMemoryExternalIdSet(rows) {
26
+ const controls = getCloudSyncControls();
27
+ return new Set(rows
28
+ .filter((row) => {
29
+ if (!shouldSyncProject(row.project, controls))
30
+ return false;
31
+ if (controls.excludeSensitive && isSensitiveLevel(row.sensitivity_level))
32
+ return false;
33
+ return true;
34
+ })
35
+ .map((row) => row.uuid));
36
+ }
37
+ function buildEnvelope(entities, triples, memoryEntities, pruneMemoryExternalIds = []) {
38
+ return {
39
+ device: {
40
+ device_id: getDeviceId(),
41
+ device_name: getDeviceName(),
42
+ platform: `${process.platform}/${process.arch}`,
43
+ },
44
+ entities,
45
+ triples,
46
+ memory_entities: memoryEntities,
47
+ ...(pruneMemoryExternalIds.length > 0 ? { prune_memory_external_ids: pruneMemoryExternalIds } : {}),
48
+ };
49
+ }
50
+ async function postGraphEnvelope(envelope) {
51
+ const config = getCloudConfig();
52
+ if (!config.cloudEnabled ||
53
+ !config.cloudApiKey ||
54
+ (envelope.entities.length === 0 &&
55
+ envelope.triples.length === 0 &&
56
+ envelope.memory_entities.length === 0 &&
57
+ (!envelope.prune_memory_external_ids || envelope.prune_memory_external_ids.length === 0))) {
58
+ return false;
59
+ }
60
+ const controller = new AbortController();
61
+ const timeoutId = setTimeout(() => controller.abort(), 15_000);
62
+ try {
63
+ const res = await fetch(`${config.cloudBaseUrl}/v1/sync/graph`, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ Authorization: `Bearer ${config.cloudApiKey}`,
68
+ },
69
+ body: JSON.stringify(envelope),
70
+ signal: controller.signal,
71
+ });
72
+ if (!res.ok) {
73
+ return false;
74
+ }
75
+ updateLastSyncAt();
76
+ return true;
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ finally {
82
+ clearTimeout(timeoutId);
83
+ }
84
+ }
85
+ function mapEntityRow(row) {
86
+ return {
87
+ external_id: entityExternalId(row.id),
88
+ local_id: row.id,
89
+ name: row.name,
90
+ type: row.type,
91
+ aliases: safeJsonParse(row.aliases, []),
92
+ first_seen: row.first_seen ? new Date(row.first_seen).toISOString() : null,
93
+ memory_count: Number(row.memory_count ?? 0),
94
+ };
95
+ }
96
+ function mapTripleRow(row) {
97
+ return {
98
+ external_id: tripleExternalId(row.id),
99
+ local_id: row.id,
100
+ subject_external_id: entityExternalId(row.subject_id),
101
+ predicate: row.predicate,
102
+ object_external_id: entityExternalId(row.object_id),
103
+ source_memory_external_id: row.source_memory_uuid ?? null,
104
+ confidence: row.confidence === undefined ? null : Number(row.confidence),
105
+ created_at: row.created_at ? new Date(row.created_at).toISOString() : null,
106
+ };
107
+ }
108
+ function mapMemoryEntityRow(row) {
109
+ return {
110
+ memory_external_id: row.memory_uuid,
111
+ entity_external_id: entityExternalId(row.entity_id),
112
+ role: row.role ?? 'mention',
113
+ };
114
+ }
115
+ function getMemoryScopedEnvelope(memoryId) {
116
+ const db = getDatabase();
117
+ const memory = db.prepare('SELECT id, uuid, project, sensitivity_level FROM memories WHERE id = ?').get(memoryId);
118
+ if (!memory)
119
+ return null;
120
+ const controls = getCloudSyncControls();
121
+ const memoryAllowed = shouldSyncProject(memory.project, controls) &&
122
+ !(controls.excludeSensitive && isSensitiveLevel(memory.sensitivity_level));
123
+ if (!memoryAllowed) {
124
+ return buildEnvelope([], [], [], [memory.uuid]);
125
+ }
126
+ const entityRows = db.prepare(`
127
+ SELECT DISTINCT e.*
128
+ FROM entities e
129
+ JOIN memory_entities me ON me.entity_id = e.id
130
+ WHERE me.memory_id = ?
131
+ ORDER BY e.memory_count DESC, e.name ASC
132
+ `).all(memoryId);
133
+ const tripleRows = db.prepare(`
134
+ SELECT t.*, m.uuid as source_memory_uuid
135
+ FROM triples t
136
+ LEFT JOIN memories m ON m.id = t.source_memory_id
137
+ WHERE t.source_memory_id = ?
138
+ ORDER BY t.id ASC
139
+ `).all(memoryId);
140
+ const memoryEntityRows = db.prepare(`
141
+ SELECT me.entity_id, me.role, m.uuid as memory_uuid
142
+ FROM memory_entities me
143
+ JOIN memories m ON m.id = me.memory_id
144
+ WHERE me.memory_id = ?
145
+ ORDER BY me.entity_id ASC
146
+ `).all(memoryId);
147
+ return buildEnvelope(entityRows.map(mapEntityRow), tripleRows.map(mapTripleRow), memoryEntityRows.map(mapMemoryEntityRow), [memory.uuid]);
148
+ }
149
+ export function syncGraphForMemoryToCloud(memoryId) {
150
+ const config = getCloudConfig();
151
+ if (!config.cloudEnabled || !config.cloudApiKey)
152
+ return;
153
+ const envelope = getMemoryScopedEnvelope(memoryId);
154
+ if (!envelope)
155
+ return;
156
+ postGraphEnvelope(envelope).then((ok) => {
157
+ if (!ok)
158
+ enqueueFailedGraphSync(envelope);
159
+ }).catch(() => {
160
+ enqueueFailedGraphSync(envelope);
161
+ });
162
+ }
163
+ export function syncGraphDeleteForMemoryToCloud(memory) {
164
+ const config = getCloudConfig();
165
+ if (!config.cloudEnabled || !config.cloudApiKey)
166
+ return;
167
+ const envelope = buildEnvelope([], [], [], [memory.uuid]);
168
+ postGraphEnvelope(envelope).then((ok) => {
169
+ if (!ok)
170
+ enqueueFailedGraphSync(envelope);
171
+ }).catch(() => {
172
+ enqueueFailedGraphSync(envelope);
173
+ });
174
+ }
175
+ export async function syncAllGraphToCloud() {
176
+ const db = getDatabase();
177
+ const memoryRows = db.prepare('SELECT uuid, project, sensitivity_level FROM memories ORDER BY id ASC').all();
178
+ const allowedMemoryExternalIds = buildAllowedMemoryExternalIdSet(memoryRows);
179
+ const entityRows = db.prepare('SELECT * FROM entities ORDER BY id ASC').all();
180
+ const tripleRows = db.prepare(`
181
+ SELECT t.*, m.uuid as source_memory_uuid
182
+ FROM triples t
183
+ LEFT JOIN memories m ON m.id = t.source_memory_id
184
+ ORDER BY t.id ASC
185
+ `).all();
186
+ const memoryEntityRows = db.prepare(`
187
+ SELECT me.entity_id, me.role, m.uuid as memory_uuid
188
+ FROM memory_entities me
189
+ JOIN memories m ON m.id = me.memory_id
190
+ ORDER BY me.memory_id ASC, me.entity_id ASC
191
+ `).all();
192
+ const filteredMemoryEntityRows = memoryEntityRows.filter((row) => allowedMemoryExternalIds.has(row.memory_uuid));
193
+ const allowedEntityIds = new Set(filteredMemoryEntityRows.map((row) => row.entity_id));
194
+ const filteredTripleRows = tripleRows.filter((row) => {
195
+ const sourceMemoryUuid = row.source_memory_uuid;
196
+ if (sourceMemoryUuid && !allowedMemoryExternalIds.has(sourceMemoryUuid))
197
+ return false;
198
+ const subjectId = row.subject_id;
199
+ const objectId = row.object_id;
200
+ return allowedEntityIds.has(subjectId) || allowedEntityIds.has(objectId);
201
+ });
202
+ for (const row of filteredTripleRows) {
203
+ allowedEntityIds.add(row.subject_id);
204
+ allowedEntityIds.add(row.object_id);
205
+ }
206
+ const filteredEntityRows = entityRows.filter((row) => allowedEntityIds.has(row.id));
207
+ const entities = filteredEntityRows.map(mapEntityRow);
208
+ const triples = filteredTripleRows.map(mapTripleRow);
209
+ const memoryEntities = filteredMemoryEntityRows.map(mapMemoryEntityRow);
210
+ const activeGraphMemoryExternalIds = new Set([
211
+ ...filteredMemoryEntityRows.map((row) => row.memory_uuid),
212
+ ...filteredTripleRows
213
+ .map((row) => row.source_memory_uuid)
214
+ .filter((value) => Boolean(value)),
215
+ ]);
216
+ const pruneMemoryExternalIds = memoryRows
217
+ .map((row) => row.uuid)
218
+ .filter((uuid) => !activeGraphMemoryExternalIds.has(uuid));
219
+ const batchSize = 200;
220
+ let syncedBatches = 0;
221
+ let failedBatches = 0;
222
+ for (let pruneIndex = 0; pruneIndex < pruneMemoryExternalIds.length; pruneIndex += batchSize) {
223
+ const pruneEnvelope = buildEnvelope([], [], [], pruneMemoryExternalIds.slice(pruneIndex, pruneIndex + batchSize));
224
+ const ok = await postGraphEnvelope(pruneEnvelope);
225
+ if (ok) {
226
+ syncedBatches++;
227
+ }
228
+ else {
229
+ failedBatches++;
230
+ enqueueFailedGraphSync(pruneEnvelope);
231
+ }
232
+ }
233
+ const totalBatches = Math.max(Math.ceil(entities.length / batchSize), Math.ceil(triples.length / batchSize), Math.ceil(memoryEntities.length / batchSize), 1);
234
+ for (let batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
235
+ const envelope = buildEnvelope(entities.slice(batchIndex * batchSize, (batchIndex + 1) * batchSize), triples.slice(batchIndex * batchSize, (batchIndex + 1) * batchSize), memoryEntities.slice(batchIndex * batchSize, (batchIndex + 1) * batchSize));
236
+ const hasPayload = envelope.entities.length > 0 ||
237
+ envelope.triples.length > 0 ||
238
+ envelope.memory_entities.length > 0;
239
+ if (!hasPayload)
240
+ continue;
241
+ const ok = await postGraphEnvelope(envelope);
242
+ if (ok) {
243
+ syncedBatches++;
244
+ }
245
+ else {
246
+ failedBatches++;
247
+ enqueueFailedGraphSync(envelope);
248
+ }
249
+ }
250
+ return {
251
+ entities: entities.length,
252
+ triples: triples.length,
253
+ memoryEntities: memoryEntities.length,
254
+ syncedBatches,
255
+ failedBatches,
256
+ };
257
+ }
@@ -0,0 +1,36 @@
1
+ import type { Memory } from '../memory/types.js';
2
+ export interface SyncedMemoryRecord {
3
+ external_id: string;
4
+ local_id: number;
5
+ type: string;
6
+ category: string;
7
+ title: string;
8
+ content: string;
9
+ project: string | null;
10
+ tags: string[];
11
+ salience: number;
12
+ scope: string;
13
+ transferable: boolean;
14
+ trust_score: number | null;
15
+ sensitivity_level: string | null;
16
+ source: string | null;
17
+ metadata: Record<string, unknown>;
18
+ created_at: string;
19
+ updated_at: string;
20
+ deleted_at?: string | null;
21
+ }
22
+ export interface MemorySyncEnvelope {
23
+ memories: SyncedMemoryRecord[];
24
+ device: {
25
+ device_id: string;
26
+ device_name: string;
27
+ platform: string;
28
+ };
29
+ }
30
+ export declare function syncMemoryUpsertToCloud(memory: Memory): void;
31
+ export declare function syncMemoryDeleteToCloud(memory: Memory): void;
32
+ export declare function syncAllMemoriesToCloud(): Promise<{
33
+ total: number;
34
+ synced: number;
35
+ failed: number;
36
+ }>;
@@ -0,0 +1,183 @@
1
+ import { getDatabase } from '../database/init.js';
2
+ import { getCloudConfig, getCloudSyncControls, getDeviceId, getDeviceName, isSensitiveLevel, shouldSyncProject, updateLastSyncAt, } from './config.js';
3
+ import { enqueueFailedMemorySync } from './sync-queue.js';
4
+ function safeJsonParse(value, fallback) {
5
+ if (!value)
6
+ return fallback;
7
+ try {
8
+ return JSON.parse(value);
9
+ }
10
+ catch {
11
+ return fallback;
12
+ }
13
+ }
14
+ function rowToSyncRecord(row) {
15
+ return {
16
+ external_id: row.uuid,
17
+ local_id: row.id,
18
+ type: row.type,
19
+ category: row.category,
20
+ title: row.title,
21
+ content: row.content,
22
+ project: row.project ?? null,
23
+ tags: safeJsonParse(row.tags, []),
24
+ salience: Number(row.salience ?? 0),
25
+ scope: row.scope ?? 'project',
26
+ transferable: Boolean(row.transferable),
27
+ trust_score: row.trust_score === undefined ? null : Number(row.trust_score),
28
+ sensitivity_level: row.sensitivity_level ?? null,
29
+ source: row.source ?? null,
30
+ metadata: safeJsonParse(row.metadata, {}),
31
+ created_at: new Date(row.created_at ?? Date.now()).toISOString(),
32
+ updated_at: new Date(row.updated_at ?? row.created_at ?? Date.now()).toISOString(),
33
+ deleted_at: null,
34
+ };
35
+ }
36
+ function buildEnvelope(records) {
37
+ return {
38
+ memories: records,
39
+ device: {
40
+ device_id: getDeviceId(),
41
+ device_name: getDeviceName(),
42
+ platform: `${process.platform}/${process.arch}`,
43
+ },
44
+ };
45
+ }
46
+ function shouldSyncRecord(record) {
47
+ const controls = getCloudSyncControls();
48
+ if (!shouldSyncProject(record.project, controls))
49
+ return false;
50
+ if (controls.excludeSensitive && isSensitiveLevel(record.sensitivity_level))
51
+ return false;
52
+ return true;
53
+ }
54
+ function applyContentMode(record) {
55
+ const controls = getCloudSyncControls();
56
+ if (controls.contentMode !== 'metadata')
57
+ return record;
58
+ return {
59
+ ...record,
60
+ title: `[Metadata only] ${record.category}`,
61
+ content: `[ShieldCortex] Memory content redacted by local sync policy.`,
62
+ metadata: {
63
+ ...record.metadata,
64
+ _shieldcortex_sync: {
65
+ ...(typeof record.metadata?._shieldcortex_sync === 'object' && record.metadata._shieldcortex_sync !== null
66
+ ? record.metadata._shieldcortex_sync
67
+ : {}),
68
+ content_redacted: true,
69
+ mode: 'metadata',
70
+ },
71
+ },
72
+ };
73
+ }
74
+ function hydrateMemoryRecord(memoryId) {
75
+ const db = getDatabase();
76
+ const row = db.prepare('SELECT * FROM memories WHERE id = ?').get(memoryId);
77
+ if (!row)
78
+ return null;
79
+ return rowToSyncRecord(row);
80
+ }
81
+ async function postEnvelope(envelope) {
82
+ const config = getCloudConfig();
83
+ if (!config.cloudEnabled || !config.cloudApiKey || envelope.memories.length === 0) {
84
+ return false;
85
+ }
86
+ const controller = new AbortController();
87
+ const timeoutId = setTimeout(() => controller.abort(), 15_000);
88
+ try {
89
+ const res = await fetch(`${config.cloudBaseUrl}/v1/sync/memories`, {
90
+ method: 'POST',
91
+ headers: {
92
+ 'Content-Type': 'application/json',
93
+ Authorization: `Bearer ${config.cloudApiKey}`,
94
+ },
95
+ body: JSON.stringify(envelope),
96
+ signal: controller.signal,
97
+ });
98
+ if (!res.ok) {
99
+ return false;
100
+ }
101
+ updateLastSyncAt();
102
+ return true;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ finally {
108
+ clearTimeout(timeoutId);
109
+ }
110
+ }
111
+ export function syncMemoryUpsertToCloud(memory) {
112
+ const config = getCloudConfig();
113
+ if (!config.cloudEnabled || !config.cloudApiKey)
114
+ return;
115
+ const record = hydrateMemoryRecord(memory.id);
116
+ if (!record)
117
+ return;
118
+ if (!shouldSyncRecord(record))
119
+ return;
120
+ const envelope = buildEnvelope([applyContentMode(record)]);
121
+ postEnvelope(envelope).then((ok) => {
122
+ if (!ok) {
123
+ enqueueFailedMemorySync(envelope.memories[0]);
124
+ }
125
+ }).catch(() => {
126
+ enqueueFailedMemorySync(envelope.memories[0]);
127
+ });
128
+ }
129
+ export function syncMemoryDeleteToCloud(memory) {
130
+ const config = getCloudConfig();
131
+ if (!config.cloudEnabled || !config.cloudApiKey)
132
+ return;
133
+ const record = {
134
+ external_id: memory.uuid,
135
+ local_id: memory.id,
136
+ type: memory.type,
137
+ category: memory.category,
138
+ title: memory.title,
139
+ content: memory.content,
140
+ project: memory.project ?? null,
141
+ tags: memory.tags,
142
+ salience: memory.salience,
143
+ scope: memory.scope,
144
+ transferable: memory.transferable,
145
+ trust_score: null,
146
+ sensitivity_level: null,
147
+ source: null,
148
+ metadata: memory.metadata,
149
+ created_at: memory.createdAt.toISOString(),
150
+ updated_at: memory.updatedAt.toISOString(),
151
+ deleted_at: new Date().toISOString(),
152
+ };
153
+ const envelope = buildEnvelope([record]);
154
+ postEnvelope(envelope).then((ok) => {
155
+ if (!ok) {
156
+ enqueueFailedMemorySync(record);
157
+ }
158
+ }).catch(() => {
159
+ enqueueFailedMemorySync(record);
160
+ });
161
+ }
162
+ export async function syncAllMemoriesToCloud() {
163
+ const db = getDatabase();
164
+ const rows = db.prepare('SELECT * FROM memories ORDER BY id ASC').all();
165
+ const records = rows.map(rowToSyncRecord).filter(shouldSyncRecord).map(applyContentMode);
166
+ let synced = 0;
167
+ let failed = 0;
168
+ const batchSize = 50;
169
+ for (let i = 0; i < records.length; i += batchSize) {
170
+ const batch = records.slice(i, i + batchSize);
171
+ const ok = await postEnvelope(buildEnvelope(batch));
172
+ if (ok) {
173
+ synced += batch.length;
174
+ }
175
+ else {
176
+ failed += batch.length;
177
+ for (const record of batch) {
178
+ enqueueFailedMemorySync(record);
179
+ }
180
+ }
181
+ }
182
+ return { total: records.length, synced, failed };
183
+ }