shieldcortex 3.0.4 → 3.2.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 (102) hide show
  1. package/README.md +21 -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/9232a2d99b47b21f.js +3 -0
  37. package/dashboard/.next/standalone/dashboard/.next/static/chunks/98e2c181d5c4349f.js +1 -0
  38. package/dashboard/.next/standalone/dashboard/.next/static/chunks/9cb86821c1107fd6.js +9 -0
  39. package/dashboard/.next/standalone/dashboard/.next/static/chunks/a56c497e02afd4ba.css +3 -0
  40. package/dashboard/.next/standalone/dashboard/.next/static/chunks/a90355d73183a5e6.js +1 -0
  41. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  42. package/dashboard/.next/standalone/{node_modules/@img/sharp-linux-x64 → dashboard/node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  43. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/README.md +2 -2
  44. 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
  45. 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
  46. package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  47. package/dashboard/.next/standalone/dashboard/server.js +1 -1
  48. package/dashboard/.next/standalone/{dashboard/node_modules/@img/sharp-linux-x64 → node_modules/@img/sharp-darwin-arm64}/package.json +7 -13
  49. package/dashboard/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/package.json +5 -11
  50. package/dist/api/routes/memories.js +366 -1
  51. package/dist/api/routes/recall.js +53 -0
  52. package/dist/api/routes/system.js +67 -2
  53. package/dist/cloud/cli.d.ts +1 -0
  54. package/dist/cloud/cli.js +40 -0
  55. package/dist/cloud/config.d.ts +10 -0
  56. package/dist/cloud/config.js +54 -0
  57. package/dist/cloud/graph-sync.d.ts +45 -0
  58. package/dist/cloud/graph-sync.js +260 -0
  59. package/dist/cloud/memory-sync.d.ts +37 -0
  60. package/dist/cloud/memory-sync.js +186 -0
  61. package/dist/cloud/sync-queue.d.ts +24 -0
  62. package/dist/cloud/sync-queue.js +126 -7
  63. package/dist/database/init.js +53 -0
  64. package/dist/graph/backfill.js +3 -5
  65. package/dist/graph/resolve.d.ts +10 -0
  66. package/dist/graph/resolve.js +63 -1
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +61 -4
  69. package/dist/memory/search.d.ts +1 -0
  70. package/dist/memory/search.js +4 -0
  71. package/dist/memory/store.js +188 -30
  72. package/dist/memory/types.d.ts +33 -0
  73. package/dist/service/install.d.ts +1 -0
  74. package/dist/service/install.js +43 -1
  75. package/dist/tools/context.d.ts +4 -4
  76. package/dist/tools/forget.d.ts +6 -6
  77. package/dist/tools/recall.d.ts +11 -11
  78. package/dist/tools/remember.d.ts +19 -4
  79. package/dist/tools/remember.js +17 -1
  80. package/hooks/openclaw/cortex-memory/handler.ts +8 -0
  81. package/package.json +1 -1
  82. package/dashboard/.next/standalone/dashboard/.next/static/chunks/313c0d327bbf244a.js +0 -9
  83. package/dashboard/.next/standalone/dashboard/.next/static/chunks/3cc7e8d4f73cf5d2.js +0 -1
  84. package/dashboard/.next/standalone/dashboard/.next/static/chunks/49c1cec591af1460.js +0 -3
  85. package/dashboard/.next/standalone/dashboard/.next/static/chunks/ca21f348cb163905.js +0 -1
  86. package/dashboard/.next/standalone/dashboard/.next/static/chunks/f4ca424319f58dc7.css +0 -3
  87. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
  88. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
  89. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
  90. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
  91. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  92. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
  93. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
  94. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
  95. package/dashboard/.next/standalone/dashboard/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  96. package/dashboard/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
  97. package/dashboard/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
  98. /package/dashboard/.next/standalone/dashboard/.next/static/{BEvyMAX62LQMyt5iSb-F9 → ctp9eCBcHDpTWtUYMwJK7}/_buildManifest.js +0 -0
  99. /package/dashboard/.next/standalone/dashboard/.next/static/{BEvyMAX62LQMyt5iSb-F9 → ctp9eCBcHDpTWtUYMwJK7}/_clientMiddlewareManifest.json +0 -0
  100. /package/dashboard/.next/standalone/dashboard/.next/static/{BEvyMAX62LQMyt5iSb-F9 → ctp9eCBcHDpTWtUYMwJK7}/_ssgManifest.js +0 -0
  101. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/lib/index.js +0 -0
  102. /package/dashboard/.next/standalone/dashboard/node_modules/@img/{sharp-libvips-linux-x64 → sharp-libvips-darwin-arm64}/versions.json +0 -0
@@ -4,6 +4,7 @@
4
4
  * Core CRUD operations for the memory database.
5
5
  * Handles storage, retrieval, and management of memories.
6
6
  */
7
+ import { randomUUID } from 'crypto';
7
8
  import { getDatabase, isDatabaseInitialized } from '../database/init.js';
8
9
  import { DEFAULT_CONFIG, } from './types.js';
9
10
  import { calculateSalience, suggestCategory, extractTags, } from './salience.js';
@@ -14,9 +15,11 @@ import { emitMemoryCreated, emitMemoryAccessed, emitMemoryDeleted, emitMemoryUpd
14
15
  import { generateEmbedding, cosineSimilarity } from '../embeddings/index.js';
15
16
  import { isPaused } from '../api/control.js';
16
17
  import { extractFromMemory } from '../graph/extract.js';
17
- import { processExtractionResult } from '../graph/resolve.js';
18
+ import { processExtractionResult, removeMemoryGraph, replaceMemoryGraph } from '../graph/resolve.js';
18
19
  import { runDefencePipeline, storeFragmentationData } from '../defence/index.js';
19
20
  import { syncQuarantineToCloud } from '../cloud/quarantine-sync.js';
21
+ import { syncGraphDeleteForMemoryToCloud, syncGraphForMemoryToCloud } from '../cloud/graph-sync.js';
22
+ import { syncMemoryDeleteToCloud, syncMemoryUpsertToCloud } from '../cloud/memory-sync.js';
20
23
  import { isFeatureEnabled } from '../license/gate.js';
21
24
  import { checkAccess } from '../defence/trust/access-control.js';
22
25
  import { scoreSource } from '../defence/trust/source-scorer.js';
@@ -119,12 +122,39 @@ function safeJsonParse(value, fallback) {
119
122
  return fallback;
120
123
  }
121
124
  }
125
+ function inferSourceDetails(input, source) {
126
+ const tags = (input.tags ?? []).map((tag) => tag.toLowerCase());
127
+ if (tags.includes('openclaw-hook')) {
128
+ return { sourceKind: 'hook', captureMethod: tags.includes('auto-extracted') ? 'auto' : 'hook', sourceValue: 'hook:openclaw' };
129
+ }
130
+ if (tags.includes('realtime-plugin') || tags.includes('llm-output')) {
131
+ return { sourceKind: 'plugin', captureMethod: tags.includes('auto-extracted') ? 'auto' : 'plugin', sourceValue: 'agent:openclaw-plugin' };
132
+ }
133
+ if (source?.type === 'hook') {
134
+ return { sourceKind: 'hook', captureMethod: 'hook', sourceValue: `${source.type}:${source.identifier}` };
135
+ }
136
+ if (source?.type === 'agent') {
137
+ return { sourceKind: 'agent', captureMethod: 'plugin', sourceValue: `${source.type}:${source.identifier}` };
138
+ }
139
+ if (source?.type === 'api') {
140
+ return { sourceKind: 'api', captureMethod: 'api', sourceValue: `${source.type}:${source.identifier}` };
141
+ }
142
+ if (source?.type === 'cli') {
143
+ return { sourceKind: 'cli', captureMethod: 'manual', sourceValue: `${source.type}:${source.identifier}` };
144
+ }
145
+ return {
146
+ sourceKind: input.sourceKind ?? 'user',
147
+ captureMethod: input.captureMethod ?? (tags.includes('auto-extracted') ? 'auto' : 'manual'),
148
+ sourceValue: input.source ?? (source ? `${source.type}:${source.identifier}` : 'user:direct'),
149
+ };
150
+ }
122
151
  /**
123
152
  * Convert database row to Memory object
124
153
  */
125
154
  export function rowToMemory(row) {
126
155
  return {
127
156
  id: row.id,
157
+ uuid: row.uuid,
128
158
  type: row.type,
129
159
  category: row.category,
130
160
  title: row.title,
@@ -135,11 +165,22 @@ export function rowToMemory(row) {
135
165
  accessCount: row.access_count,
136
166
  lastAccessed: new Date(row.last_accessed),
137
167
  createdAt: new Date(row.created_at),
168
+ updatedAt: new Date(row.updated_at ?? row.created_at),
138
169
  decayedScore: row.decayed_score ?? row.salience,
139
170
  metadata: safeJsonParse(row.metadata, {}),
140
171
  embedding: row.embedding,
141
172
  scope: row.scope ?? 'project',
142
173
  transferable: Boolean(row.transferable),
174
+ status: (row.status ?? 'active'),
175
+ pinned: Boolean(row.pinned),
176
+ reviewedAt: row.reviewed_at ? new Date(row.reviewed_at) : null,
177
+ reviewedBy: row.reviewed_by ?? null,
178
+ sourceKind: (row.source_kind ?? 'user'),
179
+ captureMethod: (row.capture_method ?? 'manual'),
180
+ trustScore: Number(row.trust_score ?? 1),
181
+ sensitivityLevel: row.sensitivity_level ?? 'INTERNAL',
182
+ source: row.source ?? null,
183
+ cloudExcluded: Boolean(row.cloud_excluded),
143
184
  };
144
185
  }
145
186
  /**
@@ -352,9 +393,16 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
352
393
  const scope = input.scope ??
353
394
  (detectGlobalPattern(input.content, category, tags) ? 'global' : 'project');
354
395
  const transferable = input.transferable ?? (scope === 'global' ? 1 : 0);
396
+ const sourceDetails = inferSourceDetails({ ...input, tags }, source);
397
+ const status = input.status ?? 'active';
398
+ const pinned = input.pinned ? 1 : 0;
399
+ const cloudExcluded = input.cloudExcluded ? 1 : 0;
355
400
  const stmt = db.prepare(`
356
- INSERT INTO memories (type, category, title, content, project, tags, salience, metadata, scope, transferable)
357
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
401
+ INSERT INTO memories (
402
+ uuid, type, category, title, content, project, tags, salience, metadata, scope, transferable,
403
+ status, pinned, reviewed_at, reviewed_by, source_kind, capture_method, cloud_excluded, updated_at
404
+ )
405
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
358
406
  `);
359
407
  // Anti-bloat: Truncate content if too large
360
408
  const truncationResult = truncateContent(input.content);
@@ -366,10 +414,15 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
366
414
  };
367
415
  // Transaction: INSERT + defence UPDATE must be atomic to prevent wrong trust scores
368
416
  const insertedId = db.transaction(() => {
369
- const result = stmt.run(type, category, input.title, truncationResult.content, input.project || null, JSON.stringify(tags), salience, JSON.stringify(input.metadata || {}), scope, transferable);
417
+ const memoryUuid = randomUUID();
418
+ const result = stmt.run(memoryUuid, type, category, input.title, truncationResult.content, input.project || null, JSON.stringify(tags), salience, JSON.stringify(input.metadata || {}), scope, transferable, status, pinned, input.reviewedBy ? new Date().toISOString() : null, input.reviewedBy ?? null, sourceDetails.sourceKind, sourceDetails.captureMethod, cloudExcluded);
370
419
  if (defenceResult) {
371
420
  db.prepare(`UPDATE memories SET trust_score = ?, sensitivity_level = ?, source = ? WHERE id = ?`)
372
- .run(defenceResult.trust.score, defenceResult.sensitivity.level, `${source.type}:${source.identifier}`, result.lastInsertRowid);
421
+ .run(defenceResult.trust.score, defenceResult.sensitivity.level, sourceDetails.sourceValue, result.lastInsertRowid);
422
+ }
423
+ else {
424
+ db.prepare(`UPDATE memories SET source = ?, trust_score = COALESCE(trust_score, ?), sensitivity_level = COALESCE(sensitivity_level, ?) WHERE id = ?`)
425
+ .run(sourceDetails.sourceValue, input.trustScore ?? 1.0, input.sensitivityLevel ?? 'INTERNAL', result.lastInsertRowid);
373
426
  }
374
427
  return result.lastInsertRowid;
375
428
  })();
@@ -380,6 +433,9 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
380
433
  persistEvent('memory_created', { memory });
381
434
  // Webhook notification (fire-and-forget)
382
435
  dispatchWebhook('memory_created', { id: memory.id, title: memory.title, category: memory.category });
436
+ if (isFeatureEnabled('cloud_sync')) {
437
+ syncMemoryUpsertToCloud(memory);
438
+ }
383
439
  // ORGANIC FEATURE: Auto-link to related memories
384
440
  // This builds the knowledge graph automatically as memories are created
385
441
  try {
@@ -397,6 +453,9 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
397
453
  const extraction = extractFromMemory(input.title, truncationResult.content, category);
398
454
  if (extraction.entities.length > 0) {
399
455
  processExtractionResult(extraction, memory.id);
456
+ if (isFeatureEnabled('cloud_sync')) {
457
+ syncGraphForMemoryToCloud(memory.id);
458
+ }
400
459
  }
401
460
  }
402
461
  catch (e) {
@@ -443,30 +502,32 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
443
502
  });
444
503
  // Anti-bloat: Check if limits exceeded and trigger async cleanup
445
504
  // We use setImmediate to not block the insert response
446
- setImmediate(() => {
447
- try {
448
- if (!isDatabaseInitialized()) {
449
- return;
505
+ if (process.env.NODE_ENV !== 'test') {
506
+ setImmediate(() => {
507
+ try {
508
+ if (!isDatabaseInitialized()) {
509
+ return;
510
+ }
511
+ const stats = getMemoryStats();
512
+ if (stats.shortTerm > config.maxShortTermMemories ||
513
+ stats.longTerm > config.maxLongTermMemories) {
514
+ // Import dynamically to avoid circular dependency
515
+ import('./consolidate.js').then(({ enforceMemoryLimits }) => {
516
+ if (typeof enforceMemoryLimits === 'function') {
517
+ enforceMemoryLimits(config);
518
+ }
519
+ }).catch((e) => {
520
+ // Log but don't fail - consolidation will happen on next scheduled run
521
+ console.warn('[shieldcortex] Async cleanup import failed:', e instanceof Error ? e.message : e);
522
+ });
523
+ }
450
524
  }
451
- const stats = getMemoryStats();
452
- if (stats.shortTerm > config.maxShortTermMemories ||
453
- stats.longTerm > config.maxLongTermMemories) {
454
- // Import dynamically to avoid circular dependency
455
- import('./consolidate.js').then(({ enforceMemoryLimits }) => {
456
- if (typeof enforceMemoryLimits === 'function') {
457
- enforceMemoryLimits(config);
458
- }
459
- }).catch((e) => {
460
- // Log but don't fail - consolidation will happen on next scheduled run
461
- console.warn('[shieldcortex] Async cleanup import failed:', e instanceof Error ? e.message : e);
462
- });
525
+ catch (e) {
526
+ // Log unexpected errors in async cleanup
527
+ console.warn('[shieldcortex] Async cleanup check failed:', e instanceof Error ? e.message : e);
463
528
  }
464
- }
465
- catch (e) {
466
- // Log unexpected errors in async cleanup
467
- console.warn('[shieldcortex] Async cleanup check failed:', e instanceof Error ? e.message : e);
468
- }
469
- });
529
+ });
530
+ }
470
531
  return memory;
471
532
  }
472
533
  /**
@@ -529,15 +590,69 @@ export function updateMemory(id, updates) {
529
590
  fields.push('metadata = ?');
530
591
  values.push(JSON.stringify(updates.metadata));
531
592
  }
593
+ if (updates.status !== undefined) {
594
+ fields.push('status = ?');
595
+ values.push(updates.status);
596
+ }
597
+ if (updates.pinned !== undefined) {
598
+ fields.push('pinned = ?');
599
+ values.push(updates.pinned ? 1 : 0);
600
+ }
601
+ if (updates.reviewedBy !== undefined) {
602
+ fields.push('reviewed_by = ?');
603
+ values.push(updates.reviewedBy);
604
+ fields.push('reviewed_at = ?');
605
+ values.push(updates.reviewedBy ? new Date().toISOString() : null);
606
+ }
607
+ if (updates.sourceKind !== undefined) {
608
+ fields.push('source_kind = ?');
609
+ values.push(updates.sourceKind);
610
+ }
611
+ if (updates.captureMethod !== undefined) {
612
+ fields.push('capture_method = ?');
613
+ values.push(updates.captureMethod);
614
+ }
615
+ if (updates.trustScore !== undefined) {
616
+ fields.push('trust_score = ?');
617
+ values.push(updates.trustScore);
618
+ }
619
+ if (updates.sensitivityLevel !== undefined) {
620
+ fields.push('sensitivity_level = ?');
621
+ values.push(updates.sensitivityLevel);
622
+ }
623
+ if (updates.source !== undefined) {
624
+ fields.push('source = ?');
625
+ values.push(updates.source);
626
+ }
627
+ if (updates.cloudExcluded !== undefined) {
628
+ fields.push('cloud_excluded = ?');
629
+ values.push(updates.cloudExcluded ? 1 : 0);
630
+ }
532
631
  if (fields.length === 0)
533
632
  return existing;
534
633
  values.push(id);
535
- db.prepare(`UPDATE memories SET ${fields.join(', ')} WHERE id = ?`).run(...values);
634
+ db.prepare(`UPDATE memories SET ${fields.join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(...values);
536
635
  const updatedMemory = getMemoryById(id);
636
+ const shouldRefreshGraph = updates.title !== undefined ||
637
+ updates.content !== undefined ||
638
+ updates.category !== undefined;
639
+ if (shouldRefreshGraph) {
640
+ try {
641
+ const extraction = extractFromMemory(updatedMemory.title, updatedMemory.content, updatedMemory.category);
642
+ replaceMemoryGraph(updatedMemory.id, extraction);
643
+ }
644
+ catch (e) {
645
+ console.error('[shieldcortex] Entity extraction refresh failed:', e);
646
+ }
647
+ }
537
648
  // Emit event for real-time dashboard (in-process)
538
649
  emitMemoryUpdated(updatedMemory);
539
650
  // Persist event for cross-process IPC (MCP → Dashboard)
540
651
  persistEvent('memory_updated', { memory: updatedMemory });
652
+ if (isFeatureEnabled('cloud_sync')) {
653
+ syncMemoryUpsertToCloud(updatedMemory);
654
+ syncGraphForMemoryToCloud(updatedMemory.id);
655
+ }
541
656
  return updatedMemory;
542
657
  }
543
658
  /**
@@ -558,9 +673,21 @@ export function deleteMemory(id, source) {
558
673
  }
559
674
  // Get memory before deletion for event
560
675
  const memory = getMemoryById(id);
676
+ if (memory) {
677
+ try {
678
+ removeMemoryGraph(id);
679
+ }
680
+ catch (e) {
681
+ console.error('[shieldcortex] Graph cleanup before delete failed:', e);
682
+ }
683
+ }
561
684
  const result = db.prepare('DELETE FROM memories WHERE id = ?').run(id);
562
685
  // Emit event for real-time dashboard (in-process)
563
686
  if (result.changes > 0 && memory) {
687
+ if (isFeatureEnabled('cloud_sync')) {
688
+ syncMemoryDeleteToCloud(memory);
689
+ syncGraphDeleteForMemoryToCloud(memory);
690
+ }
564
691
  emitMemoryDeleted(id, memory.title);
565
692
  // Persist event for cross-process IPC (MCP → Dashboard)
566
693
  persistEvent('memory_deleted', { memoryId: id, title: memory.title });
@@ -837,6 +964,12 @@ async function searchMemoriesInternal(options, config, source, execution) {
837
964
  sql += ' AND m.type = ?';
838
965
  params.push(options.type);
839
966
  }
967
+ if (!options.includeArchived) {
968
+ sql += ` AND m.status != 'archived'`;
969
+ }
970
+ if (!options.includeSuppressed) {
971
+ sql += ` AND m.status != 'suppressed'`;
972
+ }
840
973
  if (options.minSalience) {
841
974
  sql += ' AND m.salience >= ?';
842
975
  params.push(options.minSalience);
@@ -875,12 +1008,33 @@ async function searchMemoriesInternal(options, config, source, execution) {
875
1008
  const vectorSimilarity = vectorResults.get(memory.id) || 0;
876
1009
  const vectorBoost = vectorSimilarity * 0.3;
877
1010
  const priorityBoost = calculatePriority(memory) * 0.05;
1011
+ const contradictionCount = db.prepare(`SELECT COUNT(*) as count FROM memory_links WHERE relationship = 'contradicts' AND (source_id = ? OR target_id = ?)`).get(memory.id, memory.id).count;
1012
+ const contradictionPenalty = Math.min(0.12, contradictionCount * 0.03);
1013
+ const eligibilityReasons = [];
1014
+ if (memory.status === 'archived')
1015
+ eligibilityReasons.push('Archived memories are excluded from normal recall');
1016
+ if (memory.status === 'suppressed')
1017
+ eligibilityReasons.push('Suppressed memories are excluded from normal recall');
1018
+ if (memory.cloudExcluded)
1019
+ eligibilityReasons.push('Excluded from cloud sync');
1020
+ if (memory.trustScore < 0.7)
1021
+ eligibilityReasons.push(`Low trust source (${memory.trustScore.toFixed(2)})`);
1022
+ if (contradictionCount > 0)
1023
+ eligibilityReasons.push(`${contradictionCount} contradiction link${contradictionCount === 1 ? '' : 's'} attached`);
878
1024
  const relevanceScore = (ftsScore * 0.25 +
879
1025
  vectorBoost +
880
1026
  decayedScore * 0.2 +
881
1027
  priorityBoost +
882
- recencyBoost + categoryBoost + linkBoost + tagBoost + activationBoost);
883
- const result = { memory, relevanceScore };
1028
+ recencyBoost + categoryBoost + linkBoost + tagBoost + activationBoost -
1029
+ contradictionPenalty);
1030
+ const result = {
1031
+ memory,
1032
+ relevanceScore,
1033
+ recallEligibility: {
1034
+ eligible: eligibilityReasons.length === 0,
1035
+ reasons: eligibilityReasons,
1036
+ },
1037
+ };
884
1038
  if (execution.includeExplanation) {
885
1039
  result.explanation = buildSearchExplanation(memory, scoringContext, {
886
1040
  ftsScore,
@@ -893,8 +1047,12 @@ async function searchMemoriesInternal(options, config, source, execution) {
893
1047
  linkBoost,
894
1048
  tagBoost,
895
1049
  activationBoost,
1050
+ contradictionPenalty,
896
1051
  finalScore: relevanceScore,
897
1052
  });
1053
+ if (result.explanation) {
1054
+ result.explanation.eligibility = result.recallEligibility;
1055
+ }
898
1056
  }
899
1057
  return result;
900
1058
  });
@@ -2,9 +2,13 @@
2
2
  * Core type definitions for the ShieldCortex memory system
3
3
  */
4
4
  export type MemoryType = 'short_term' | 'long_term' | 'episodic';
5
+ export type MemoryStatus = 'active' | 'archived' | 'suppressed' | 'canonical';
6
+ export type MemorySourceKind = 'user' | 'cli' | 'hook' | 'plugin' | 'agent' | 'import' | 'cloud' | 'api' | 'system';
7
+ export type MemoryCaptureMethod = 'manual' | 'hook' | 'plugin' | 'import' | 'cloud' | 'api' | 'auto' | 'review';
5
8
  export type MemoryCategory = 'architecture' | 'pattern' | 'preference' | 'error' | 'context' | 'learning' | 'todo' | 'note' | 'relationship' | 'custom';
6
9
  export interface Memory {
7
10
  id: number;
11
+ uuid: string;
8
12
  type: MemoryType;
9
13
  category: MemoryCategory;
10
14
  title: string;
@@ -15,11 +19,22 @@ export interface Memory {
15
19
  accessCount: number;
16
20
  lastAccessed: Date;
17
21
  createdAt: Date;
22
+ updatedAt: Date;
18
23
  decayedScore: number;
19
24
  metadata: Record<string, unknown>;
20
25
  embedding?: Buffer;
21
26
  scope: 'project' | 'global';
22
27
  transferable: boolean;
28
+ status: MemoryStatus;
29
+ pinned: boolean;
30
+ reviewedAt: Date | null;
31
+ reviewedBy: string | null;
32
+ sourceKind: MemorySourceKind;
33
+ captureMethod: MemoryCaptureMethod;
34
+ trustScore: number;
35
+ sensitivityLevel: string;
36
+ source: string | null;
37
+ cloudExcluded: boolean;
23
38
  }
24
39
  export interface MemoryInput {
25
40
  type?: MemoryType;
@@ -32,6 +47,15 @@ export interface MemoryInput {
32
47
  metadata?: Record<string, unknown>;
33
48
  scope?: 'project' | 'global';
34
49
  transferable?: boolean;
50
+ status?: MemoryStatus;
51
+ pinned?: boolean;
52
+ reviewedBy?: string | null;
53
+ sourceKind?: MemorySourceKind;
54
+ captureMethod?: MemoryCaptureMethod;
55
+ trustScore?: number;
56
+ sensitivityLevel?: string;
57
+ source?: string | null;
58
+ cloudExcluded?: boolean;
35
59
  }
36
60
  export interface SearchOptions {
37
61
  query: string;
@@ -43,6 +67,8 @@ export interface SearchOptions {
43
67
  limit?: number;
44
68
  includeDecayed?: boolean;
45
69
  includeGlobal?: boolean;
70
+ includeArchived?: boolean;
71
+ includeSuppressed?: boolean;
46
72
  }
47
73
  export interface SearchResult {
48
74
  memory: Memory;
@@ -53,6 +79,7 @@ export interface SearchResult {
53
79
  score: number;
54
80
  }[];
55
81
  explanation?: SearchExplanation;
82
+ recallEligibility?: RecallEligibility;
56
83
  }
57
84
  export interface SearchScoreBreakdown {
58
85
  ftsScore: number;
@@ -65,14 +92,20 @@ export interface SearchScoreBreakdown {
65
92
  linkBoost: number;
66
93
  tagBoost: number;
67
94
  activationBoost: number;
95
+ contradictionPenalty: number;
68
96
  finalScore: number;
69
97
  matchedTags: string[];
70
98
  matchedCategory: string | null;
71
99
  }
100
+ export interface RecallEligibility {
101
+ eligible: boolean;
102
+ reasons: string[];
103
+ }
72
104
  export interface SearchExplanation {
73
105
  query: string;
74
106
  reasons: string[];
75
107
  breakdown: SearchScoreBreakdown;
108
+ eligibility?: RecallEligibility;
76
109
  }
77
110
  export interface ConsolidationResult {
78
111
  consolidated: number;
@@ -7,6 +7,7 @@
7
7
  * - Windows: VBS script in Startup folder
8
8
  */
9
9
  export declare function installService(): Promise<void>;
10
+ export declare function repairService(): Promise<void>;
10
11
  export declare function uninstallService(options?: {
11
12
  cleanLogs?: boolean;
12
13
  }): Promise<void>;
@@ -31,6 +31,33 @@ function getServiceConfig() {
31
31
  logsDir,
32
32
  };
33
33
  }
34
+ function inspectServiceEntryPoint(platform, servicePath) {
35
+ if (!fs.existsSync(servicePath)) {
36
+ return { entryPoint: null, stale: false };
37
+ }
38
+ try {
39
+ const content = fs.readFileSync(servicePath, 'utf-8');
40
+ let entryPoint = null;
41
+ if (platform === 'macos') {
42
+ const matches = [...content.matchAll(/<string>([^<]+)<\/string>/g)].map((match) => match[1]);
43
+ entryPoint = matches.find((value) => value.endsWith('index.js')) ?? null;
44
+ }
45
+ else if (platform === 'linux') {
46
+ const match = content.match(/ExecStart=\S+\s+(\S+index\.js)/);
47
+ entryPoint = match?.[1] ?? null;
48
+ }
49
+ else {
50
+ const match = content.match(/Run\s+\"\"[^\"]+\"\"\s+\"\"([^\"]+index\.js)\"\"/);
51
+ entryPoint = match?.[1] ?? null;
52
+ }
53
+ const currentEntryPoint = getServiceConfig().entryPoint;
54
+ const stale = !!entryPoint && (entryPoint.includes('/.npm/_npx/') || entryPoint !== currentEntryPoint);
55
+ return { entryPoint, stale };
56
+ }
57
+ catch {
58
+ return { entryPoint: null, stale: false };
59
+ }
60
+ }
34
61
  function getServicePath(platform) {
35
62
  switch (platform) {
36
63
  case 'macos':
@@ -91,6 +118,10 @@ export async function installService() {
91
118
  console.log(` API: http://localhost:3001`);
92
119
  console.log(` Dashboard: http://localhost:3030`);
93
120
  }
121
+ export async function repairService() {
122
+ await uninstallService();
123
+ await installService();
124
+ }
94
125
  function cleanLogsDirectory() {
95
126
  const logsDir = path.join(os.homedir(), '.shieldcortex', 'logs');
96
127
  if (fs.existsSync(logsDir)) {
@@ -131,9 +162,14 @@ export async function serviceStatus() {
131
162
  const platform = detectPlatform();
132
163
  const servicePath = getServicePath(platform);
133
164
  const installed = fs.existsSync(servicePath);
165
+ const inspection = inspectServiceEntryPoint(platform, servicePath);
134
166
  console.log(`Platform: ${platform}`);
135
167
  console.log(`Installed: ${installed ? 'yes' : 'no'}`);
136
168
  console.log(`Path: ${servicePath}`);
169
+ if (inspection.entryPoint) {
170
+ console.log(`Entry: ${inspection.entryPoint}`);
171
+ console.log(`Healthy: ${inspection.stale ? 'no (repair recommended)' : 'yes'}`);
172
+ }
137
173
  if (!installed)
138
174
  return;
139
175
  // Check if running
@@ -158,12 +194,18 @@ export async function serviceStatus() {
158
194
  catch {
159
195
  console.log('Running: no');
160
196
  }
197
+ if (inspection.stale) {
198
+ console.log('Repair: shieldcortex service repair');
199
+ }
161
200
  }
162
201
  export async function handleServiceCommand(subcommand) {
163
202
  switch (subcommand) {
164
203
  case 'install':
165
204
  await installService();
166
205
  break;
206
+ case 'repair':
207
+ await repairService();
208
+ break;
167
209
  case 'uninstall':
168
210
  await uninstallService({ cleanLogs: process.argv.includes('--clean-logs') });
169
211
  break;
@@ -171,7 +213,7 @@ export async function handleServiceCommand(subcommand) {
171
213
  await serviceStatus();
172
214
  break;
173
215
  default:
174
- console.log('Usage: shieldcortex service <install|uninstall|status>');
216
+ console.log('Usage: shieldcortex service <install|repair|uninstall|status>');
175
217
  process.exit(1);
176
218
  }
177
219
  }
@@ -15,23 +15,23 @@ export declare const getContextSchema: z.ZodObject<{
15
15
  type: z.ZodEnum<["user", "cli", "hook", "email", "web", "agent", "file", "api", "tool_response"]>;
16
16
  identifier: z.ZodString;
17
17
  }, "strip", z.ZodTypeAny, {
18
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
18
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
19
19
  identifier: string;
20
20
  }, {
21
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
21
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
22
22
  identifier: string;
23
23
  }>>;
24
24
  }, "strip", z.ZodTypeAny, {
25
25
  format: "summary" | "detailed" | "raw";
26
26
  source?: {
27
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
27
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
28
28
  identifier: string;
29
29
  } | undefined;
30
30
  project?: string | undefined;
31
31
  query?: string | undefined;
32
32
  }, {
33
33
  source?: {
34
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
34
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
35
35
  identifier: string;
36
36
  } | undefined;
37
37
  project?: string | undefined;
@@ -17,34 +17,34 @@ export declare const forgetSchema: z.ZodObject<{
17
17
  type: z.ZodEnum<["user", "cli", "hook", "email", "web", "agent", "file", "api", "tool_response"]>;
18
18
  identifier: z.ZodString;
19
19
  }, "strip", z.ZodTypeAny, {
20
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
20
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
21
21
  identifier: string;
22
22
  }, {
23
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
23
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
24
24
  identifier: string;
25
25
  }>>;
26
26
  }, "strip", z.ZodTypeAny, {
27
27
  dryRun: boolean;
28
28
  confirm: boolean;
29
29
  source?: {
30
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
30
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
31
31
  identifier: string;
32
32
  } | undefined;
33
33
  project?: string | undefined;
34
34
  id?: number | undefined;
35
- query?: string | undefined;
36
35
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
36
+ query?: string | undefined;
37
37
  olderThan?: number | undefined;
38
38
  belowSalience?: number | undefined;
39
39
  }, {
40
40
  source?: {
41
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
41
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
42
42
  identifier: string;
43
43
  } | undefined;
44
44
  project?: string | undefined;
45
45
  id?: number | undefined;
46
- query?: string | undefined;
47
46
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
47
+ query?: string | undefined;
48
48
  olderThan?: number | undefined;
49
49
  belowSalience?: number | undefined;
50
50
  dryRun?: boolean | undefined;