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.
- package/README.md +5 -2
- package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/required-server-files.json +4 -4
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/page/react-loadable-manifest.json +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/0a69eb25d08447ee.js +1 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/9232a2d99b47b21f.js +3 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/97537d3db46c8467.css +3 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/aa6e9b8a52353969.js +9 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
- package/dashboard/.next/standalone/{node_modules/@img/sharp-linux-x64 → dashboard/node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/README.md +2 -2
- 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
- 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
- package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
- package/dashboard/.next/standalone/dashboard/server.js +1 -1
- package/dashboard/.next/standalone/{dashboard/node_modules/@img/sharp-linux-x64 → node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
- package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
- package/dist/api/routes/admin.d.ts +12 -0
- package/dist/api/routes/admin.js +502 -0
- package/dist/api/routes/graph.d.ts +4 -0
- package/dist/api/routes/graph.js +333 -0
- package/dist/api/routes/incidents.d.ts +2 -0
- package/dist/api/routes/incidents.js +32 -0
- package/dist/api/routes/memories.d.ts +4 -0
- package/dist/api/routes/memories.js +659 -0
- package/dist/api/routes/recall.d.ts +4 -0
- package/dist/api/routes/recall.js +36 -0
- package/dist/api/routes/system.d.ts +9 -0
- package/dist/api/routes/system.js +266 -0
- package/dist/api/visualization-server.js +31 -1913
- package/dist/cloud/cli.d.ts +1 -0
- package/dist/cloud/cli.js +40 -0
- package/dist/cloud/config.d.ts +10 -0
- package/dist/cloud/config.js +54 -0
- package/dist/cloud/graph-sync.d.ts +45 -0
- package/dist/cloud/graph-sync.js +257 -0
- package/dist/cloud/memory-sync.d.ts +36 -0
- package/dist/cloud/memory-sync.js +183 -0
- package/dist/cloud/sync-queue.d.ts +24 -0
- package/dist/cloud/sync-queue.js +126 -7
- package/dist/database/init.js +24 -0
- package/dist/graph/backfill.js +3 -5
- package/dist/graph/resolve.d.ts +10 -0
- package/dist/graph/resolve.js +63 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +61 -4
- package/dist/memory/search.d.ts +37 -0
- package/dist/memory/search.js +143 -0
- package/dist/memory/store.js +47 -171
- package/dist/memory/types.d.ts +2 -0
- package/dist/service/install.d.ts +1 -0
- package/dist/service/install.js +43 -1
- package/dist/tools/recall.d.ts +1 -1
- package/hooks/openclaw/cortex-memory/handler.ts +5 -141
- package/hooks/openclaw/cortex-memory/runtime.mjs +129 -0
- package/package.json +8 -4
- package/plugins/openclaw/dist/index.js +5 -39
- package/scripts/run-jest.mjs +25 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/be6970da20a17c0b.js +0 -9
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/e63d2228780629dd.css +0 -3
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/f69fd1c5e71fbbfd.js +0 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/fa5217550a8ab9a6.js +0 -3
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
- package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsc.js +0 -133818
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsserver.js +0 -659
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_typingsInstaller.js +0 -222
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsc.js +0 -8
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserver.js +0 -8
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserverlibrary.js +0 -21
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typesMap.json +0 -497
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typescript.js +0 -200276
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typingsInstaller.js +0 -8
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/watchGuard.js +0 -53
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
- package/dashboard/.next/standalone/dashboard/node_modules/typescript/package.json +0 -120
- package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
- package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/stop-dashboard.sh +0 -21
- /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{THy6JENQ0c1sq6jQhvIDp → RnvqrTXo_jN8SuMdaNcIj}/_ssgManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/index.js +0 -0
- /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/versions.json +0 -0
package/dist/cloud/cli.d.ts
CHANGED
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
|
+
}
|
package/dist/cloud/config.d.ts
CHANGED
|
@@ -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;
|
package/dist/cloud/config.js
CHANGED
|
@@ -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
|
+
}
|