shieldcortex 3.1.0 → 3.2.1

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 (59) hide show
  1. package/README.md +64 -9
  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/server/app/_global-error.html +2 -2
  5. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
  6. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
  13. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +2 -2
  14. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  17. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
  21. package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +3 -3
  22. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  23. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +3 -3
  24. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
  25. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +2 -2
  26. package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  27. package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  28. package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
  29. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  31. package/dashboard/.next/standalone/dashboard/.next/static/chunks/98e2c181d5c4349f.js +1 -0
  32. package/dashboard/.next/standalone/dashboard/.next/static/chunks/9cb86821c1107fd6.js +9 -0
  33. package/dashboard/.next/standalone/dashboard/.next/static/chunks/a56c497e02afd4ba.css +3 -0
  34. package/dashboard/.next/standalone/dashboard/.next/static/chunks/a90355d73183a5e6.js +1 -0
  35. package/dist/api/routes/memories.js +366 -1
  36. package/dist/api/routes/recall.js +53 -0
  37. package/dist/cloud/graph-sync.js +6 -3
  38. package/dist/cloud/memory-sync.d.ts +1 -0
  39. package/dist/cloud/memory-sync.js +3 -0
  40. package/dist/database/init.js +29 -0
  41. package/dist/memory/search.d.ts +1 -0
  42. package/dist/memory/search.js +4 -0
  43. package/dist/memory/store.js +146 -28
  44. package/dist/memory/types.d.ts +31 -0
  45. package/dist/setup/quickstart.js +13 -6
  46. package/dist/tools/context.d.ts +4 -4
  47. package/dist/tools/forget.d.ts +4 -4
  48. package/dist/tools/recall.d.ts +8 -8
  49. package/dist/tools/remember.d.ts +19 -4
  50. package/dist/tools/remember.js +17 -1
  51. package/hooks/openclaw/cortex-memory/handler.ts +8 -0
  52. package/package.json +2 -2
  53. package/dashboard/.next/standalone/dashboard/.next/static/chunks/0a69eb25d08447ee.js +0 -1
  54. package/dashboard/.next/standalone/dashboard/.next/static/chunks/3cc7e8d4f73cf5d2.js +0 -1
  55. package/dashboard/.next/standalone/dashboard/.next/static/chunks/97537d3db46c8467.css +0 -3
  56. package/dashboard/.next/standalone/dashboard/.next/static/chunks/aa6e9b8a52353969.js +0 -9
  57. /package/dashboard/.next/standalone/dashboard/.next/static/{RnvqrTXo_jN8SuMdaNcIj → Oi8lTcFeUV-igSMtPHAG-}/_buildManifest.js +0 -0
  58. /package/dashboard/.next/standalone/dashboard/.next/static/{RnvqrTXo_jN8SuMdaNcIj → Oi8lTcFeUV-igSMtPHAG-}/_clientMiddlewareManifest.json +0 -0
  59. /package/dashboard/.next/standalone/dashboard/.next/static/{RnvqrTXo_jN8SuMdaNcIj → Oi8lTcFeUV-igSMtPHAG-}/_ssgManifest.js +0 -0
@@ -122,6 +122,32 @@ function safeJsonParse(value, fallback) {
122
122
  return fallback;
123
123
  }
124
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
+ }
125
151
  /**
126
152
  * Convert database row to Memory object
127
153
  */
@@ -145,6 +171,16 @@ export function rowToMemory(row) {
145
171
  embedding: row.embedding,
146
172
  scope: row.scope ?? 'project',
147
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),
148
184
  };
149
185
  }
