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
@@ -5,6 +5,8 @@
5
5
  * Supports both audit metadata sync and quarantine content sync payloads.
6
6
  * Uses SQLite sync_queue table with exponential backoff.
7
7
  */
8
+ import type { SyncedMemoryRecord } from './memory-sync.js';
9
+ import type { GraphSyncEnvelope } from './graph-sync.js';
8
10
  export interface SyncEntry {
9
11
  source_type: string;
10
12
  source_identifier: string;
@@ -37,6 +39,15 @@ export interface QueueStats {
37
39
  pending: number;
38
40
  failed: number;
39
41
  synced: number;
42
+ byKind: Record<'audit' | 'quarantine' | 'memory' | 'graph' | 'unknown', {
43
+ pending: number;
44
+ failed: number;
45
+ synced: number;
46
+ }>;
47
+ oldestPendingAt: string | null;
48
+ nextRetryAt: string | null;
49
+ lastError: string | null;
50
+ lastErrorKind: 'audit' | 'quarantine' | 'memory' | 'graph' | 'unknown' | null;
40
51
  }
41
52
  export interface SyncQueueResult {
42
53
  processed: number;
@@ -44,6 +55,9 @@ export interface SyncQueueResult {
44
55
  failed: number;
45
56
  permanentlyFailed: number;
46
57
  }
58
+ export interface ReconcileQueueResult {
59
+ removed: number;
60
+ }
47
61
  /**
48
62
  * Enqueue a failed sync entry for later retry.
49
63
  * INSERT into sync_queue with exponential backoff schedule.
@@ -53,6 +67,11 @@ export declare function enqueueFailedSync(entry: SyncEntry): void;
53
67
  * Enqueue a failed quarantine sync entry for later retry.
54
68
  */
55
69
  export declare function enqueueFailedQuarantineSync(entry: QuarantineSyncEntry): void;
70
+ /**
71
+ * Enqueue a failed memory sync entry for later retry.
72
+ */
73
+ export declare function enqueueFailedMemorySync(entry: SyncedMemoryRecord): void;
74
+ export declare function enqueueFailedGraphSync(entry: GraphSyncEnvelope): void;
56
75
  /**
57
76
  * Process pending items in the retry queue.
58
77
  * SELECT pending WHERE next_retry_at <= now, retry each (up to 10 per tick).
@@ -63,6 +82,11 @@ export declare function processRetryQueue(): Promise<SyncQueueResult>;
63
82
  * Get queue statistics by status.
64
83
  */
65
84
  export declare function getQueueStats(): QueueStats;
85
+ export declare function reconcileSyncQueue(options?: {
86
+ kinds?: Array<'memory' | 'graph' | 'audit' | 'quarantine'>;
87
+ statuses?: Array<'pending' | 'failed'>;
88
+ maxCreatedAt?: string | null;
89
+ }): ReconcileQueueResult;
66
90
  /**
67
91
  * Purge old entries from the queue.
68
92
  * DELETE WHERE created_at < 7 days ago.
@@ -6,7 +6,7 @@
6
6
  * Uses SQLite sync_queue table with exponential backoff.
7
7
  */
8
8
  import { getDatabase } from '../database/init.js';
9
- import { getCloudConfig, updateLastSyncAt } from './config.js';
9
+ import { getCloudConfig, getDeviceId, getDeviceName, updateLastSyncAt } from './config.js';
10
10
  /**
11
11
  * Enqueue a failed sync entry for later retry.
12
12
  * INSERT into sync_queue with exponential backoff schedule.
@@ -20,6 +20,23 @@ export function enqueueFailedSync(entry) {
20
20
  export function enqueueFailedQuarantineSync(entry) {
21
21
  enqueuePayload({ kind: 'quarantine', entry });
22
22
  }
23
+ /**
24
+ * Enqueue a failed memory sync entry for later retry.
25
+ */
26
+ export function enqueueFailedMemorySync(entry) {
27
+ enqueuePayload({
28
+ kind: 'memory',
29
+ entry: {
30
+ record: entry,
31
+ device_id: getDeviceId(),
32
+ device_name: getDeviceName(),
33
+ platform: `${process.platform}/${process.arch}`,
34
+ },
35
+ });
36
+ }
37
+ export function enqueueFailedGraphSync(entry) {
38
+ enqueuePayload({ kind: 'graph', entry });
39
+ }
23
40
  function enqueuePayload(payload) {
24
41
  const db = getDatabase();
25
42
  const payloadJson = JSON.stringify(payload);
@@ -60,6 +77,35 @@ function buildRetryRequest(payloadText) {
60
77
  body: JSON.stringify(payload.entry),
61
78
  };
62
79
  }
80
+ if (parsed &&
81
+ typeof parsed === 'object' &&
82
+ !Array.isArray(parsed) &&
83
+ 'kind' in parsed &&
84
+ parsed.kind === 'memory') {
85
+ const payload = parsed;
86
+ return {
87
+ path: '/v1/sync/memories',
88
+ body: JSON.stringify({
89
+ device: {
90
+ device_id: payload.entry.device_id,
91
+ device_name: payload.entry.device_name,
92
+ platform: payload.entry.platform,
93
+ },
94
+ memories: [payload.entry.record],
95
+ }),
96
+ };
97
+ }
98
+ if (parsed &&
99
+ typeof parsed === 'object' &&
100
+ !Array.isArray(parsed) &&
101
+ 'kind' in parsed &&
102
+ parsed.kind === 'graph') {
103
+ const payload = parsed;
104
+ return {
105
+ path: '/v1/sync/graph',
106
+ body: JSON.stringify(payload.entry),
107
+ };
108
+ }
63
109
  throw new Error('Unsupported sync queue payload');
64
110
  }
65
111
  /**
@@ -167,21 +213,94 @@ export async function processRetryQueue() {
167
213
  export function getQueueStats() {
168
214
  const db = getDatabase();
169
215
  const rows = db.prepare(`
170
- SELECT status, COUNT(*) as count
216
+ SELECT id, payload, status, next_retry_at, last_error, created_at
171
217
  FROM sync_queue
172
- GROUP BY status
173
218
  `).all();
174
- const stats = { pending: 0, failed: 0, synced: 0 };
219
+ const stats = {
220
+ pending: 0,
221
+ failed: 0,
222
+ synced: 0,
223
+ byKind: {
224
+ audit: { pending: 0, failed: 0, synced: 0 },
225
+ quarantine: { pending: 0, failed: 0, synced: 0 },
226
+ memory: { pending: 0, failed: 0, synced: 0 },
227
+ graph: { pending: 0, failed: 0, synced: 0 },
228
+ unknown: { pending: 0, failed: 0, synced: 0 },
229
+ },
230
+ oldestPendingAt: null,
231
+ nextRetryAt: null,
232
+ lastError: null,
233
+ lastErrorKind: null,
234
+ };
235
+ let newestErrorRowId = -1;
175
236
  for (const row of rows) {
237
+ const kind = getPayloadKind(row.payload);
176
238
  if (row.status === 'pending')
177
- stats.pending = row.count;
239
+ stats.pending++;
178
240
  else if (row.status === 'failed')
179
- stats.failed = row.count;
241
+ stats.failed++;
180
242
  else if (row.status === 'synced')
181
- stats.synced = row.count;
243
+ stats.synced++;
244
+ if (row.status === 'pending' || row.status === 'failed' || row.status === 'synced') {
245
+ stats.byKind[kind][row.status]++;
246
+ }
247
+ if (row.status === 'pending' && row.created_at) {
248
+ if (!stats.oldestPendingAt || row.created_at < stats.oldestPendingAt) {
249
+ stats.oldestPendingAt = row.created_at;
250
+ }
251
+ }
252
+ if (row.status === 'pending' && row.next_retry_at) {
253
+ if (!stats.nextRetryAt || row.next_retry_at < stats.nextRetryAt) {
254
+ stats.nextRetryAt = row.next_retry_at;
255
+ }
256
+ }
257
+ if (row.last_error && row.id > newestErrorRowId) {
258
+ newestErrorRowId = row.id;
259
+ stats.lastError = row.last_error;
260
+ stats.lastErrorKind = kind;
261
+ }
182
262
  }
183
263
  return stats;
184
264
  }
265
+ export function reconcileSyncQueue(options = {}) {
266
+ const db = getDatabase();
267
+ const kinds = options.kinds ?? ['memory', 'graph'];
268
+ const statuses = options.statuses ?? ['pending', 'failed'];
269
+ if (kinds.length === 0 || statuses.length === 0) {
270
+ return { removed: 0 };
271
+ }
272
+ const kindPlaceholders = kinds.map(() => '?').join(', ');
273
+ const statusPlaceholders = statuses.map(() => '?').join(', ');
274
+ const params = [...statuses, ...kinds];
275
+ let sqlText = `
276
+ DELETE FROM sync_queue
277
+ WHERE status IN (${statusPlaceholders})
278
+ AND json_extract(payload, '$.kind') IN (${kindPlaceholders})
279
+ `;
280
+ if (options.maxCreatedAt) {
281
+ sqlText += ' AND created_at <= ?';
282
+ params.push(options.maxCreatedAt);
283
+ }
284
+ const result = db.prepare(sqlText).run(...params);
285
+ return { removed: Number(result.changes ?? 0) };
286
+ }
287
+ function getPayloadKind(payloadText) {
288
+ try {
289
+ const parsed = JSON.parse(payloadText);
290
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && 'kind' in parsed) {
291
+ const kind = parsed.kind;
292
+ if (kind === 'audit' || kind === 'quarantine' || kind === 'memory' || kind === 'graph')
293
+ return kind;
294
+ }
295
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
296
+ return 'audit';
297
+ }
298
+ }
299
+ catch {
300
+ // ignore malformed payloads
301
+ }
302
+ return 'unknown';
303
+ }
185
304
  /**
186
305
  * Purge old entries from the queue.
187
306
  * DELETE WHERE created_at < 7 days ago.
@@ -7,6 +7,7 @@ import { dirname, join } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { execSync } from 'child_process';
10
+ import { randomUUID } from 'crypto';
10
11
  const _currentFile = fileURLToPath(import.meta.url);
11
12
  const _currentDir = dirname(_currentFile);
12
13
  let db = null;
@@ -260,6 +261,25 @@ function runMigrations(database) {
260
261
  if (!columnNames.has('source')) {
261
262
  database.exec("ALTER TABLE memories ADD COLUMN source TEXT DEFAULT 'user:direct'");
262
263
  }
264
+ if (!columnNames.has('uuid')) {
265
+ database.exec("ALTER TABLE memories ADD COLUMN uuid TEXT");
266
+ }
267
+ if (!columnNames.has('updated_at')) {
268
+ database.exec('ALTER TABLE memories ADD COLUMN updated_at TIMESTAMP');
269
+ }
270
+ try {
271
+ const rowsWithoutUuid = database.prepare('SELECT id FROM memories WHERE uuid IS NULL OR uuid = ?').all('');
272
+ const setUuid = database.prepare('UPDATE memories SET uuid = ? WHERE id = ?');
273
+ for (const row of rowsWithoutUuid) {
274
+ setUuid.run(randomUUID(), row.id);
275
+ }
276
+ database.exec('UPDATE memories SET updated_at = COALESCE(updated_at, created_at, CURRENT_TIMESTAMP) WHERE updated_at IS NULL');
277
+ database.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_uuid ON memories(uuid)');
278
+ database.exec('CREATE INDEX IF NOT EXISTS idx_memories_updated ON memories(updated_at DESC)');
279
+ }
280
+ catch {
281
+ // Safe to ignore on partially migrated databases
282
+ }
263
283
  // Migration: Defence tables (defence_audit, quarantine, fragmentation_entities)
264
284
  try {
265
285
  database.exec(`
@@ -673,6 +693,7 @@ function getInlineSchema() {
673
693
  return `
674
694
  CREATE TABLE IF NOT EXISTS memories (
675
695
  id INTEGER PRIMARY KEY AUTOINCREMENT,
696
+ uuid TEXT NOT NULL UNIQUE,
676
697
  type TEXT NOT NULL CHECK(type IN ('short_term', 'long_term', 'episodic')),
677
698
  category TEXT NOT NULL DEFAULT 'note',
678
699
  title TEXT NOT NULL,
@@ -684,6 +705,7 @@ function getInlineSchema() {
684
705
  access_count INTEGER DEFAULT 0,
685
706
  last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
686
707
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
708
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
687
709
  metadata TEXT DEFAULT '{}',
688
710
  embedding BLOB,
689
711
  scope TEXT DEFAULT 'project',
@@ -721,11 +743,13 @@ function getInlineSchema() {
721
743
  END;
722
744
 
723
745
  CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
746
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_uuid ON memories(uuid);
724
747
  CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project);
725
748
  CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
726
749
  CREATE INDEX IF NOT EXISTS idx_memories_salience ON memories(salience DESC);
727
750
  CREATE INDEX IF NOT EXISTS idx_memories_decayed_score ON memories(decayed_score DESC);
728
751
  CREATE INDEX IF NOT EXISTS idx_memories_last_accessed ON memories(last_accessed DESC);
752
+ CREATE INDEX IF NOT EXISTS idx_memories_updated ON memories(updated_at DESC);
729
753
 
730
754
  CREATE TABLE IF NOT EXISTS sessions (
731
755
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1,8 +1,8 @@
1
1
  import { getDatabase } from '../database/init.js';
2
2
  import { extractFromMemory } from './extract.js';
3
- import { processExtractionResult } from './resolve.js';
3
+ import { replaceMemoryGraph } from './resolve.js';
4
4
  /** Bump this when extract.ts logic changes to force re-extraction */
5
- const EXTRACTION_VERSION = 2;
5
+ const EXTRACTION_VERSION = 3;
6
6
  export function backfillGraph(options) {
7
7
  const db = getDatabase();
8
8
  const force = options?.force ?? false;
@@ -32,9 +32,7 @@ export function backfillGraph(options) {
32
32
  for (const mem of memories) {
33
33
  try {
34
34
  const extraction = extractFromMemory(mem.title, mem.content, mem.category);
35
- if (extraction.entities.length > 0) {
36
- processExtractionResult(extraction, mem.id);
37
- }
35
+ replaceMemoryGraph(mem.id, extraction);
38
36
  // Mark this memory as extracted at the current version
39
37
  if (updateVersion) {
40
38
  updateVersion.run(EXTRACTION_VERSION, mem.id);
@@ -2,4 +2,14 @@ import type { EntityType, ExtractionResult } from './extract.js';
2
2
  export declare function levenshtein(a: string, b: string): number;
3
3
  export declare function resolveEntity(name: string, type: EntityType): number;
4
4
  export declare function mergeEntities(keepId: number, removeId: number): void;
5
+ export declare function removeMemoryGraph(memoryId: number): {
6
+ removedEntityLinks: number;
7
+ removedTriples: number;
8
+ orphanEntitiesPruned: number;
9
+ };
10
+ export declare function replaceMemoryGraph(memoryId: number, result: ExtractionResult): {
11
+ removedEntityLinks: number;
12
+ removedTriples: number;
13
+ orphanEntitiesPruned: number;
14
+ };
5
15
  export declare function processExtractionResult(result: ExtractionResult, memoryId: number): void;
@@ -93,7 +93,7 @@ export function mergeEntities(keepId, removeId) {
93
93
  db.prepare('DELETE FROM entities WHERE id = ?').run(removeId);
94
94
  })();
95
95
  }
96
- export function processExtractionResult(result, memoryId) {
96
+ function applyExtractionResult(result, memoryId) {
97
97
  const db = getDatabase();
98
98
  const nameToId = new Map();
99
99
  // 1. Resolve all entities
@@ -123,3 +123,65 @@ export function processExtractionResult(result, memoryId) {
123
123
  updateCount.run(id);
124
124
  }
125
125
  }
126
+ function pruneOrphanEntities(candidateIds) {
127
+ if (candidateIds.length === 0)
128
+ return 0;
129
+ const db = getDatabase();
130
+ const placeholders = candidateIds.map(() => '?').join(', ');
131
+ const orphanRows = db.prepare(`
132
+ SELECT e.id
133
+ FROM entities e
134
+ WHERE e.id IN (${placeholders})
135
+ AND NOT EXISTS (SELECT 1 FROM memory_entities me WHERE me.entity_id = e.id)
136
+ AND NOT EXISTS (SELECT 1 FROM triples t WHERE t.subject_id = e.id OR t.object_id = e.id)
137
+ `).all(...candidateIds);
138
+ if (orphanRows.length === 0)
139
+ return 0;
140
+ const orphanIds = orphanRows.map((row) => row.id);
141
+ const orphanPlaceholders = orphanIds.map(() => '?').join(', ');
142
+ db.prepare(`DELETE FROM entities WHERE id IN (${orphanPlaceholders})`).run(...orphanIds);
143
+ return orphanIds.length;
144
+ }
145
+ function clearMemoryGraphSlice(memoryId) {
146
+ const db = getDatabase();
147
+ const linkedEntityRows = db.prepare(`
148
+ SELECT DISTINCT entity_id
149
+ FROM memory_entities
150
+ WHERE memory_id = ?
151
+ `).all(memoryId);
152
+ const linkedEntityIds = linkedEntityRows.map((row) => row.entity_id);
153
+ const removedTriples = Number(db.prepare('DELETE FROM triples WHERE source_memory_id = ?').run(memoryId).changes ?? 0);
154
+ const removedEntityLinks = Number(db.prepare('DELETE FROM memory_entities WHERE memory_id = ?').run(memoryId).changes ?? 0);
155
+ if (linkedEntityIds.length > 0) {
156
+ const placeholders = linkedEntityIds.map(() => '?').join(', ');
157
+ db.prepare(`
158
+ UPDATE entities
159
+ SET memory_count = CASE WHEN memory_count > 0 THEN memory_count - 1 ELSE 0 END
160
+ WHERE id IN (${placeholders})
161
+ `).run(...linkedEntityIds);
162
+ }
163
+ const orphanEntitiesPruned = pruneOrphanEntities(linkedEntityIds);
164
+ return {
165
+ removedEntityLinks,
166
+ removedTriples,
167
+ orphanEntitiesPruned,
168
+ };
169
+ }
170
+ export function removeMemoryGraph(memoryId) {
171
+ const db = getDatabase();
172
+ return db.transaction(() => clearMemoryGraphSlice(memoryId))();
173
+ }
174
+ export function replaceMemoryGraph(memoryId, result) {
175
+ const db = getDatabase();
176
+ return db.transaction(() => {
177
+ const cleared = clearMemoryGraphSlice(memoryId);
178
+ if (result.entities.length > 0) {
179
+ applyExtractionResult(result, memoryId);
180
+ }
181
+ return cleared;
182
+ })();
183
+ }
184
+ export function processExtractionResult(result, memoryId) {
185
+ const db = getDatabase();
186
+ db.transaction(() => applyExtractionResult(result, memoryId))();
187
+ }
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@
25
25
  * shieldcortex hook session-start # Run session-start hook (for settings.json)
26
26
  * shieldcortex hook session-end # Run session-end hook (for settings.json)
27
27
  * shieldcortex service install # Auto-start dashboard on login
28
+ * shieldcortex service repair # Rebuild auto-start service with current install path
28
29
  * shieldcortex service uninstall # Remove auto-start
29
30
  * shieldcortex service status # Check service status
30
31
  * shieldcortex openclaw install # Install OpenClaw hook
@@ -32,6 +33,7 @@
32
33
  * shieldcortex openclaw status # Check OpenClaw hook status
33
34
  * shieldcortex config --openclaw-auto-memory true # Enable OpenClaw auto-memory extraction
34
35
  * shieldcortex config --openclaw-auto-memory false # Disable OpenClaw auto-memory extraction
36
+ * shieldcortex cloud sync --full # Backfill local memories + graph to ShieldCortex Cloud
35
37
  * shieldcortex copilot install # Configure MCP server for VS Code + Cursor
36
38
  * shieldcortex copilot uninstall # Remove MCP server configuration
37
39
  * shieldcortex copilot status # Check MCP server configuration
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@
25
25
  * shieldcortex hook session-start # Run session-start hook (for settings.json)
26
26
  * shieldcortex hook session-end # Run session-end hook (for settings.json)
27
27
  * shieldcortex service install # Auto-start dashboard on login
28
+ * shieldcortex service repair # Rebuild auto-start service with current install path
28
29
  * shieldcortex service uninstall # Remove auto-start
29
30
  * shieldcortex service status # Check service status
30
31
  * shieldcortex openclaw install # Install OpenClaw hook
@@ -32,6 +33,7 @@
32
33
  * shieldcortex openclaw status # Check OpenClaw hook status
33
34
  * shieldcortex config --openclaw-auto-memory true # Enable OpenClaw auto-memory extraction
34
35
  * shieldcortex config --openclaw-auto-memory false # Disable OpenClaw auto-memory extraction
36
+ * shieldcortex cloud sync --full # Backfill local memories + graph to ShieldCortex Cloud
35
37
  * shieldcortex copilot install # Configure MCP server for VS Code + Cursor
36
38
  * shieldcortex copilot uninstall # Remove MCP server configuration
37
39
  * shieldcortex copilot status # Check MCP server configuration
@@ -47,6 +49,7 @@
47
49
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
48
50
  import { spawn } from 'child_process';
49
51
  import { fileURLToPath } from 'url';
52
+ import http from 'http';
50
53
  import path from 'path';
51
54
  import fs from 'fs';
52
55
  import { createServer } from './server.js';
@@ -295,6 +298,25 @@ function startDashboard() {
295
298
  });
296
299
  return dashboard;
297
300
  }
301
+ function checkLocalHttp(url, expectedStatus = [200], timeoutMs = 1200) {
302
+ return new Promise((resolve) => {
303
+ const req = http.get(url, { timeout: timeoutMs }, (res) => {
304
+ res.resume();
305
+ resolve(Boolean(res.statusCode && expectedStatus.includes(res.statusCode)));
306
+ });
307
+ req.on('timeout', () => {
308
+ req.destroy();
309
+ resolve(false);
310
+ });
311
+ req.on('error', () => resolve(false));
312
+ });
313
+ }
314
+ async function isLocalApiRunning() {
315
+ return checkLocalHttp('http://127.0.0.1:3001/api/health');
316
+ }
317
+ async function isLocalDashboardRunning() {
318
+ return checkLocalHttp('http://127.0.0.1:3030', [200, 307, 308]);
319
+ }
298
320
  /**
299
321
  * Main entry point
300
322
  */
@@ -321,6 +343,7 @@ ${bold}COMMANDS${reset}
321
343
  ${cyan}doctor${reset} Diagnose installation issues
322
344
  ${cyan}quickstart${reset} [target] Detect the fastest setup path
323
345
  ${cyan}config${reset} [options] Configure cloud sync and settings
346
+ ${cyan}cloud${reset} sync --full Backfill local memories + graph to ShieldCortex Cloud
324
347
  ${cyan}license${reset} <action> Manage licence key (activate, status, deactivate)
325
348
  ${cyan}iron-dome${reset} <action> Manage behaviour protection layer
326
349
  ${cyan}audit${reset} [options] Run a full security audit
@@ -343,6 +366,7 @@ ${bold}EXAMPLES${reset}
343
366
  shieldcortex dashboard
344
367
  shieldcortex license activate sc_pro_...
345
368
  shieldcortex config --cloud-enable --cloud-api-key <key>
369
+ shieldcortex cloud sync --full
346
370
 
347
371
  ${bold}DOCS${reset}
348
372
  https://shieldcortex.ai/docs
@@ -423,6 +447,12 @@ ${bold}DOCS${reset}
423
447
  handleCloudConfig(process.argv.slice(3));
424
448
  return;
425
449
  }
450
+ // Handle "cloud" subcommand
451
+ if (process.argv[2] === 'cloud') {
452
+ const { handleCloudCommand } = await import('./cloud/cli.js');
453
+ await handleCloudCommand(process.argv.slice(3));
454
+ return;
455
+ }
426
456
  // Handle "status" subcommand
427
457
  if (process.argv[2] === 'status') {
428
458
  const { handleStatusCommand } = await import('./setup/status.js');
@@ -602,7 +632,7 @@ ${bold}DOCS${reset}
602
632
  const knownCommands = new Set([
603
633
  'doctor', 'quickstart', 'setup', 'install', 'migrate', 'uninstall', 'hook',
604
634
  'openclaw', 'clawdbot', 'copilot', 'service', 'config', 'status',
605
- 'graph', 'license', 'licence', 'audit', 'iron-dome', 'scan',
635
+ 'graph', 'license', 'licence', 'audit', 'iron-dome', 'scan', 'cloud',
606
636
  'scan-skill', 'scan-skills', 'dashboard', 'api',
607
637
  ]);
608
638
  const arg = process.argv[2];
@@ -615,14 +645,36 @@ ${bold}DOCS${reset}
615
645
  let dashboardProcess = null;
616
646
  if (mode === 'api') {
617
647
  // API mode only - for dashboard visualization
648
+ if (await isLocalApiRunning()) {
649
+ console.log('ShieldCortex API is already running at http://localhost:3001');
650
+ return;
651
+ }
618
652
  console.log('Starting ShieldCortex in API mode...');
619
653
  startVisualizationServer(dbPath);
620
654
  }
621
655
  else if (mode === 'dashboard') {
622
656
  // Dashboard mode - API + Next.js dashboard
623
657
  console.log('Starting ShieldCortex with Dashboard...');
624
- startVisualizationServer(dbPath);
625
- dashboardProcess = startDashboard();
658
+ const apiRunning = await isLocalApiRunning();
659
+ const dashboardRunning = await isLocalDashboardRunning();
660
+ if (apiRunning && dashboardRunning) {
661
+ console.log('ShieldCortex dashboard is already running.');
662
+ console.log('Dashboard: http://localhost:3030');
663
+ console.log('API: http://localhost:3001');
664
+ return;
665
+ }
666
+ if (!apiRunning) {
667
+ startVisualizationServer(dbPath);
668
+ }
669
+ else {
670
+ console.log('Reusing existing local ShieldCortex API on http://localhost:3001');
671
+ }
672
+ if (!dashboardRunning) {
673
+ dashboardProcess = startDashboard();
674
+ }
675
+ else {
676
+ console.log('Dashboard UI is already running on http://localhost:3030');
677
+ }
626
678
  // Kill dashboard child process on any exit (prevents orphans)
627
679
  const killDashboard = () => {
628
680
  if (dashboardProcess && !dashboardProcess.killed) {
@@ -648,7 +700,12 @@ ${bold}DOCS${reset}
648
700
  else if (mode === 'both') {
649
701
  // Both modes - API in background, MCP in foreground
650
702
  console.log('Starting ShieldCortex in both modes...');
651
- startVisualizationServer(dbPath);
703
+ if (!(await isLocalApiRunning())) {
704
+ startVisualizationServer(dbPath);
705
+ }
706
+ else {
707
+ console.log('Reusing existing local ShieldCortex API on http://localhost:3001');
708
+ }
652
709
  await startMcpServer(dbPath);
653
710
  }
654
711
  else {
@@ -0,0 +1,37 @@
1
+ import type Database from 'better-sqlite3';
2
+ import { Memory, MemoryCategory, MemoryConfig, SearchResult } from './types.js';
3
+ export interface SearchExecutionOptions {
4
+ enableSideEffects: boolean;
5
+ includeExplanation: boolean;
6
+ }
7
+ export interface SearchScoringContext {
8
+ db: Database.Database;
9
+ config: MemoryConfig;
10
+ detectedCategory: MemoryCategory | null;
11
+ queryTags: string[];
12
+ vectorResults: Map<number, number>;
13
+ query: string;
14
+ }
15
+ export interface SearchScoreValues {
16
+ ftsScore: number;
17
+ vectorSimilarity: number;
18
+ vectorBoost: number;
19
+ decayedScore: number;
20
+ priorityBoost: number;
21
+ recencyBoost: number;
22
+ categoryBoost: number;
23
+ linkBoost: number;
24
+ tagBoost: number;
25
+ activationBoost: number;
26
+ finalScore: number;
27
+ }
28
+ export type MemoryRowConverter = (row: Record<string, unknown>) => Memory;
29
+ export declare function detectQueryCategory(query: string): MemoryCategory | null;
30
+ export declare function calculateLinkBoost(memoryId: number, db: Database.Database): number;
31
+ export declare function calculateTagScore(queryTags: string[], memoryTags: string[]): number;
32
+ export declare function extractQueryTags(query: string): string[];
33
+ export declare function vectorSearch(db: Database.Database, rowToMemory: MemoryRowConverter, queryEmbedding: Float32Array, limit: number, project?: string, includeGlobal?: boolean): Array<{
34
+ memory: Memory;
35
+ similarity: number;
36
+ }>;
37
+ export declare function buildSearchExplanation(memory: Memory, context: SearchScoringContext, values: SearchScoreValues): SearchResult['explanation'];