shieldcortex 3.0.2 → 3.0.4

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 (91) hide show
  1. package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
  2. package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
  3. package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
  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/react-loadable-manifest.json +1 -1
  28. package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
  29. package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_3051539d._.js +1 -1
  30. package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
  31. package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
  32. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
  33. package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
  34. package/dashboard/.next/standalone/dashboard/.next/static/chunks/313c0d327bbf244a.js +9 -0
  35. package/dashboard/.next/standalone/dashboard/.next/static/chunks/{fa5217550a8ab9a6.js → 49c1cec591af1460.js} +2 -2
  36. package/dashboard/.next/standalone/dashboard/.next/static/chunks/{f69fd1c5e71fbbfd.js → ca21f348cb163905.js} +1 -1
  37. package/dashboard/.next/standalone/dashboard/.next/static/chunks/f4ca424319f58dc7.css +3 -0
  38. package/dist/api/routes/admin.d.ts +12 -0
  39. package/dist/api/routes/admin.js +502 -0
  40. package/dist/api/routes/graph.d.ts +4 -0
  41. package/dist/api/routes/graph.js +333 -0
  42. package/dist/api/routes/incidents.d.ts +2 -0
  43. package/dist/api/routes/incidents.js +32 -0
  44. package/dist/api/routes/memories.d.ts +4 -0
  45. package/dist/api/routes/memories.js +659 -0
  46. package/dist/api/routes/recall.d.ts +4 -0
  47. package/dist/api/routes/recall.js +36 -0
  48. package/dist/api/routes/system.d.ts +9 -0
  49. package/dist/api/routes/system.js +201 -0
  50. package/dist/api/visualization-server.js +31 -1913
  51. package/dist/memory/search.d.ts +37 -0
  52. package/dist/memory/search.js +143 -0
  53. package/dist/memory/store.js +15 -166
  54. package/dist/tools/forget.d.ts +2 -2
  55. package/dist/tools/recall.d.ts +2 -2
  56. package/hooks/openclaw/cortex-memory/handler.ts +5 -141
  57. package/hooks/openclaw/cortex-memory/runtime.mjs +129 -0
  58. package/package.json +8 -4
  59. package/plugins/openclaw/dist/index.js +5 -39
  60. package/scripts/run-jest.mjs +25 -1
  61. package/dashboard/.next/standalone/dashboard/.next/static/chunks/be6970da20a17c0b.js +0 -9
  62. package/dashboard/.next/standalone/dashboard/.next/static/chunks/e63d2228780629dd.css +0 -3
  63. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsc.js +0 -133818
  64. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_tsserver.js +0 -659
  65. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  66. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  67. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  68. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  69. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  70. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  71. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  72. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  73. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  74. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  75. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  76. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  77. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsc.js +0 -8
  78. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserver.js +0 -8
  79. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  80. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typesMap.json +0 -497
  81. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typescript.js +0 -200276
  82. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/typingsInstaller.js +0 -8
  83. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/watchGuard.js +0 -53
  84. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  85. package/dashboard/.next/standalone/dashboard/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  86. package/dashboard/.next/standalone/dashboard/node_modules/typescript/package.json +0 -120
  87. package/scripts/start-dashboard.sh +0 -41
  88. package/scripts/stop-dashboard.sh +0 -21
  89. /package/dashboard/.next/standalone/dashboard/.next/static/{wWaw0Bi8k5Hc3R8vWNUFY → BEvyMAX62LQMyt5iSb-F9}/_buildManifest.js +0 -0
  90. /package/dashboard/.next/standalone/dashboard/.next/static/{wWaw0Bi8k5Hc3R8vWNUFY → BEvyMAX62LQMyt5iSb-F9}/_clientMiddlewareManifest.json +0 -0
  91. /package/dashboard/.next/standalone/dashboard/.next/static/{wWaw0Bi8k5Hc3R8vWNUFY → BEvyMAX62LQMyt5iSb-F9}/_ssgManifest.js +0 -0