150
186
  /**
@@ -357,9 +393,16 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
357
393
  const scope = input.scope ??
358
394
  (detectGlobalPattern(input.content, category, tags) ? 'global' : 'project');
359
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;
360
400
  const stmt = db.prepare(`
361
- INSERT INTO memories (uuid, type, category, title, content, project, tags, salience, metadata, scope, transferable, updated_at)
362
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
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)
363
406
  `);
364
407
  // Anti-bloat: Truncate content if too large
365
408
  const truncationResult = truncateContent(input.content);
@@ -372,10 +415,14 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
372
415
  // Transaction: INSERT + defence UPDATE must be atomic to prevent wrong trust scores
373
416
  const insertedId = db.transaction(() => {
374
417
  const memoryUuid = randomUUID();
375
- const result = stmt.run(memoryUuid, type, category, input.title, truncationResult.content, input.project || null, JSON.stringify(tags), salience, JSON.stringify(input.metadata || {}), scope, transferable);
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);
376
419
  if (defenceResult) {
377
420
  db.prepare(`UPDATE memories SET trust_score = ?, sensitivity_level = ?, source = ? WHERE id = ?`)
378
- .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);
379
426
  }
380
427
  return result.lastInsertRowid;
381
428
  })();
@@ -455,30 +502,32 @@ export function addMemory(input, config = DEFAULT_CONFIG, source) {
455
502
  });
456
503
  // Anti-bloat: Check if limits exceeded and trigger async cleanup
457
504
  // We use setImmediate to not block the insert response
458
- setImmediate(() => {
459
- try {
460
- if (!isDatabaseInitialized()) {
461
- 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
+ }
462
524
  }
463
- const stats = getMemoryStats();
464
- if (stats.shortTerm > config.maxShortTermMemories ||
465
- stats.longTerm > config.maxLongTermMemories) {
466
- // Import dynamically to avoid circular dependency
467
- import('./consolidate.js').then(({ enforceMemoryLimits }) => {
468
- if (typeof enforceMemoryLimits === 'function') {
469
- enforceMemoryLimits(config);
470
- }
471
- }).catch((e) => {
472
- // Log but don't fail - consolidation will happen on next scheduled run
473
- console.warn('[shieldcortex] Async cleanup import failed:', e instanceof Error ? e.message : e);
474
- });
525
+ catch (e) {
526
+ // Log unexpected errors in async cleanup
527
+ console.warn('[shieldcortex] Async cleanup check failed:', e instanceof Error ? e.message : e);
475
528
  }
476
- }
477
- catch (e) {
478
- // Log unexpected errors in async cleanup
479
- console.warn('[shieldcortex] Async cleanup check failed:', e instanceof Error ? e.message : e);
480
- }
481
- });
529
+ });
530
+ }
482
531
  return memory;
483
532
  }
484
533
  /**
@@ -541,6 +590,44 @@ export function updateMemory(id, updates) {
541
590
  fields.push('metadata = ?');
542
591
  values.push(JSON.stringify(updates.metadata));
543
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
+ }
544
631
  if (fields.length === 0)
545
632
  return existing;
546
633
  values.push(id);
@@ -877,6 +964,12 @@ async function searchMemoriesInternal(options, config, source, execution) {
877
964
  sql += ' AND m.type = ?';
878
965
  params.push(options.type);
879
966
  }
967
+ if (!options.includeArchived) {
968
+ sql += ` AND m.status != 'archived'`;
969
+ }
970
+ if (!options.includeSuppressed) {
971
+ sql += ` AND m.status != 'suppressed'`;
972
+ }
880
973
  if (options.minSalience) {
881
974
  sql += ' AND m.salience >= ?';
882
975
  params.push(options.minSalience);
@@ -915,12 +1008,33 @@ async function searchMemoriesInternal(options, config, source, execution) {
915
1008
  const vectorSimilarity = vectorResults.get(memory.id) || 0;
916
1009
  const vectorBoost = vectorSimilarity * 0.3;
917
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`);
918
1024
  const relevanceScore = (ftsScore * 0.25 +
919
1025
  vectorBoost +
920
1026
  decayedScore * 0.2 +
921
1027
  priorityBoost +
922
- recencyBoost + categoryBoost + linkBoost + tagBoost + activationBoost);
923
- 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
+ };
924
1038
  if (execution.includeExplanation) {
925
1039
  result.explanation = buildSearchExplanation(memory, scoringContext, {
926
1040
  ftsScore,
@@ -933,8 +1047,12 @@ async function searchMemoriesInternal(options, config, source, execution) {
933
1047
  linkBoost,
934
1048
  tagBoost,
935
1049
  activationBoost,
1050
+ contradictionPenalty,
936
1051
  finalScore: relevanceScore,
937
1052
  });
1053
+ if (result.explanation) {
1054
+ result.explanation.eligibility = result.recallEligibility;
1055
+ }
938
1056
  }
939
1057
  return result;
940
1058
  });
@@ -2,6 +2,9 @@
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;
@@ -22,6 +25,16 @@ export interface Memory {
22
25
  embedding?: Buffer;
23
26
  scope: 'project' | 'global';
24
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;
25
38
  }
26
39
  export interface MemoryInput {
27
40
  type?: MemoryType;
@@ -34,6 +47,15 @@ export interface MemoryInput {
34
47
  metadata?: Record<string, unknown>;
35
48
  scope?: 'project' | 'global';
36
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;
37
59
  }
38
60
  export interface SearchOptions {
39
61
  query: string;
@@ -45,6 +67,8 @@ export interface SearchOptions {
45
67
  limit?: number;
46
68
  includeDecayed?: boolean;
47
69
  includeGlobal?: boolean;
70
+ includeArchived?: boolean;
71
+ includeSuppressed?: boolean;
48
72
  }
49
73
  export interface SearchResult {
50
74
  memory: Memory;
@@ -55,6 +79,7 @@ export interface SearchResult {
55
79
  score: number;
56
80
  }[];
57
81
  explanation?: SearchExplanation;
82
+ recallEligibility?: RecallEligibility;
58
83
  }
59
84
  export interface SearchScoreBreakdown {
60
85
  ftsScore: number;
@@ -67,14 +92,20 @@ export interface SearchScoreBreakdown {
67
92
  linkBoost: number;
68
93
  tagBoost: number;
69
94
  activationBoost: number;
95
+ contradictionPenalty: number;
70
96
  finalScore: number;
71
97
  matchedTags: string[];
72
98
  matchedCategory: string | null;
73
99
  }
100
+ export interface RecallEligibility {
101
+ eligible: boolean;
102
+ reasons: string[];
103
+ }
74
104
  export interface SearchExplanation {
75
105
  query: string;
76
106
  reasons: string[];
77
107
  breakdown: SearchScoreBreakdown;
108
+ eligibility?: RecallEligibility;
78
109
  }
79
110
  export interface ConsolidationResult {
80
111
  consolidated: number;
@@ -41,27 +41,34 @@ function printAutoGuide() {
41
41
  lines.push('');
42
42
  lines.push('ShieldCortex Quickstart');
43
43
  lines.push('────────────────────────────────────────────────────');
44
+ lines.push('Goal: give your agent memory you can inspect and security you can trust.');
44
45
  lines.push(`Detected: Claude=${env.claude ? 'yes' : 'no'} · OpenClaw=${env.openclaw ? 'yes' : 'no'} · Copilot/Cursor=${env.copilot ? 'yes' : 'no'}`);
45
46
  lines.push('');
47
+ lines.push('Best paths:');
48
+ lines.push('');
46
49
  if (env.claude) {
47
- lines.push('Best next step for persistent memory in Claude Code:');
50
+ lines.push('Claude Code');
51
+ lines.push(' Make recall and review available in Claude sessions:');
48
52
  lines.push(' shieldcortex quickstart claude');
49
53
  lines.push('');
50
54
  }
51
55
  if (env.openclaw) {
52
- lines.push('Best next step for OpenClaw hook + realtime plugin:');
56
+ lines.push('OpenClaw');
57
+ lines.push(' Install the hook + realtime plugin for session capture and defence:');
53
58
  lines.push(' shieldcortex quickstart openclaw');
54
59
  lines.push('');
55
60
  }
56
61
  if (env.copilot) {
57
- lines.push('Best next step for VS Code / Cursor MCP wiring:');
62
+ lines.push('VS Code / Cursor');
63
+ lines.push(' Wire ShieldCortex in as an MCP memory + security layer:');
58
64
  lines.push(' shieldcortex quickstart copilot');
59
65
  lines.push('');
60
66
  }
61
- lines.push('Security-only workflow:');
67
+ lines.push('Security-only');
68
+ lines.push(' Scan prompts, tools, and environments without adopting memory first:');
62
69
  lines.push(' shieldcortex quickstart security');
63
70
  lines.push('');
64
- lines.push('You can also run one of these directly:');
71
+ lines.push('Direct commands:');
65
72
  lines.push(' shieldcortex setup');
66
73
  lines.push(' shieldcortex openclaw install');
67
74
  lines.push(' shieldcortex copilot install');
@@ -79,7 +86,7 @@ ShieldCortex Security Quickstart
79
86
  2. Audit your local agent environment:
80
87
  shieldcortex audit
81
88
 
82
- 3. Open the dashboard for quarantine, audit, and graph visibility:
89
+ 3. Open the dashboard for quarantine, recall review, and sync visibility:
83
90
  shieldcortex dashboard
84
91
  `);
85
92
  }
@@ -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,17 +17,17 @@ 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;
@@ -38,7 +38,7 @@ export declare const forgetSchema: z.ZodObject<{
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;
@@ -20,10 +20,10 @@ export declare const recallSchema: z.ZodObject<{
20
20
  type: z.ZodEnum<["user", "cli", "hook", "email", "web", "agent", "file", "api", "tool_response"]>;
21
21
  identifier: z.ZodString;
22
22
  }, "strip", z.ZodTypeAny, {
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
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
26
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
27
27
  identifier: string;
28
28
  }>>;
29
29
  }, "strip", z.ZodTypeAny, {
@@ -32,7 +32,7 @@ export declare const recallSchema: z.ZodObject<{
32
32
  includeDecayed: boolean;
33
33
  includeGlobal: boolean;
34
34
  source?: {
35
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
35
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
36
36
  identifier: string;
37
37
  } | undefined;
38
38
  project?: string | undefined;
@@ -42,7 +42,7 @@ export declare const recallSchema: z.ZodObject<{
42
42
  query?: string | undefined;
43
43
  }, {
44
44
  source?: {
45
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
45
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
46
46
  identifier: string;
47
47
  } | undefined;
48
48
  project?: string | undefined;
@@ -87,22 +87,22 @@ export declare const getMemorySchema: z.ZodObject<{
87
87
  type: z.ZodEnum<["user", "cli", "hook", "email", "web", "agent", "file", "api", "tool_response"]>;
88
88
  identifier: z.ZodString;
89
89
  }, "strip", z.ZodTypeAny, {
90
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
90
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
91
91
  identifier: string;
92
92
  }, {
93
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
93
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
94
94
  identifier: string;
95
95
  }>>;
96
96
  }, "strip", z.ZodTypeAny, {
97
97
  id: number;
98
98
  source?: {
99
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
99
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
100
100
  identifier: string;
101
101
  } | undefined;
102
102
  }, {
103
103
  id: number;
104
104
  source?: {
105
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
105
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
106
106
  identifier: string;
107
107
  } | undefined;
108
108
  }>;
@@ -19,40 +19,55 @@ export declare const rememberSchema: z.ZodObject<{
19
19
  type: z.ZodEnum<["user", "cli", "hook", "email", "web", "agent", "file", "api", "tool_response"]>;
20
20
  identifier: z.ZodString;
21
21
  }, "strip", z.ZodTypeAny, {
22
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
22
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
23
23
  identifier: string;
24
24
  }, {
25
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
25
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
26
26
  identifier: string;
27
27
  }>>;
28
+ sourceType: z.ZodOptional<z.ZodEnum<["user", "cli", "hook", "email", "web", "agent", "file", "api", "tool_response"]>>;
29
+ sourceIdentifier: z.ZodOptional<z.ZodString>;
30
+ sessionId: z.ZodOptional<z.ZodString>;
31
+ agentId: z.ZodOptional<z.ZodString>;
32
+ workspaceDir: z.ZodOptional<z.ZodString>;
28
33
  }, "strip", z.ZodTypeAny, {
29
34
  title: string;
30
35
  content: string;
31
36
  scope?: "project" | "global" | undefined;
32
37
  transferable?: boolean | undefined;
33
38
  source?: {
34
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
39
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
35
40
  identifier: string;
36
41
  } | undefined;
37
42
  project?: string | undefined;
38
43
  type?: "short_term" | "long_term" | "episodic" | undefined;
39
44
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
40
45
  tags?: string[] | undefined;
46
+ sourceIdentifier?: string | undefined;
47
+ sessionId?: string | undefined;
41
48
  importance?: "critical" | "low" | "high" | "normal" | undefined;
49
+ sourceType?: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response" | undefined;
50
+ agentId?: string | undefined;
51
+ workspaceDir?: string | undefined;
42
52
  }, {
43
53
  title: string;
44
54
  content: string;
45
55
  scope?: "project" | "global" | undefined;
46
56
  transferable?: boolean | undefined;
47
57
  source?: {
48
- type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
58
+ type: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response";
49
59
  identifier: string;
50
60
  } | undefined;
51
61
  project?: string | undefined;
52
62
  type?: "short_term" | "long_term" | "episodic" | undefined;
53
63
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
54
64
  tags?: string[] | undefined;
65
+ sourceIdentifier?: string | undefined;
66
+ sessionId?: string | undefined;
55
67
  importance?: "critical" | "low" | "high" | "normal" | undefined;
68
+ sourceType?: "user" | "cli" | "hook" | "agent" | "api" | "email" | "web" | "file" | "tool_response" | undefined;
69
+ agentId?: string | undefined;
70
+ workspaceDir?: string | undefined;
56
71
  }>;
57
72
  export type RememberInput = z.infer<typeof rememberSchema>;
58
73
  /**
@@ -30,6 +30,13 @@ export const rememberSchema = z.object({
30
30
  type: z.enum(['user', 'cli', 'hook', 'email', 'web', 'agent', 'file', 'api', 'tool_response']),
31
31
  identifier: z.string(),
32
32
  }).optional().describe('Source of this memory for trust scoring (agents should pass this)'),
33
+ sourceType: z.enum(['user', 'cli', 'hook', 'email', 'web', 'agent', 'file', 'api', 'tool_response']).optional()
34
+ .describe('Flat source type for integrations that cannot pass nested objects'),
35
+ sourceIdentifier: z.string().optional()
36
+ .describe('Flat source identifier for integrations that cannot pass nested objects'),
37
+ sessionId: z.string().optional().describe('Optional integration session identifier'),
38
+ agentId: z.string().optional().describe('Optional integration agent identifier'),
39
+ workspaceDir: z.string().optional().describe('Optional workspace/source path'),
33
40
  });
34
41
  /**
35
42
  * Execute the remember tool
@@ -53,6 +60,14 @@ export async function executeRemember(input) {
53
60
  }
54
61
  // Resolve project (auto-detect if not provided)
55
62
  const resolvedProject = resolveProject(input.project);
63
+ const derivedSource = input.source ?? (input.sourceType && input.sourceIdentifier
64
+ ? { type: input.sourceType, identifier: input.sourceIdentifier }
65
+ : undefined);
66
+ const metadata = {
67
+ ...(input.sessionId ? { sessionId: input.sessionId } : {}),
68
+ ...(input.agentId ? { agentId: input.agentId } : {}),
69
+ ...(input.workspaceDir ? { workspaceDir: input.workspaceDir } : {}),
70
+ };
56
71
  // Map importance to salience override
57
72
  let salienceOverride;
58
73
  if (input.importance) {
@@ -96,7 +111,8 @@ export async function executeRemember(input) {
96
111
  salience: salienceOverride,
97
112
  scope: input.scope,
98
113
  transferable: input.transferable,
99
- }, undefined, input.source ?? { type: 'cli', identifier: 'mcp' });
114
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
115
+ }, undefined, derivedSource ?? { type: 'cli', identifier: 'mcp' });
100
116
  // Auto-detect and create relationships with existing memories
101
117
  let linksCreated = 0;
102
118
  try {
@@ -400,6 +400,9 @@ async function onSessionEnd(event) {
400
400
  scope: "global",
401
401
  importance: "high",
402
402
  tags: "auto-extracted,openclaw-hook",
403
+ sourceType: "hook",
404
+ sourceIdentifier: "openclaw-session-end",
405
+ workspaceDir: context.workspaceDir || "",
403
406
  });
404
407
  if (result) {
405
408
  saved++;
@@ -468,6 +471,9 @@ async function onSessionStop(event) {
468
471
  scope: "global",
469
472
  importance: "high",
470
473
  tags: "auto-extracted,openclaw-hook,session-stop",
474
+ sourceType: "hook",
475
+ sourceIdentifier: "openclaw-session-stop",
476
+ workspaceDir: context.workspaceDir || "",
471
477
  });
472
478
  if (result) {
473
479
  saved++;
@@ -611,6 +617,8 @@ async function checkAndSaveKeywordTrigger(messageText, event) {
611
617
  scope: "global",
612
618
  importance: matchedTrigger.importance,
613
619
  tags: `keyword-trigger,openclaw-hook,trigger:${matchedTrigger.phrase.replace(/\s+/g, "-")}`,
620
+ sourceType: "hook",
621
+ sourceIdentifier: `openclaw-keyword:${matchedTrigger.phrase.replace(/\s+/g, "-")}`,
614
622
  });
615
623
 
616
624
  if (result) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shieldcortex",
3
- "version": "3.1.0",
4
- "description": "Persistent brain for AI agents. Knowledge graphs, memory decay, contradiction detection, Iron Dome behaviour protection — plus the only defence pipeline that stops memory poisoning. Works with Claude Code, OpenClaw, LangChain, and any MCP agent.",
3
+ "version": "3.2.1",
4
+ "description": "Trustworthy memory and security for AI agents. Recall debugging, review queue, OpenClaw session capture, and memory poisoning defence for Claude Code, OpenClaw, LangChain, and MCP agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {