n8n-nodes-engram 0.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 (154) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +334 -0
  3. package/dist/community/CommunityDetector.d.ts +11 -0
  4. package/dist/community/CommunityDetector.js +126 -0
  5. package/dist/community/CommunityDetector.js.map +1 -0
  6. package/dist/community/CommunitySummarizer.d.ts +8 -0
  7. package/dist/community/CommunitySummarizer.js +56 -0
  8. package/dist/community/CommunitySummarizer.js.map +1 -0
  9. package/dist/community/index.d.ts +2 -0
  10. package/dist/community/index.js +8 -0
  11. package/dist/community/index.js.map +1 -0
  12. package/dist/credentials/EngramExtractionApi.credentials.d.ts +8 -0
  13. package/dist/credentials/EngramExtractionApi.credentials.js +41 -0
  14. package/dist/credentials/EngramExtractionApi.credentials.js.map +1 -0
  15. package/dist/credentials/EngramNeo4jApi.credentials.d.ts +8 -0
  16. package/dist/credentials/EngramNeo4jApi.credentials.js +59 -0
  17. package/dist/credentials/EngramNeo4jApi.credentials.js.map +1 -0
  18. package/dist/descriptions.d.ts +4 -0
  19. package/dist/descriptions.js +41 -0
  20. package/dist/descriptions.js.map +1 -0
  21. package/dist/embeddings/EmbeddingService.d.ts +24 -0
  22. package/dist/embeddings/EmbeddingService.js +64 -0
  23. package/dist/embeddings/EmbeddingService.js.map +1 -0
  24. package/dist/embeddings/cosine.d.ts +1 -0
  25. package/dist/embeddings/cosine.js +21 -0
  26. package/dist/embeddings/cosine.js.map +1 -0
  27. package/dist/embeddings/index.d.ts +2 -0
  28. package/dist/embeddings/index.js +8 -0
  29. package/dist/embeddings/index.js.map +1 -0
  30. package/dist/extraction/ContradictionDetector.d.ts +11 -0
  31. package/dist/extraction/ContradictionDetector.js +35 -0
  32. package/dist/extraction/ContradictionDetector.js.map +1 -0
  33. package/dist/extraction/EntityDeduplicator.d.ts +17 -0
  34. package/dist/extraction/EntityDeduplicator.js +39 -0
  35. package/dist/extraction/EntityDeduplicator.js.map +1 -0
  36. package/dist/extraction/EntityExtractor.d.ts +11 -0
  37. package/dist/extraction/EntityExtractor.js +33 -0
  38. package/dist/extraction/EntityExtractor.js.map +1 -0
  39. package/dist/extraction/ExtractionPipeline.d.ts +24 -0
  40. package/dist/extraction/ExtractionPipeline.js +126 -0
  41. package/dist/extraction/ExtractionPipeline.js.map +1 -0
  42. package/dist/extraction/LlmClient.d.ts +36 -0
  43. package/dist/extraction/LlmClient.js +73 -0
  44. package/dist/extraction/LlmClient.js.map +1 -0
  45. package/dist/extraction/RelationshipExtractor.d.ts +12 -0
  46. package/dist/extraction/RelationshipExtractor.js +38 -0
  47. package/dist/extraction/RelationshipExtractor.js.map +1 -0
  48. package/dist/extraction/index.d.ts +6 -0
  49. package/dist/extraction/index.js +16 -0
  50. package/dist/extraction/index.js.map +1 -0
  51. package/dist/extraction/prompts.d.ts +16 -0
  52. package/dist/extraction/prompts.js +101 -0
  53. package/dist/extraction/prompts.js.map +1 -0
  54. package/dist/memory/EngramChatMemory.d.ts +42 -0
  55. package/dist/memory/EngramChatMemory.js +162 -0
  56. package/dist/memory/EngramChatMemory.js.map +1 -0
  57. package/dist/memory/EngramChatMessageHistory.d.ts +22 -0
  58. package/dist/memory/EngramChatMessageHistory.js +72 -0
  59. package/dist/memory/EngramChatMessageHistory.js.map +1 -0
  60. package/dist/memory/index.d.ts +2 -0
  61. package/dist/memory/index.js +8 -0
  62. package/dist/memory/index.js.map +1 -0
  63. package/dist/nodes/EngramAdmin/EngramAdmin.node.d.ts +5 -0
  64. package/dist/nodes/EngramAdmin/EngramAdmin.node.js +798 -0
  65. package/dist/nodes/EngramAdmin/EngramAdmin.node.js.map +1 -0
  66. package/dist/nodes/EngramAdmin/engram-admin.png +0 -0
  67. package/dist/nodes/EngramExplorer/EngramExplorer.node.d.ts +5 -0
  68. package/dist/nodes/EngramExplorer/EngramExplorer.node.js +932 -0
  69. package/dist/nodes/EngramExplorer/EngramExplorer.node.js.map +1 -0
  70. package/dist/nodes/EngramExplorer/engram-explorer.png +0 -0
  71. package/dist/nodes/EngramMemory/EngramMemory.node.d.ts +10 -0
  72. package/dist/nodes/EngramMemory/EngramMemory.node.js +462 -0
  73. package/dist/nodes/EngramMemory/EngramMemory.node.js.map +1 -0
  74. package/dist/nodes/EngramMemory/engram.png +0 -0
  75. package/dist/nodes/EngramTrigger/EngramTrigger.node.d.ts +5 -0
  76. package/dist/nodes/EngramTrigger/EngramTrigger.node.js +146 -0
  77. package/dist/nodes/EngramTrigger/EngramTrigger.node.js.map +1 -0
  78. package/dist/nodes/EngramTrigger/engram-trigger.png +0 -0
  79. package/dist/schemas/Community.schema.d.ts +656 -0
  80. package/dist/schemas/Community.schema.js +26 -0
  81. package/dist/schemas/Community.schema.js.map +1 -0
  82. package/dist/schemas/EntityEdge.schema.d.ts +86 -0
  83. package/dist/schemas/EntityEdge.schema.js +34 -0
  84. package/dist/schemas/EntityEdge.schema.js.map +1 -0
  85. package/dist/schemas/EntityNode.schema.d.ts +56 -0
  86. package/dist/schemas/EntityNode.schema.js +24 -0
  87. package/dist/schemas/EntityNode.schema.js.map +1 -0
  88. package/dist/schemas/EpisodicNode.schema.d.ts +53 -0
  89. package/dist/schemas/EpisodicNode.schema.js +23 -0
  90. package/dist/schemas/EpisodicNode.schema.js.map +1 -0
  91. package/dist/schemas/GraphData.schema.d.ts +220 -0
  92. package/dist/schemas/GraphData.schema.js +25 -0
  93. package/dist/schemas/GraphData.schema.js.map +1 -0
  94. package/dist/schemas/index.d.ts +5 -0
  95. package/dist/schemas/index.js +20 -0
  96. package/dist/schemas/index.js.map +1 -0
  97. package/dist/search/HybridSearchEngine.d.ts +31 -0
  98. package/dist/search/HybridSearchEngine.js +140 -0
  99. package/dist/search/HybridSearchEngine.js.map +1 -0
  100. package/dist/search/MinisearchProvider.d.ts +20 -0
  101. package/dist/search/MinisearchProvider.js +77 -0
  102. package/dist/search/MinisearchProvider.js.map +1 -0
  103. package/dist/search/TextSearchProvider.d.ts +20 -0
  104. package/dist/search/TextSearchProvider.js +3 -0
  105. package/dist/search/TextSearchProvider.js.map +1 -0
  106. package/dist/search/index.d.ts +3 -0
  107. package/dist/search/index.js +8 -0
  108. package/dist/search/index.js.map +1 -0
  109. package/dist/storage/GraphologyStorage.d.ts +42 -0
  110. package/dist/storage/GraphologyStorage.js +665 -0
  111. package/dist/storage/GraphologyStorage.js.map +1 -0
  112. package/dist/storage/IGraphStorage.d.ts +64 -0
  113. package/dist/storage/IGraphStorage.js +3 -0
  114. package/dist/storage/IGraphStorage.js.map +1 -0
  115. package/dist/storage/Neo4jStorage.d.ts +45 -0
  116. package/dist/storage/Neo4jStorage.js +949 -0
  117. package/dist/storage/Neo4jStorage.js.map +1 -0
  118. package/dist/storage/StorageFactory.d.ts +14 -0
  119. package/dist/storage/StorageFactory.js +26 -0
  120. package/dist/storage/StorageFactory.js.map +1 -0
  121. package/dist/storage/index.d.ts +3 -0
  122. package/dist/storage/index.js +8 -0
  123. package/dist/storage/index.js.map +1 -0
  124. package/dist/traversal/EpisodeTraverser.d.ts +10 -0
  125. package/dist/traversal/EpisodeTraverser.js +46 -0
  126. package/dist/traversal/EpisodeTraverser.js.map +1 -0
  127. package/dist/traversal/GraphTraverser.d.ts +24 -0
  128. package/dist/traversal/GraphTraverser.js +89 -0
  129. package/dist/traversal/GraphTraverser.js.map +1 -0
  130. package/dist/traversal/index.d.ts +2 -0
  131. package/dist/traversal/index.js +8 -0
  132. package/dist/traversal/index.js.map +1 -0
  133. package/dist/utils/descriptions.d.ts +6 -0
  134. package/dist/utils/descriptions.js +95 -0
  135. package/dist/utils/descriptions.js.map +1 -0
  136. package/dist/utils/helpers.d.ts +23 -0
  137. package/dist/utils/helpers.js +146 -0
  138. package/dist/utils/helpers.js.map +1 -0
  139. package/dist/utils/logWrapper.d.ts +26 -0
  140. package/dist/utils/logWrapper.js +300 -0
  141. package/dist/utils/logWrapper.js.map +1 -0
  142. package/dist/utils/sharedFields.d.ts +6 -0
  143. package/dist/utils/sharedFields.js +121 -0
  144. package/dist/utils/sharedFields.js.map +1 -0
  145. package/dist/utils/temporal.d.ts +22 -0
  146. package/dist/utils/temporal.js +44 -0
  147. package/dist/utils/temporal.js.map +1 -0
  148. package/dist/utils/tracing.d.ts +7 -0
  149. package/dist/utils/tracing.js +20 -0
  150. package/dist/utils/tracing.js.map +1 -0
  151. package/dist/utils/uuid.d.ts +2 -0
  152. package/dist/utils/uuid.js +13 -0
  153. package/dist/utils/uuid.js.map +1 -0
  154. package/package.json +108 -0
@@ -0,0 +1,798 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EngramAdmin = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const StorageFactory_1 = require("../../storage/StorageFactory");
6
+ const temporal_1 = require("../../utils/temporal");
7
+ const CommunityDetector_1 = require("../../community/CommunityDetector");
8
+ const CommunitySummarizer_1 = require("../../community/CommunitySummarizer");
9
+ const LlmClient_1 = require("../../extraction/LlmClient");
10
+ class EngramAdmin {
11
+ constructor() {
12
+ this.description = {
13
+ displayName: 'Engram Admin',
14
+ name: 'engramAdmin',
15
+ icon: 'file:engram-admin.png',
16
+ group: ['transform'],
17
+ version: 1,
18
+ description: 'Manage and administer the Engram knowledge graph',
19
+ defaults: {
20
+ name: 'Engram Admin',
21
+ },
22
+ inputs: ["main"],
23
+ outputs: ["main"],
24
+ credentials: [
25
+ {
26
+ name: 'engramNeo4jApi',
27
+ required: false,
28
+ displayOptions: {
29
+ show: {
30
+ backend: ['neo4j'],
31
+ },
32
+ },
33
+ },
34
+ {
35
+ name: 'engramExtractionApi',
36
+ required: false,
37
+ displayOptions: {
38
+ show: {
39
+ generateSummaries: [true],
40
+ },
41
+ },
42
+ },
43
+ ],
44
+ properties: [
45
+ {
46
+ displayName: 'Backend',
47
+ name: 'backend',
48
+ type: 'options',
49
+ options: [
50
+ { name: 'Embedded (Graphology)', value: 'embedded' },
51
+ { name: 'Neo4j (Remote)', value: 'neo4j' },
52
+ ],
53
+ default: 'embedded',
54
+ },
55
+ {
56
+ displayName: 'Operation',
57
+ name: 'operation',
58
+ type: 'options',
59
+ noDataExpression: true,
60
+ options: [
61
+ {
62
+ name: 'Stats',
63
+ value: 'stats',
64
+ description: 'Get graph statistics with enhanced metrics',
65
+ },
66
+ {
67
+ name: 'List Groups',
68
+ value: 'listGroups',
69
+ description: 'List all groups/sessions with per-group statistics',
70
+ },
71
+ {
72
+ name: 'Group Stats',
73
+ value: 'groupStats',
74
+ description: 'Get detailed statistics for a specific group/session',
75
+ },
76
+ {
77
+ name: 'Apply Retention',
78
+ value: 'applyRetention',
79
+ description: 'Remove old episodes based on a retention policy',
80
+ },
81
+ {
82
+ name: 'Clear Group',
83
+ value: 'clearGroup',
84
+ description: 'Clear all data for a specific group/session',
85
+ },
86
+ {
87
+ name: 'Bulk Clear Groups',
88
+ value: 'bulkClearGroups',
89
+ description: 'Clear multiple groups/sessions at once',
90
+ },
91
+ {
92
+ name: 'Clear All',
93
+ value: 'clearAll',
94
+ description: 'Clear ALL data from the graph (destructive!)',
95
+ },
96
+ {
97
+ name: 'Orphaned Entities',
98
+ value: 'orphanedEntities',
99
+ description: 'Find or remove entities with no relationships',
100
+ },
101
+ {
102
+ name: 'Duplicate Entities',
103
+ value: 'duplicateEntities',
104
+ description: 'Find entities with duplicate or similar names',
105
+ },
106
+ {
107
+ name: 'Expire Stale Edges',
108
+ value: 'expireStaleEdges',
109
+ description: 'Find and expire edges with broken references or past validity',
110
+ },
111
+ {
112
+ name: 'Export',
113
+ value: 'export',
114
+ description: 'Export graph data as JSON',
115
+ },
116
+ {
117
+ name: 'Import',
118
+ value: 'import',
119
+ description: 'Import graph data from JSON',
120
+ },
121
+ {
122
+ name: 'Detect Communities',
123
+ value: 'detectCommunities',
124
+ description: 'Detect entity communities/clusters using label propagation',
125
+ },
126
+ ],
127
+ default: 'stats',
128
+ },
129
+ {
130
+ displayName: 'Group ID',
131
+ name: 'groupId',
132
+ type: 'string',
133
+ default: '',
134
+ required: true,
135
+ displayOptions: {
136
+ show: {
137
+ operation: [
138
+ 'clearGroup',
139
+ 'applyRetention',
140
+ 'groupStats',
141
+ 'expireStaleEdges',
142
+ 'duplicateEntities',
143
+ 'orphanedEntities',
144
+ 'detectCommunities',
145
+ ],
146
+ },
147
+ },
148
+ description: 'The group/session ID to operate on',
149
+ },
150
+ {
151
+ displayName: 'Group ID',
152
+ name: 'groupIdFilter',
153
+ type: 'string',
154
+ default: '',
155
+ displayOptions: {
156
+ show: {
157
+ operation: ['export', 'stats'],
158
+ },
159
+ },
160
+ description: 'Optional: limit to a specific group/session. Leave empty to include all groups.',
161
+ },
162
+ {
163
+ displayName: 'Retention Type',
164
+ name: 'retentionType',
165
+ type: 'options',
166
+ options: [
167
+ {
168
+ name: 'Keep for N Days',
169
+ value: 'days',
170
+ description: 'Delete episodes older than N days',
171
+ },
172
+ {
173
+ name: 'Keep Last N Episodes',
174
+ value: 'max_episodes',
175
+ description: 'Keep only the most recent N episodes',
176
+ },
177
+ ],
178
+ default: 'days',
179
+ displayOptions: {
180
+ show: {
181
+ operation: ['applyRetention'],
182
+ },
183
+ },
184
+ description: 'How to determine which episodes to remove',
185
+ },
186
+ {
187
+ displayName: 'Retention Value',
188
+ name: 'retentionValue',
189
+ type: 'number',
190
+ default: 30,
191
+ typeOptions: {
192
+ minValue: 1,
193
+ },
194
+ displayOptions: {
195
+ show: {
196
+ operation: ['applyRetention'],
197
+ },
198
+ },
199
+ description: 'Number of days or maximum episodes to keep',
200
+ },
201
+ {
202
+ displayName: 'Group IDs',
203
+ name: 'groupIds',
204
+ type: 'json',
205
+ default: '[]',
206
+ required: true,
207
+ displayOptions: {
208
+ show: {
209
+ operation: ['bulkClearGroups'],
210
+ },
211
+ },
212
+ description: 'JSON array of group/session IDs to clear, e.g. ["session-1", "session-2"]',
213
+ },
214
+ {
215
+ displayName: 'Confirm Destructive',
216
+ name: 'confirmDestructive',
217
+ type: 'boolean',
218
+ default: false,
219
+ displayOptions: {
220
+ show: {
221
+ operation: ['bulkClearGroups'],
222
+ },
223
+ },
224
+ description: 'Safety latch: must be enabled for the bulk clear to proceed',
225
+ },
226
+ {
227
+ displayName: 'Delete Orphans',
228
+ name: 'deleteOrphans',
229
+ type: 'boolean',
230
+ default: false,
231
+ displayOptions: {
232
+ show: {
233
+ operation: ['orphanedEntities'],
234
+ },
235
+ },
236
+ description: 'If enabled, orphaned entities will be deleted. Otherwise, they are only listed.',
237
+ },
238
+ {
239
+ displayName: 'Dry Run',
240
+ name: 'dryRun',
241
+ type: 'boolean',
242
+ default: true,
243
+ displayOptions: {
244
+ show: {
245
+ operation: ['expireStaleEdges'],
246
+ },
247
+ },
248
+ description: 'If enabled, only reports stale edges without modifying them. Disable to actually expire them.',
249
+ },
250
+ {
251
+ displayName: 'Include Per-Group Stats',
252
+ name: 'includeStats',
253
+ type: 'boolean',
254
+ default: true,
255
+ displayOptions: {
256
+ show: {
257
+ operation: ['listGroups'],
258
+ },
259
+ },
260
+ description: 'Include entity/edge/episode counts per group. Slightly slower for large deployments.',
261
+ },
262
+ {
263
+ displayName: 'Limit',
264
+ name: 'limit',
265
+ type: 'number',
266
+ default: 100,
267
+ typeOptions: {
268
+ minValue: 1,
269
+ maxValue: 500,
270
+ },
271
+ displayOptions: {
272
+ show: {
273
+ operation: ['listGroups'],
274
+ },
275
+ },
276
+ description: 'Maximum number of groups to return',
277
+ },
278
+ {
279
+ displayName: 'Import Data',
280
+ name: 'importData',
281
+ type: 'json',
282
+ default: '{}',
283
+ required: true,
284
+ displayOptions: {
285
+ show: {
286
+ operation: ['import'],
287
+ },
288
+ },
289
+ description: 'JSON data to import (must contain entities, edges, and episodes arrays from a previous export)',
290
+ },
291
+ {
292
+ displayName: 'Min Community Size',
293
+ name: 'minCommunitySize',
294
+ type: 'number',
295
+ default: 2,
296
+ typeOptions: {
297
+ minValue: 2,
298
+ maxValue: 100,
299
+ },
300
+ displayOptions: {
301
+ show: {
302
+ operation: ['detectCommunities'],
303
+ },
304
+ },
305
+ description: 'Minimum number of entities required to form a community',
306
+ },
307
+ {
308
+ displayName: 'Generate Summaries',
309
+ name: 'generateSummaries',
310
+ type: 'boolean',
311
+ default: false,
312
+ displayOptions: {
313
+ show: {
314
+ operation: ['detectCommunities'],
315
+ },
316
+ },
317
+ description: 'Use an LLM to generate natural language summaries for each community. Requires Engram Extraction LLM credential.',
318
+ },
319
+ {
320
+ displayName: 'Summary Model',
321
+ name: 'summaryModel',
322
+ type: 'string',
323
+ default: '',
324
+ displayOptions: {
325
+ show: {
326
+ operation: ['detectCommunities'],
327
+ generateSummaries: [true],
328
+ },
329
+ },
330
+ placeholder: 'e.g. gpt-4o-mini',
331
+ description: 'LLM model to use for generating community summaries',
332
+ },
333
+ {
334
+ displayName: 'Summary Concurrency',
335
+ name: 'summaryConcurrency',
336
+ type: 'number',
337
+ default: 3,
338
+ typeOptions: {
339
+ minValue: 1,
340
+ maxValue: 10,
341
+ },
342
+ displayOptions: {
343
+ show: {
344
+ operation: ['detectCommunities'],
345
+ generateSummaries: [true],
346
+ },
347
+ },
348
+ description: 'Number of communities to summarize concurrently',
349
+ },
350
+ ],
351
+ };
352
+ }
353
+ async execute() {
354
+ var _a;
355
+ const items = this.getInputData();
356
+ const backend = this.getNodeParameter('backend', 0, 'embedded');
357
+ const operation = this.getNodeParameter('operation', 0);
358
+ let storage;
359
+ if (backend === 'neo4j') {
360
+ const credentials = await this.getCredentials('engramNeo4jApi');
361
+ storage = (0, StorageFactory_1.createStorage)({
362
+ backend: 'neo4j',
363
+ uri: credentials.uri,
364
+ username: credentials.username,
365
+ password: credentials.password,
366
+ database: credentials.database,
367
+ });
368
+ }
369
+ else {
370
+ const workflowId = (_a = this.getWorkflow().id) !== null && _a !== void 0 ? _a : 'default';
371
+ storage = (0, StorageFactory_1.createStorage)({
372
+ backend: 'embedded',
373
+ persistPath: `engram-data/${workflowId}-engram.json`,
374
+ });
375
+ }
376
+ await storage.initialize();
377
+ const returnData = [];
378
+ try {
379
+ for (let i = 0; i < items.length; i++) {
380
+ try {
381
+ switch (operation) {
382
+ case 'stats': {
383
+ const groupId = this.getNodeParameter('groupIdFilter', i, '');
384
+ const baseStats = await storage.getStats(groupId || undefined);
385
+ const data = await storage.exportGraph(groupId || undefined);
386
+ const activeEdges = data.edges.filter((e) => e.expired_at === null).length;
387
+ const expiredEdges = data.edges.filter((e) => e.expired_at !== null).length;
388
+ const groupCount = baseStats.group_ids.length;
389
+ const enhancedStats = {
390
+ ...baseStats,
391
+ group_count: groupCount,
392
+ active_edge_count: activeEdges,
393
+ expired_edge_count: expiredEdges,
394
+ avg_edges_per_entity: baseStats.entity_count > 0
395
+ ? Math.round((baseStats.edge_count / baseStats.entity_count) * 100) / 100
396
+ : 0,
397
+ avg_episodes_per_group: groupCount > 0
398
+ ? Math.round((baseStats.episode_count / groupCount) * 100) / 100
399
+ : 0,
400
+ storage_backend: backend,
401
+ data_span_days: baseStats.oldest_episode && baseStats.newest_episode
402
+ ? Math.round(((new Date(baseStats.newest_episode).getTime() -
403
+ new Date(baseStats.oldest_episode).getTime()) /
404
+ (1000 * 60 * 60 * 24)) *
405
+ 10) / 10
406
+ : null,
407
+ };
408
+ returnData.push({ json: enhancedStats });
409
+ break;
410
+ }
411
+ case 'listGroups': {
412
+ const includeStats = this.getNodeParameter('includeStats', i, true);
413
+ const limit = this.getNodeParameter('limit', i, 100);
414
+ const globalStats = await storage.getStats();
415
+ const allGroupIds = globalStats.group_ids.slice(0, limit);
416
+ if (includeStats) {
417
+ for (const gid of allGroupIds) {
418
+ const gStats = await storage.getStats(gid);
419
+ returnData.push({
420
+ json: {
421
+ group_id: gid,
422
+ entity_count: gStats.entity_count,
423
+ edge_count: gStats.edge_count,
424
+ episode_count: gStats.episode_count,
425
+ entity_types: gStats.entity_types,
426
+ oldest_episode: gStats.oldest_episode,
427
+ newest_episode: gStats.newest_episode,
428
+ },
429
+ });
430
+ }
431
+ }
432
+ else {
433
+ for (const gid of allGroupIds) {
434
+ returnData.push({ json: { group_id: gid } });
435
+ }
436
+ }
437
+ if (returnData.length === 0) {
438
+ returnData.push({
439
+ json: {
440
+ message: 'No groups found',
441
+ total_groups: 0,
442
+ },
443
+ });
444
+ }
445
+ break;
446
+ }
447
+ case 'groupStats': {
448
+ const groupId = this.getNodeParameter('groupId', i);
449
+ if (!groupId) {
450
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Group Stats operation', { itemIndex: i });
451
+ }
452
+ const gStats = await storage.getStats(groupId);
453
+ const gData = await storage.exportGraph(groupId);
454
+ const activeEdges = gData.edges.filter((e) => e.expired_at === null).length;
455
+ const expiredEdges = gData.edges.filter((e) => e.expired_at !== null).length;
456
+ const relationshipTypes = {};
457
+ for (const edge of gData.edges) {
458
+ relationshipTypes[edge.name] = (relationshipTypes[edge.name] || 0) + 1;
459
+ }
460
+ returnData.push({
461
+ json: {
462
+ group_id: groupId,
463
+ entity_count: gStats.entity_count,
464
+ edge_count: gStats.edge_count,
465
+ episode_count: gStats.episode_count,
466
+ entity_types: gStats.entity_types,
467
+ relationship_types: relationshipTypes,
468
+ active_edge_count: activeEdges,
469
+ expired_edge_count: expiredEdges,
470
+ avg_edges_per_entity: gStats.entity_count > 0
471
+ ? Math.round((gStats.edge_count / gStats.entity_count) * 100) / 100
472
+ : 0,
473
+ oldest_episode: gStats.oldest_episode,
474
+ newest_episode: gStats.newest_episode,
475
+ data_span_days: gStats.oldest_episode && gStats.newest_episode
476
+ ? Math.round(((new Date(gStats.newest_episode).getTime() -
477
+ new Date(gStats.oldest_episode).getTime()) /
478
+ (1000 * 60 * 60 * 24)) *
479
+ 10) / 10
480
+ : null,
481
+ },
482
+ });
483
+ break;
484
+ }
485
+ case 'applyRetention': {
486
+ const groupId = this.getNodeParameter('groupId', i);
487
+ if (!groupId) {
488
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Apply Retention operation', { itemIndex: i });
489
+ }
490
+ const retentionType = this.getNodeParameter('retentionType', i);
491
+ const retentionValue = this.getNodeParameter('retentionValue', i);
492
+ const removed = await storage.applyRetention(groupId, {
493
+ type: retentionType,
494
+ value: retentionValue,
495
+ });
496
+ returnData.push({
497
+ json: {
498
+ success: true,
499
+ operation: 'applyRetention',
500
+ group_id: groupId,
501
+ policy: { type: retentionType, value: retentionValue },
502
+ episodes_removed: removed,
503
+ },
504
+ });
505
+ break;
506
+ }
507
+ case 'clearGroup': {
508
+ const groupId = this.getNodeParameter('groupId', i);
509
+ if (!groupId) {
510
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Clear Group operation', {
511
+ itemIndex: i,
512
+ description: 'Provide the session/group ID whose data you want to delete.',
513
+ });
514
+ }
515
+ await storage.clearGroup(groupId);
516
+ returnData.push({
517
+ json: { success: true, operation: 'clearGroup', group_id: groupId },
518
+ });
519
+ break;
520
+ }
521
+ case 'bulkClearGroups': {
522
+ const confirmDestructive = this.getNodeParameter('confirmDestructive', i, false);
523
+ if (!confirmDestructive) {
524
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Confirm Destructive must be enabled to proceed with Bulk Clear Groups', {
525
+ itemIndex: i,
526
+ description: 'This is a safety measure. Enable "Confirm Destructive" to proceed.',
527
+ });
528
+ }
529
+ const groupIdsRaw = this.getNodeParameter('groupIds', i);
530
+ let groupIds;
531
+ if (Array.isArray(groupIdsRaw)) {
532
+ groupIds = groupIdsRaw.filter((id) => typeof id === 'string' && id.length > 0);
533
+ }
534
+ else {
535
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group IDs must be a JSON array of strings', { itemIndex: i });
536
+ }
537
+ if (groupIds.length === 0) {
538
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group IDs array is empty', {
539
+ itemIndex: i,
540
+ });
541
+ }
542
+ const cleared = [];
543
+ const failed = [];
544
+ for (const gid of groupIds) {
545
+ try {
546
+ await storage.clearGroup(gid);
547
+ cleared.push(gid);
548
+ }
549
+ catch (err) {
550
+ failed.push({ group_id: gid, error: err.message });
551
+ }
552
+ }
553
+ returnData.push({
554
+ json: {
555
+ success: failed.length === 0,
556
+ operation: 'bulkClearGroups',
557
+ cleared,
558
+ failed,
559
+ total_cleared: cleared.length,
560
+ total_failed: failed.length,
561
+ },
562
+ });
563
+ break;
564
+ }
565
+ case 'clearAll': {
566
+ await storage.clearAll();
567
+ returnData.push({
568
+ json: { success: true, operation: 'clearAll' },
569
+ });
570
+ break;
571
+ }
572
+ case 'orphanedEntities': {
573
+ const groupId = this.getNodeParameter('groupId', i);
574
+ if (!groupId) {
575
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Orphaned Entities operation', { itemIndex: i });
576
+ }
577
+ const deleteOrphans = this.getNodeParameter('deleteOrphans', i, false);
578
+ const entities = await storage.listEntities(groupId, { limit: 10000 });
579
+ const orphaned = [];
580
+ for (const entity of entities) {
581
+ const edges = await storage.getEdgesForEntity(entity.uuid);
582
+ if (edges.length === 0) {
583
+ orphaned.push({
584
+ uuid: entity.uuid,
585
+ name: entity.name,
586
+ entity_type: entity.entity_type,
587
+ summary: entity.summary,
588
+ created_at: entity.created_at,
589
+ });
590
+ }
591
+ }
592
+ if (deleteOrphans) {
593
+ for (const o of orphaned) {
594
+ await storage.deleteEntity(o.uuid);
595
+ }
596
+ }
597
+ returnData.push({
598
+ json: {
599
+ operation: 'orphanedEntities',
600
+ group_id: groupId,
601
+ orphaned,
602
+ total_orphaned: orphaned.length,
603
+ deleted: deleteOrphans,
604
+ },
605
+ });
606
+ break;
607
+ }
608
+ case 'duplicateEntities': {
609
+ const groupId = this.getNodeParameter('groupId', i);
610
+ if (!groupId) {
611
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Duplicate Entities operation', { itemIndex: i });
612
+ }
613
+ const entities = await storage.listEntities(groupId, { limit: 10000 });
614
+ const nameMap = new Map();
615
+ for (const entity of entities) {
616
+ const key = entity.name.toLowerCase().trim();
617
+ if (!nameMap.has(key))
618
+ nameMap.set(key, []);
619
+ nameMap.get(key).push(entity);
620
+ }
621
+ const duplicateGroups = [];
622
+ for (const [canonicalName, group] of nameMap) {
623
+ if (group.length > 1) {
624
+ const enriched = [];
625
+ for (const e of group) {
626
+ const edges = await storage.getEdgesForEntity(e.uuid);
627
+ enriched.push({
628
+ uuid: e.uuid,
629
+ name: e.name,
630
+ entity_type: e.entity_type,
631
+ edge_count: edges.length,
632
+ created_at: e.created_at,
633
+ });
634
+ }
635
+ duplicateGroups.push({
636
+ canonical_name: canonicalName,
637
+ count: group.length,
638
+ entities: enriched,
639
+ });
640
+ }
641
+ }
642
+ returnData.push({
643
+ json: {
644
+ operation: 'duplicateEntities',
645
+ group_id: groupId,
646
+ duplicate_groups: duplicateGroups,
647
+ total_duplicate_groups: duplicateGroups.length,
648
+ },
649
+ });
650
+ break;
651
+ }
652
+ case 'expireStaleEdges': {
653
+ const groupId = this.getNodeParameter('groupId', i);
654
+ if (!groupId) {
655
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Expire Stale Edges operation', { itemIndex: i });
656
+ }
657
+ const dryRun = this.getNodeParameter('dryRun', i, true);
658
+ const data = await storage.exportGraph(groupId);
659
+ const entityUuids = new Set(data.entities.map((e) => e.uuid));
660
+ const staleEdges = [];
661
+ for (const edge of data.edges) {
662
+ if (edge.expired_at)
663
+ continue;
664
+ if (!entityUuids.has(edge.source_node_uuid) ||
665
+ !entityUuids.has(edge.target_node_uuid)) {
666
+ staleEdges.push({
667
+ uuid: edge.uuid,
668
+ name: edge.name,
669
+ fact: edge.fact,
670
+ reason: 'dangling_reference',
671
+ });
672
+ }
673
+ else if (edge.invalid_at && new Date(edge.invalid_at) < new Date()) {
674
+ staleEdges.push({
675
+ uuid: edge.uuid,
676
+ name: edge.name,
677
+ fact: edge.fact,
678
+ reason: 'past_invalid_at',
679
+ });
680
+ }
681
+ }
682
+ if (!dryRun) {
683
+ for (const edge of staleEdges) {
684
+ await storage.updateEdge(edge.uuid, { expired_at: (0, temporal_1.nowIso)() });
685
+ }
686
+ }
687
+ returnData.push({
688
+ json: {
689
+ operation: 'expireStaleEdges',
690
+ group_id: groupId,
691
+ dry_run: dryRun,
692
+ stale_edges: staleEdges,
693
+ total_stale: staleEdges.length,
694
+ expired: !dryRun,
695
+ },
696
+ });
697
+ break;
698
+ }
699
+ case 'detectCommunities': {
700
+ const groupId = this.getNodeParameter('groupId', i);
701
+ if (!groupId) {
702
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Detect Communities operation', { itemIndex: i });
703
+ }
704
+ const minCommunitySize = this.getNodeParameter('minCommunitySize', i, 2);
705
+ const generateSummaries = this.getNodeParameter('generateSummaries', i, false);
706
+ const detector = new CommunityDetector_1.CommunityDetector(storage);
707
+ let result = await detector.detect(groupId, { minCommunitySize });
708
+ if (generateSummaries) {
709
+ const extractionCreds = await this.getCredentials('engramExtractionApi');
710
+ const model = this.getNodeParameter('summaryModel', i, '');
711
+ const concurrency = this.getNodeParameter('summaryConcurrency', i, 3);
712
+ if (!model) {
713
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Summary Model is required when Generate Summaries is enabled', { itemIndex: i });
714
+ }
715
+ const llm = new LlmClient_1.LlmClient({
716
+ apiKey: extractionCreds.apiKey,
717
+ baseUrl: extractionCreds.baseUrl,
718
+ model,
719
+ });
720
+ const summarizer = new CommunitySummarizer_1.CommunitySummarizer(llm);
721
+ result = await summarizer.summarizeAll(result, concurrency);
722
+ }
723
+ returnData.push({
724
+ json: {
725
+ total_entities: result.total_entities,
726
+ unclustered_entities: result.unclustered_entities,
727
+ detection_method: result.detection_method,
728
+ community_count: result.communities.length,
729
+ communities: result.communities.map((c) => ({
730
+ id: c.id,
731
+ label: c.label,
732
+ summary: c.summary,
733
+ entity_count: c.entity_count,
734
+ edge_count: c.edge_count,
735
+ key_entities: c.key_entities,
736
+ members: c.members.map((m) => ({
737
+ uuid: m.entity.uuid,
738
+ name: m.entity.name,
739
+ entity_type: m.entity.entity_type,
740
+ edge_count: m.edges.length,
741
+ })),
742
+ })),
743
+ },
744
+ });
745
+ break;
746
+ }
747
+ case 'export': {
748
+ const groupId = this.getNodeParameter('groupIdFilter', i, '');
749
+ const data = await storage.exportGraph(groupId || undefined);
750
+ returnData.push({ json: data });
751
+ break;
752
+ }
753
+ case 'import': {
754
+ const importData = this.getNodeParameter('importData', i);
755
+ if (!importData ||
756
+ typeof importData !== 'object' ||
757
+ !Array.isArray(importData.entities) ||
758
+ !Array.isArray(importData.edges) ||
759
+ !Array.isArray(importData.episodes)) {
760
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid import data format', {
761
+ itemIndex: i,
762
+ description: 'Import data must be a JSON object with "entities", "edges", and "episodes" arrays. Use data from a previous Export operation.',
763
+ });
764
+ }
765
+ await storage.importGraph(importData);
766
+ const graphData = importData;
767
+ returnData.push({
768
+ json: {
769
+ success: true,
770
+ operation: 'import',
771
+ imported: {
772
+ entities: graphData.entities.length,
773
+ edges: graphData.edges.length,
774
+ episodes: graphData.episodes.length,
775
+ },
776
+ },
777
+ });
778
+ break;
779
+ }
780
+ }
781
+ }
782
+ catch (error) {
783
+ if (error instanceof n8n_workflow_1.NodeOperationError)
784
+ throw error;
785
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Engram Admin error: ${error.message}`, { itemIndex: i });
786
+ }
787
+ }
788
+ }
789
+ finally {
790
+ if (backend === 'neo4j') {
791
+ await storage.close();
792
+ }
793
+ }
794
+ return [returnData.length > 0 ? returnData : [{ json: {} }]];
795
+ }
796
+ }
797
+ exports.EngramAdmin = EngramAdmin;
798
+ //# sourceMappingURL=EngramAdmin.node.js.map