@@ -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'];
@@ -0,0 +1,143 @@
1
+ import { cosineSimilarity } from '../embeddings/index.js';
2
+ export function detectQueryCategory(query) {
3
+ const lower = query.toLowerCase();
4
+ if (/architect|design|structure|pattern|system|schema|model/.test(lower)) {
5
+ return 'architecture';
6
+ }
7
+ if (/error|bug|fix|issue|crash|exception|fail|problem/.test(lower)) {
8
+ return 'error';
9
+ }
10
+ if (/prefer|always|never|style|convention|like|want/.test(lower)) {
11
+ return 'preference';
12
+ }
13
+ if (/learn|discover|realiz|found\s+out|turns?\s+out/.test(lower)) {
14
+ return 'learning';
15
+ }
16
+ if (/todo|task|pending|need\s+to|should\s+do/.test(lower)) {
17
+ return 'todo';
18
+ }
19
+ if (/relation|depend|connect|link|reference/.test(lower)) {
20
+ return 'relationship';
21
+ }
22
+ return null;
23
+ }
24
+ export function calculateLinkBoost(memoryId, db) {
25
+ try {
26
+ const linked = db.prepare(`
27
+ SELECT m.salience, ml.strength
28
+ FROM memory_links ml
29
+ JOIN memories m ON (m.id = ml.target_id OR m.id = ml.source_id)
30
+ WHERE (ml.source_id = ? OR ml.target_id = ?)
31
+ AND m.id != ?
32
+ `).all(memoryId, memoryId, memoryId);
33
+ if (linked.length === 0)
34
+ return 0;
35
+ const totalWeight = linked.reduce((sum, link) => sum + link.strength, 0);
36
+ if (totalWeight === 0)
37
+ return 0;
38
+ const weightedSalience = linked.reduce((sum, link) => sum + link.salience * link.strength, 0) / totalWeight;
39
+ return Math.min(0.15, weightedSalience * 0.2);
40
+ }
41
+ catch {
42
+ return 0;
43
+ }
44
+ }
45
+ export function calculateTagScore(queryTags, memoryTags) {
46
+ if (queryTags.length === 0 || memoryTags.length === 0)
47
+ return 0;
48
+ let matches = 0;
49
+ for (const queryTag of queryTags) {
50
+ const lowerQueryTag = queryTag.toLowerCase();
51
+ if (memoryTags.some((memoryTag) => {
52
+ const lowerMemoryTag = memoryTag.toLowerCase();
53
+ return lowerMemoryTag.includes(lowerQueryTag) || lowerQueryTag.includes(lowerMemoryTag);
54
+ })) {
55
+ matches++;
56
+ }
57
+ }
58
+ return (matches / queryTags.length) * 0.1;
59
+ }
60
+ export function extractQueryTags(query) {
61
+ const words = query.toLowerCase().split(/\s+/);
62
+ return words.filter((word) => word.length > 2 &&
63
+ /^[a-z][a-z0-9-]*$/.test(word) &&
64
+ !['the', 'and', 'for', 'with', 'how', 'what', 'when', 'where', 'why'].includes(word));
65
+ }
66
+ export function vectorSearch(db, rowToMemory, queryEmbedding, limit, project, includeGlobal = true) {
67
+ let query = `
68
+ SELECT * FROM memories
69
+ WHERE embedding IS NOT NULL
70
+ `;
71
+ const params = [];
72
+ if (project && includeGlobal) {
73
+ query += ` AND (project = ? OR scope = 'global')`;
74
+ params.push(project);
75
+ }
76
+ else if (project) {
77
+ query += ` AND project = ?`;
78
+ params.push(project);
79
+ }
80
+ const rows = db.prepare(query).all(...params);
81
+ return rows
82
+ .map((row) => {
83
+ const embeddingBuffer = row.embedding;
84
+ const embedding = new Float32Array(embeddingBuffer.buffer, embeddingBuffer.byteOffset, embeddingBuffer.length / 4);
85
+ return {
86
+ memory: rowToMemory(row),
87
+ similarity: cosineSimilarity(queryEmbedding, embedding),
88
+ };
89
+ })
90
+ .filter((result) => result.similarity > 0.3)
91
+ .sort((a, b) => b.similarity - a.similarity)
92
+ .slice(0, limit);
93
+ }
94
+ export function buildSearchExplanation(memory, context, values) {
95
+ const matchedTags = context.queryTags.filter((queryTag) => memory.tags.some((memoryTag) => {
96
+ const lowerMemoryTag = memoryTag.toLowerCase();
97
+ return lowerMemoryTag.includes(queryTag) || queryTag.includes(lowerMemoryTag);
98
+ }));
99
+ const reasons = [];
100
+ if (values.vectorSimilarity > 0) {
101
+ reasons.push(`Semantic similarity ${(values.vectorSimilarity * 100).toFixed(0)}%`);
102
+ }
103
+ if (values.ftsScore > 0.3) {
104
+ reasons.push('Strong keyword match');
105
+ }
106
+ if (values.categoryBoost > 0 && context.detectedCategory) {
107
+ reasons.push(`Matches ${context.detectedCategory} category intent`);
108
+ }
109
+ if (matchedTags.length > 0) {
110
+ reasons.push(`Shared tags: ${matchedTags.slice(0, 3).join(', ')}`);
111
+ }
112
+ if (values.recencyBoost > 0) {
113
+ reasons.push('Recently accessed');
114
+ }
115
+ if (values.linkBoost > 0) {
116
+ reasons.push('Connected to related memories');
117
+ }
118
+ if (values.activationBoost > 0) {
119
+ reasons.push('Activated by recent recall activity');
120
+ }
121
+ if (reasons.length === 0) {
122
+ reasons.push('Ranked by salience and base recall heuristics');
123
+ }
124
+ return {
125
+ query: context.query,
126
+ reasons,
127
+ breakdown: {
128
+ ftsScore: values.ftsScore,
129
+ vectorSimilarity: values.vectorSimilarity,
130
+ vectorBoost: values.vectorBoost,
131
+ decayedScore: values.decayedScore,
132
+ priorityBoost: values.priorityBoost,
133
+ recencyBoost: values.recencyBoost,
134
+ categoryBoost: values.categoryBoost,
135
+ linkBoost: values.linkBoost,
136
+ tagBoost: values.tagBoost,
137
+ activationBoost: values.activationBoost,
138
+ finalScore: values.finalScore,
139
+ matchedTags,
140
+ matchedCategory: values.categoryBoost > 0 ? context.detectedCategory : null,
141
+ },
142
+ };
143
+ }
@@ -23,6 +23,7 @@ import { scoreSource } from '../defence/trust/source-scorer.js';
23
23
  import { logAudit } from '../defence/audit/logger.js';
24
24
  import { dispatchWebhook } from '../events/webhooks.js';
25
25
  import { getCachedQueryEmbedding, findSimilarMemories } from './embedding.js';
26
+ import { buildSearchExplanation, calculateLinkBoost, calculateTagScore, detectQueryCategory, extractQueryTags, vectorSearch, } from './search.js';
26
27
  // Anti-bloat: Maximum content size per memory (10KB)
27
28
  const MAX_CONTENT_SIZE = 10 * 1024;
28
29
  // Track truncation info globally for the last addMemory call
@@ -77,6 +78,18 @@ function escapeFts5Query(query) {
77
78
  .split(/\s+/)
78
79
  .filter(term => term.length > 0)
79
80
  .map(term => {
81
+ // Replace apostrophes with spaces — FTS5 porter unicode61 tokenizer treats
82
+ // apostrophes as word separators during indexing ("don't" → "don" + "t").
83
+ // We must split the same way so queries match the indexed tokens.
84
+ if (term.includes("'")) {
85
+ const parts = term.split("'").filter(p => p.length > 0);
86
+ // Recursively escape each part and join with spaces
87
+ return parts.map(p => {
88
+ if (/[^a-zA-Z0-9_]/.test(p))
89
+ return `"${p.replace(/"/g, '""')}"`;
90
+ return p;
91
+ }).join(' ');
92
+ }
80
93
  // FTS5 boolean operators - quote them to search literally
81
94
  const upperTerm = term.toUpperCase();
82
95
  if (upperTerm === 'AND' || upperTerm === 'OR' || upperTerm === 'NOT') {
@@ -90,6 +103,7 @@ function escapeFts5Query(query) {
90
103
  }
91
104
  return term;
92
105
  })
106
+ .filter(Boolean)
93
107
  .join(' ');
94
108
  }
95
109
  /**
@@ -763,171 +777,6 @@ export function updateDecayScores() {
763
777
  }
764
778
  return updated;
765
779
  }
766
- /**
767
- * Detect the likely category a query is asking about
768
- */
769
- function detectQueryCategory(query) {
770
- const lower = query.toLowerCase();
771
- if (/architect|design|structure|pattern|system|schema|model/.test(lower)) {
772
- return 'architecture';
773
- }
774
- if (/error|bug|fix|issue|crash|exception|fail|problem/.test(lower)) {
775
- return 'error';
776
- }
777
- if (/prefer|always|never|style|convention|like|want/.test(lower)) {
778
- return 'preference';
779
- }
780
- if (/learn|discover|realiz|found\s+out|turns?\s+out/.test(lower)) {
781
- return 'learning';
782
- }
783
- if (/todo|task|pending|need\s+to|should\s+do/.test(lower)) {
784
- return 'todo';
785
- }
786
- if (/relation|depend|connect|link|reference/.test(lower)) {
787
- return 'relationship';
788
- }
789
- return null;
790
- }
791
- /**
792
- * Calculate a boost for memories linked to high-salience memories
793
- */
794
- function calculateLinkBoost(memoryId, db) {
795
- try {
796
- // Get linked memories and their salience
797
- const linked = db.prepare(`
798
- SELECT m.salience, ml.strength
799
- FROM memory_links ml
800
- JOIN memories m ON (m.id = ml.target_id OR m.id = ml.source_id)
801
- WHERE (ml.source_id = ? OR ml.target_id = ?)
802
- AND m.id != ?
803
- `).all(memoryId, memoryId, memoryId);
804
- if (linked.length === 0)
805
- return 0;
806
- // Calculate weighted average of linked memory salience
807
- const totalWeight = linked.reduce((sum, l) => sum + l.strength, 0);
808
- if (totalWeight === 0)
809
- return 0;
810
- const weightedSalience = linked.reduce((sum, l) => sum + l.salience * l.strength, 0) / totalWeight;
811
- // Cap boost at 0.15
812
- return Math.min(0.15, weightedSalience * 0.2);
813
- }
814
- catch {
815
- return 0;
816
- }
817
- }
818
- /**
819
- * Calculate partial tag match score
820
- */
821
- function calculateTagScore(queryTags, memoryTags) {
822
- if (queryTags.length === 0 || memoryTags.length === 0)
823
- return 0;
824
- // Count partial matches (substring matching)
825
- let matches = 0;
826
- for (const qt of queryTags) {
827
- const qtLower = qt.toLowerCase();
828
- if (memoryTags.some(mt => mt.toLowerCase().includes(qtLower) || qtLower.includes(mt.toLowerCase()))) {
829
- matches++;
830
- }
831
- }
832
- return (matches / queryTags.length) * 0.1;
833
- }
834
- /**
835
- * Extract potential tags from a query string
836
- */
837
- function extractQueryTags(query) {
838
- // Extract words that might be tags (tech terms, project-specific terms)
839
- const words = query.toLowerCase().split(/\s+/);
840
- return words.filter(w => w.length > 2 &&
841
- /^[a-z][a-z0-9-]*$/.test(w) &&
842
- !['the', 'and', 'for', 'with', 'how', 'what', 'when', 'where', 'why'].includes(w));
843
- }
844
- /**
845
- * Search memories by vector similarity
846
- * Returns memories sorted by cosine similarity to the query embedding
847
- */
848
- function vectorSearch(queryEmbedding, limit, project, includeGlobal = true) {
849
- const db = getDatabase();
850
- // Get memories with embeddings
851
- let query = `
852
- SELECT * FROM memories
853
- WHERE embedding IS NOT NULL
854
- `;
855
- const params = [];
856
- if (project && includeGlobal) {
857
- query += ` AND (project = ? OR scope = 'global')`;
858
- params.push(project);
859
- }
860
- else if (project) {
861
- query += ` AND project = ?`;
862
- params.push(project);
863
- }
864
- const rows = db.prepare(query).all(...params);
865
- // Calculate similarities
866
- const results = rows
867
- .map(row => {
868
- const embeddingBuffer = row.embedding;
869
- const embedding = new Float32Array(embeddingBuffer.buffer, embeddingBuffer.byteOffset, embeddingBuffer.length / 4);
870
- const similarity = cosineSimilarity(queryEmbedding, embedding);
871
- return {
872
- memory: rowToMemory(row),
873
- similarity,
874
- };
875
- })
876
- .filter(r => r.similarity > 0.3) // Threshold for relevance
877
- .sort((a, b) => b.similarity - a.similarity)
878
- .slice(0, limit);
879
- return results;
880
- }
881
- function buildSearchExplanation(memory, context, values) {
882
- const matchedTags = context.queryTags.filter((queryTag) => memory.tags.some((memoryTag) => {
883
- const lowerMemoryTag = memoryTag.toLowerCase();
884
- return lowerMemoryTag.includes(queryTag) || queryTag.includes(lowerMemoryTag);
885
- }));
886
- const reasons = [];
887
- if (values.vectorSimilarity > 0) {
888
- reasons.push(`Semantic similarity ${(values.vectorSimilarity * 100).toFixed(0)}%`);
889
- }
890
- if (values.ftsScore > 0.3) {
891
- reasons.push('Strong keyword match');
892
- }
893
- if (values.categoryBoost > 0 && context.detectedCategory) {
894
- reasons.push(`Matches ${context.detectedCategory} category intent`);
895
- }
896
- if (matchedTags.length > 0) {
897
- reasons.push(`Shared tags: ${matchedTags.slice(0, 3).join(', ')}`);
898
- }
899
- if (values.recencyBoost > 0) {
900
- reasons.push('Recently accessed');
901
- }
902
- if (values.linkBoost > 0) {
903
- reasons.push('Connected to related memories');
904
- }
905
- if (values.activationBoost > 0) {
906
- reasons.push('Activated by recent recall activity');
907
- }
908
- if (reasons.length === 0) {
909
- reasons.push('Ranked by salience and base recall heuristics');
910
- }
911
- return {
912
- query: context.query,
913
- reasons,
914
- breakdown: {
915
- ftsScore: values.ftsScore,
916
- vectorSimilarity: values.vectorSimilarity,
917
- vectorBoost: values.vectorBoost,
918
- decayedScore: values.decayedScore,
919
- priorityBoost: values.priorityBoost,
920
- recencyBoost: values.recencyBoost,
921
- categoryBoost: values.categoryBoost,
922
- linkBoost: values.linkBoost,
923
- tagBoost: values.tagBoost,
924
- activationBoost: values.activationBoost,
925
- finalScore: values.finalScore,
926
- matchedTags,
927
- matchedCategory: values.categoryBoost > 0 ? context.detectedCategory : null,
928
- },
929
- };
930
- }
931
780
  async function searchMemoriesInternal(options, config, source, execution) {
932
781
  if (++searchCount % 100 === 0) {
933
782
  pruneActivationCache();
@@ -945,7 +794,7 @@ async function searchMemoriesInternal(options, config, source, execution) {
945
794
  if (!queryEmbedding) {
946
795
  throw new Error('query embedding unavailable');
947
796
  }
948
- const vectorHits = vectorSearch(queryEmbedding, limit * 2, options.project, includeGlobal);
797
+ const vectorHits = vectorSearch(db, rowToMemory, queryEmbedding, limit * 2, options.project, includeGlobal);
949
798
  for (const hit of vectorHits) {
950
799
  vectorResults.set(hit.memory.id, hit.similarity);
951
800
  }
@@ -32,8 +32,8 @@ export declare const forgetSchema: z.ZodObject<{
32
32
  } | undefined;
33
33
  project?: string | undefined;
34
34
  id?: number | undefined;
35
- category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
36
35
  query?: string | undefined;
36
+ category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
37
37
  olderThan?: number | undefined;
38
38
  belowSalience?: number | undefined;
39
39
  }, {
@@ -43,8 +43,8 @@ export declare const forgetSchema: z.ZodObject<{
43
43
  } | undefined;
44
44
  project?: string | undefined;
45
45
  id?: number | undefined;
46
- category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
47
46
  query?: string | undefined;
47
+ category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
48
48
  olderThan?: number | undefined;
49
49
  belowSalience?: number | undefined;
50
50
  dryRun?: boolean | undefined;
@@ -37,9 +37,9 @@ export declare const recallSchema: z.ZodObject<{
37
37
  } | undefined;
38
38
  project?: string | undefined;
39
39
  type?: "short_term" | "long_term" | "episodic" | undefined;
40
+ query?: string | undefined;
40
41
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
41
42
  tags?: string[] | undefined;
42
- query?: string | undefined;
43
43
  }, {
44
44
  source?: {
45
45
  type: "api" | "user" | "cli" | "hook" | "email" | "web" | "agent" | "file" | "tool_response";
@@ -49,9 +49,9 @@ export declare const recallSchema: z.ZodObject<{
49
49
  mode?: "search" | "important" | "recent" | undefined;
50
50
  type?: "short_term" | "long_term" | "episodic" | undefined;
51
51
  limit?: number | undefined;
52
+ query?: string | undefined;
52
53
  category?: "architecture" | "pattern" | "preference" | "error" | "context" | "learning" | "todo" | "note" | "relationship" | "custom" | undefined;
53
54
  tags?: string[] | undefined;
54
- query?: string | undefined;
55
55
  includeDecayed?: boolean | undefined;
56
56
  includeGlobal?: boolean | undefined;
57
57
  }>;
@@ -6,158 +6,22 @@
6
6
  * - Context injection on agent bootstrap
7
7
  * - Keyword-triggered memory saves
8
8
  */
9
- import { execFile } from "node:child_process";
10
9
  import { createHash } from "node:crypto";
11
10
  import fs from "node:fs/promises";
12
11
  import { homedir } from "node:os";
13
12
  import path from "node:path";
13
+ import { createOpenClawRuntime } from "./runtime.mjs";
14
14
 
15
15
  // ==================== SERVER COMMAND RESOLUTION ====================
16
16
 
17
- let _shieldConfig = null;
18
17
  let _autoMemoryNoticeShown = false;
19
-
20
- async function loadShieldConfig() {
21
- if (_shieldConfig) return _shieldConfig;
22
-
23
- try {
24
- const configPath = path.join(homedir(), ".shieldcortex", "config.json");
25
- _shieldConfig = JSON.parse(await fs.readFile(configPath, "utf-8"));
26
- } catch {
27
- _shieldConfig = {};
28
- }
29
-
30
- return _shieldConfig;
31
- }
18
+ const runtime = createOpenClawRuntime({ logPrefix: "[cortex-memory]" });
19
+ const loadShieldConfig = runtime.loadShieldConfig;
20
+ const callCortex = runtime.callCortex;
32
21
 
33
22
  async function isOpenClawAutoMemoryEnabled() {
34
23
  const config = await loadShieldConfig();
35
- // Default OFF — OpenClaw has its own memory system. User must explicitly opt in.
36
- return config?.openclawAutoMemory === true;
37
- }
38
-
39
- /**
40
- * Resolve the fastest way to invoke shieldcortex:
41
- * 1. ~/.shieldcortex/config.json "binaryPath" override
42
- * 2. Global install detected via `which shieldcortex`
43
- * 3. Fallback to `npx -y shieldcortex` (slow on ARM64)
44
- */
45
- let _resolvedServerCmd = null;
46
-
47
- async function resolveServerCmd() {
48
- if (_resolvedServerCmd) return _resolvedServerCmd;
49
-
50
- // 1. Check config for explicit binaryPath
51
- try {
52
- const config = await loadShieldConfig();
53
- if (config?.binaryPath) {
54
- await fs.access(config.binaryPath);
55
- _resolvedServerCmd = config.binaryPath;
56
- console.log(`[cortex-memory] Using configured binary: ${config.binaryPath}`);
57
- return _resolvedServerCmd;
58
- }
59
- } catch { /* Config not found, no binaryPath, or path invalid */ }
60
-
61
- // 2. Try to find global install via `which`
62
- try {
63
- const { execFileSync } = await import("node:child_process");
64
- const bin = execFileSync("which", ["shieldcortex"], {
65
- encoding: "utf-8",
66
- timeout: 3000,
67
- }).trim();
68
- if (bin) {
69
- _resolvedServerCmd = bin;
70
- console.log(`[cortex-memory] Using global install: ${bin}`);
71
- return _resolvedServerCmd;
72
- }
73
- } catch { /* Not in PATH */ }
74
-
75
- // 3. Fall back to npx (slow but always works)
76
- _resolvedServerCmd = "npx -y shieldcortex";
77
- console.log("[cortex-memory] Falling back to npx -y shieldcortex (slow path)");
78
- return _resolvedServerCmd;
79
- }
80
-
81
- // ==================== CORTEX MCP HELPER ====================
82
-
83
- let _lastCallErrorType = null;
84
-
85
- function classifyCallError(err) {
86
- if (err.killed || err.code === "ETIMEDOUT" || err.signal === "SIGTERM") return "timeout";
87
- if (/ENOENT|not found|command not found/i.test(err.message || "")) return "not-found";
88
- if (/mcporter/i.test(err.message || "")) return "mcporter";
89
- return "unknown";
90
- }
91
-
92
- /**
93
- * Call a ShieldCortex MCP tool via mcporter
94
- * @param {string} tool - Tool name (e.g., "remember", "recall", "get_context")
95
- * @param {Record<string, string>} args - Tool arguments as key:value pairs
96
- * @param {object} options - Options { retries: number, timeout: number }
97
- * @returns {Promise<string|null>} Raw stdout or null on error
98
- */
99
- async function callCortex(tool, args = {}, options = { retries: 1, timeout: 15000 }) {
100
- const serverCmd = await resolveServerCmd();
101
-
102
- return new Promise((resolve) => {
103
- const cmdArgs = [
104
- "mcporter", "call", "--stdio",
105
- serverCmd,
106
- tool,
107
- ];
108
- for (const [key, value] of Object.entries(args)) {
109
- // Escape single quotes by doubling them (FTS5-safe)
110
- const safe = String(value).replace(/'/g, "''");
111
- cmdArgs.push(`${key}:${safe}`);
112
- }
113
-
114
- let attempts = 0;
115
- const maxAttempts = (options.retries || 0) + 1;
116
-
117
- function attempt() {
118
- attempts++;
119
- execFile("npx", cmdArgs, {
120
- timeout: options.timeout || 15000,
121
- maxBuffer: 1024 * 256,
122
- }, (err, stdout) => {
123
- if (err) {
124
- // Distinguish timeout from other errors
125
- const isTimeout = err.killed || err.code === "ETIMEDOUT" || err.signal === "SIGTERM";
126
- const errorType = isTimeout ? "TIMEOUT" : "ERROR";
127
-
128
- if (attempts < maxAttempts) {
129
- console.warn(`[cortex-memory] ${errorType} on ${tool} (attempt ${attempts}/${maxAttempts}), retrying...`);
130
- setTimeout(attempt, 1000); // 1s delay before retry
131
- return;
132
- }
133
-
134
- // One-time categorised warning per error type (prevents spam)
135
- const category = classifyCallError(err);
136
- if (category !== _lastCallErrorType) {
137
- _lastCallErrorType = category;
138
- switch (category) {
139
- case "timeout":
140
- console.warn("[cortex-memory] ShieldCortex call timed out (15s). Memory may be under heavy load.");
141
- break;
142
- case "not-found":
143
- console.warn("[cortex-memory] ShieldCortex binary not found. Run: npm install -g shieldcortex");
144
- break;
145
- case "mcporter":
146
- console.warn("[cortex-memory] mcporter failed to reach ShieldCortex MCP server. Is it configured?");
147
- break;
148
- default:
149
- console.warn(`[cortex-memory] ShieldCortex call failed: ${err.message}`);
150
- }
151
- }
152
- resolve(null);
153
- return;
154
- }
155
- resolve(stdout?.trim() || null);
156
- });
157
- }
158
-
159
- attempt();
160
- });
24
+ return runtime.isOpenClawAutoMemoryEnabled(config);
161
25
  }
162
26
 
163
27
  // ==================== NOVELTY / DEDUPE GATE ====================