@stackflo-labs/n8n-nodes-retainr 0.2.7 → 0.3.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.
- package/README.md +34 -0
- package/dist/nodes/Retainr/Retainr.node.js +111 -42
- package/dist/nodes/Retainr/retainr.svg +2 -7
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -72,6 +72,40 @@ Use the **Dedup Threshold** field (e.g., `0.95`) when storing. If a sufficiently
|
|
|
72
72
|
|
|
73
73
|
This node has `usableAsTool` enabled — you can use it directly as a tool inside n8n's **AI Agent** node, letting the agent decide when to store or recall memories autonomously.
|
|
74
74
|
|
|
75
|
+
## Demo Video
|
|
76
|
+
|
|
77
|
+
A recorded demo covering all n8n Creator Portal requirements is at:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
products/retainr/web/public/demo/n8n-demo.mp4
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Full disk path (Windows): `D:\dev\datadir\stackflo\products\retainr\web\public\demo\n8n-demo.mp4`
|
|
84
|
+
|
|
85
|
+
**To re-record** (e.g. after a node update):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# 1. Start n8n with the node pre-installed
|
|
89
|
+
cd packages/n8n-node
|
|
90
|
+
docker compose -f e2e/docker-compose.yml up --build -d
|
|
91
|
+
|
|
92
|
+
# 2. Record — no API keys needed, the script handles everything
|
|
93
|
+
cd ../../products/retainr/web
|
|
94
|
+
N8N_BASE_URL=http://127.0.0.1:5678 \
|
|
95
|
+
node scripts/record-n8n-demo.mjs
|
|
96
|
+
# → public/demo/n8n-demo-raw.webm
|
|
97
|
+
|
|
98
|
+
# 3. Post-process to MP4
|
|
99
|
+
bash ../../packages/n8n-node/demo/post-process.sh public/demo/n8n-demo-raw.webm
|
|
100
|
+
# → public/demo/n8n-demo.mp4
|
|
101
|
+
|
|
102
|
+
# 4. Tear down
|
|
103
|
+
cd ../../packages/n8n-node
|
|
104
|
+
docker compose -f e2e/docker-compose.yml down
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
See `demo/README.md` for full details.
|
|
108
|
+
|
|
75
109
|
## API Documentation
|
|
76
110
|
|
|
77
111
|
Full API reference: [retainr.dev/docs/api](https://retainr.dev/docs/api)
|
|
@@ -10,7 +10,7 @@ class Retainr {
|
|
|
10
10
|
icon: 'file:retainr.svg',
|
|
11
11
|
group: ['transform'],
|
|
12
12
|
version: 1,
|
|
13
|
-
subtitle: '={{
|
|
13
|
+
subtitle: '={{"getContext":"Get Context","store":"Store Memory","search":"Search Memories","list":"List Memories","delete":"Delete Memories","getUsage":"Get Usage"}[$parameter["operation"]] ?? $parameter["operation"]}}',
|
|
14
14
|
description: 'Store, search, and retrieve AI agent memories.',
|
|
15
15
|
defaults: {
|
|
16
16
|
name: 'Retainr',
|
|
@@ -217,6 +217,21 @@ class Retainr {
|
|
|
217
217
|
},
|
|
218
218
|
},
|
|
219
219
|
},
|
|
220
|
+
{
|
|
221
|
+
displayName: 'Namespace',
|
|
222
|
+
name: 'namespace',
|
|
223
|
+
type: 'string',
|
|
224
|
+
required: false,
|
|
225
|
+
default: '',
|
|
226
|
+
placeholder: 'acme-corp',
|
|
227
|
+
description: 'Namespace for organizing memories by customer or tenant (e.g., "acme-corp").',
|
|
228
|
+
displayOptions: {
|
|
229
|
+
show: {
|
|
230
|
+
resource: ['memory'],
|
|
231
|
+
operation: ['store'],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
},
|
|
220
235
|
{
|
|
221
236
|
displayName: 'Additional Fields',
|
|
222
237
|
name: 'storeAdditionalFields',
|
|
@@ -248,13 +263,6 @@ class Retainr {
|
|
|
248
263
|
default: '{}',
|
|
249
264
|
description: 'Arbitrary key-value pairs as JSON object.',
|
|
250
265
|
},
|
|
251
|
-
{
|
|
252
|
-
displayName: 'Namespace',
|
|
253
|
-
name: 'namespace',
|
|
254
|
-
type: 'string',
|
|
255
|
-
default: '',
|
|
256
|
-
description: 'Free-form grouping label for organizing memories.',
|
|
257
|
-
},
|
|
258
266
|
{
|
|
259
267
|
displayName: 'Tags',
|
|
260
268
|
name: 'tags',
|
|
@@ -295,6 +303,21 @@ class Retainr {
|
|
|
295
303
|
},
|
|
296
304
|
},
|
|
297
305
|
},
|
|
306
|
+
{
|
|
307
|
+
displayName: 'Namespace',
|
|
308
|
+
name: 'namespace',
|
|
309
|
+
type: 'string',
|
|
310
|
+
required: false,
|
|
311
|
+
default: '',
|
|
312
|
+
placeholder: 'acme-corp',
|
|
313
|
+
description: 'Filter memories by namespace.',
|
|
314
|
+
displayOptions: {
|
|
315
|
+
show: {
|
|
316
|
+
resource: ['memory'],
|
|
317
|
+
operation: ['search'],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
298
321
|
{
|
|
299
322
|
displayName: 'Additional Fields',
|
|
300
323
|
name: 'searchAdditionalFields',
|
|
@@ -325,13 +348,6 @@ class Retainr {
|
|
|
325
348
|
default: 50,
|
|
326
349
|
description: 'Max number of results to return.',
|
|
327
350
|
},
|
|
328
|
-
{
|
|
329
|
-
displayName: 'Namespace',
|
|
330
|
-
name: 'namespace',
|
|
331
|
-
type: 'string',
|
|
332
|
-
default: '',
|
|
333
|
-
description: 'Filter by namespace.',
|
|
334
|
-
},
|
|
335
351
|
{
|
|
336
352
|
displayName: 'Scope',
|
|
337
353
|
name: 'scope',
|
|
@@ -430,6 +446,21 @@ class Retainr {
|
|
|
430
446
|
},
|
|
431
447
|
},
|
|
432
448
|
},
|
|
449
|
+
{
|
|
450
|
+
displayName: 'Namespace',
|
|
451
|
+
name: 'namespace',
|
|
452
|
+
type: 'string',
|
|
453
|
+
required: false,
|
|
454
|
+
default: '',
|
|
455
|
+
placeholder: 'acme-corp',
|
|
456
|
+
description: 'Filter memories by namespace.',
|
|
457
|
+
displayOptions: {
|
|
458
|
+
show: {
|
|
459
|
+
resource: ['memory'],
|
|
460
|
+
operation: ['getContext'],
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
},
|
|
433
464
|
{
|
|
434
465
|
displayName: 'Additional Fields',
|
|
435
466
|
name: 'contextAdditionalFields',
|
|
@@ -460,13 +491,6 @@ class Retainr {
|
|
|
460
491
|
default: 5,
|
|
461
492
|
description: 'Maximum number of memories to include in the context (API max 20).',
|
|
462
493
|
},
|
|
463
|
-
{
|
|
464
|
-
displayName: 'Namespace',
|
|
465
|
-
name: 'namespace',
|
|
466
|
-
type: 'string',
|
|
467
|
-
default: '',
|
|
468
|
-
description: 'Filter by namespace.',
|
|
469
|
-
},
|
|
470
494
|
{
|
|
471
495
|
displayName: 'Scope',
|
|
472
496
|
name: 'scope',
|
|
@@ -517,6 +541,21 @@ class Retainr {
|
|
|
517
541
|
// ==================================================================
|
|
518
542
|
// Fields — Memory > List
|
|
519
543
|
// ==================================================================
|
|
544
|
+
{
|
|
545
|
+
displayName: 'Namespace',
|
|
546
|
+
name: 'namespace',
|
|
547
|
+
type: 'string',
|
|
548
|
+
required: false,
|
|
549
|
+
default: '',
|
|
550
|
+
placeholder: 'acme-corp',
|
|
551
|
+
description: 'Filter memories by namespace.',
|
|
552
|
+
displayOptions: {
|
|
553
|
+
show: {
|
|
554
|
+
resource: ['memory'],
|
|
555
|
+
operation: ['list'],
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
},
|
|
520
559
|
{
|
|
521
560
|
displayName: 'Additional Fields',
|
|
522
561
|
name: 'listAdditionalFields',
|
|
@@ -547,13 +586,6 @@ class Retainr {
|
|
|
547
586
|
default: 50,
|
|
548
587
|
description: 'Max number of results to return.',
|
|
549
588
|
},
|
|
550
|
-
{
|
|
551
|
-
displayName: 'Namespace',
|
|
552
|
-
name: 'namespace',
|
|
553
|
-
type: 'string',
|
|
554
|
-
default: '',
|
|
555
|
-
description: 'Filter by namespace.',
|
|
556
|
-
},
|
|
557
589
|
{
|
|
558
590
|
displayName: 'Offset',
|
|
559
591
|
name: 'offset',
|
|
@@ -622,6 +654,21 @@ class Retainr {
|
|
|
622
654
|
},
|
|
623
655
|
},
|
|
624
656
|
},
|
|
657
|
+
{
|
|
658
|
+
displayName: 'Namespace',
|
|
659
|
+
name: 'namespace',
|
|
660
|
+
type: 'string',
|
|
661
|
+
required: false,
|
|
662
|
+
default: '',
|
|
663
|
+
placeholder: 'acme-corp',
|
|
664
|
+
description: 'Filter memories by namespace.',
|
|
665
|
+
displayOptions: {
|
|
666
|
+
show: {
|
|
667
|
+
resource: ['memory'],
|
|
668
|
+
operation: ['delete'],
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
},
|
|
625
672
|
{
|
|
626
673
|
displayName: 'Additional Fields',
|
|
627
674
|
name: 'deleteAdditionalFields',
|
|
@@ -642,13 +689,6 @@ class Retainr {
|
|
|
642
689
|
default: '',
|
|
643
690
|
description: 'Filter by agent ID.',
|
|
644
691
|
},
|
|
645
|
-
{
|
|
646
|
-
displayName: 'Namespace',
|
|
647
|
-
name: 'namespace',
|
|
648
|
-
type: 'string',
|
|
649
|
-
default: '',
|
|
650
|
-
description: 'Filter by namespace.',
|
|
651
|
-
},
|
|
652
692
|
{
|
|
653
693
|
displayName: 'Session ID',
|
|
654
694
|
name: 'sessionId',
|
|
@@ -768,6 +808,7 @@ function parseTags(raw) {
|
|
|
768
808
|
async function storeMemory(i, baseUrl) {
|
|
769
809
|
const content = this.getNodeParameter('content', i);
|
|
770
810
|
const scope = this.getNodeParameter('scope', i);
|
|
811
|
+
const namespace = this.getNodeParameter('namespace', i, '');
|
|
771
812
|
const additional = this.getNodeParameter('storeAdditionalFields', i);
|
|
772
813
|
const body = { content, scope };
|
|
773
814
|
// Scope-specific IDs
|
|
@@ -780,9 +821,13 @@ async function storeMemory(i, baseUrl) {
|
|
|
780
821
|
else if (scope === 'agent') {
|
|
781
822
|
body.agent_id = this.getNodeParameter('agentId', i);
|
|
782
823
|
}
|
|
783
|
-
//
|
|
784
|
-
if (
|
|
824
|
+
// Namespace — top-level parameter takes priority, fall back to additional fields
|
|
825
|
+
if (namespace) {
|
|
826
|
+
body.namespace = namespace;
|
|
827
|
+
}
|
|
828
|
+
else if (additional.namespace) {
|
|
785
829
|
body.namespace = additional.namespace;
|
|
830
|
+
}
|
|
786
831
|
if (additional.tags)
|
|
787
832
|
body.tags = parseTags(additional.tags);
|
|
788
833
|
if (additional.ttlSeconds)
|
|
@@ -800,6 +845,7 @@ async function storeMemory(i, baseUrl) {
|
|
|
800
845
|
}
|
|
801
846
|
async function searchMemories(i, baseUrl) {
|
|
802
847
|
const query = this.getNodeParameter('query', i);
|
|
848
|
+
const namespace = this.getNodeParameter('namespace', i, '');
|
|
803
849
|
const additional = this.getNodeParameter('searchAdditionalFields', i);
|
|
804
850
|
const body = { query };
|
|
805
851
|
if (additional.scope)
|
|
@@ -810,8 +856,13 @@ async function searchMemories(i, baseUrl) {
|
|
|
810
856
|
body.user_id = additional.userId;
|
|
811
857
|
if (additional.agentId)
|
|
812
858
|
body.agent_id = additional.agentId;
|
|
813
|
-
|
|
859
|
+
// Namespace — top-level parameter takes priority, fall back to additional fields
|
|
860
|
+
if (namespace) {
|
|
861
|
+
body.namespace = namespace;
|
|
862
|
+
}
|
|
863
|
+
else if (additional.namespace) {
|
|
814
864
|
body.namespace = additional.namespace;
|
|
865
|
+
}
|
|
815
866
|
if (additional.tags)
|
|
816
867
|
body.tags = parseTags(additional.tags);
|
|
817
868
|
if (additional.limit)
|
|
@@ -823,6 +874,7 @@ async function searchMemories(i, baseUrl) {
|
|
|
823
874
|
async function getContext(i, baseUrl) {
|
|
824
875
|
const query = this.getNodeParameter('query', i);
|
|
825
876
|
const format = this.getNodeParameter('format', i);
|
|
877
|
+
const namespace = this.getNodeParameter('namespace', i, '');
|
|
826
878
|
const additional = this.getNodeParameter('contextAdditionalFields', i);
|
|
827
879
|
const body = { query, format };
|
|
828
880
|
if (additional.scope)
|
|
@@ -833,8 +885,13 @@ async function getContext(i, baseUrl) {
|
|
|
833
885
|
body.user_id = additional.userId;
|
|
834
886
|
if (additional.agentId)
|
|
835
887
|
body.agent_id = additional.agentId;
|
|
836
|
-
|
|
888
|
+
// Namespace — top-level parameter takes priority, fall back to additional fields
|
|
889
|
+
if (namespace) {
|
|
890
|
+
body.namespace = namespace;
|
|
891
|
+
}
|
|
892
|
+
else if (additional.namespace) {
|
|
837
893
|
body.namespace = additional.namespace;
|
|
894
|
+
}
|
|
838
895
|
if (additional.tags)
|
|
839
896
|
body.tags = parseTags(additional.tags);
|
|
840
897
|
if (additional.maxMemories)
|
|
@@ -844,6 +901,7 @@ async function getContext(i, baseUrl) {
|
|
|
844
901
|
return apiRequest.call(this, 'POST', baseUrl, '/v1/memories/context', body);
|
|
845
902
|
}
|
|
846
903
|
async function listMemories(i, baseUrl) {
|
|
904
|
+
const namespace = this.getNodeParameter('namespace', i, '');
|
|
847
905
|
const additional = this.getNodeParameter('listAdditionalFields', i);
|
|
848
906
|
const qs = {};
|
|
849
907
|
if (additional.scope)
|
|
@@ -854,8 +912,13 @@ async function listMemories(i, baseUrl) {
|
|
|
854
912
|
qs.user_id = additional.userId;
|
|
855
913
|
if (additional.agentId)
|
|
856
914
|
qs.agent_id = additional.agentId;
|
|
857
|
-
|
|
915
|
+
// Namespace — top-level parameter takes priority, fall back to additional fields
|
|
916
|
+
if (namespace) {
|
|
917
|
+
qs.namespace = namespace;
|
|
918
|
+
}
|
|
919
|
+
else if (additional.namespace) {
|
|
858
920
|
qs.namespace = additional.namespace;
|
|
921
|
+
}
|
|
859
922
|
if (additional.tags)
|
|
860
923
|
qs.tags = additional.tags;
|
|
861
924
|
if (additional.limit)
|
|
@@ -866,6 +929,7 @@ async function listMemories(i, baseUrl) {
|
|
|
866
929
|
}
|
|
867
930
|
async function deleteMemories(i, baseUrl) {
|
|
868
931
|
const scope = this.getNodeParameter('deleteScope', i);
|
|
932
|
+
const namespace = this.getNodeParameter('namespace', i, '');
|
|
869
933
|
const additional = this.getNodeParameter('deleteAdditionalFields', i);
|
|
870
934
|
const body = { scope };
|
|
871
935
|
if (additional.sessionId)
|
|
@@ -874,8 +938,13 @@ async function deleteMemories(i, baseUrl) {
|
|
|
874
938
|
body.user_id = additional.userId;
|
|
875
939
|
if (additional.agentId)
|
|
876
940
|
body.agent_id = additional.agentId;
|
|
877
|
-
|
|
941
|
+
// Namespace — top-level parameter takes priority, fall back to additional fields
|
|
942
|
+
if (namespace) {
|
|
943
|
+
body.namespace = namespace;
|
|
944
|
+
}
|
|
945
|
+
else if (additional.namespace) {
|
|
878
946
|
body.namespace = additional.namespace;
|
|
947
|
+
}
|
|
879
948
|
return apiRequest.call(this, 'DELETE', baseUrl, '/v1/memories', body);
|
|
880
949
|
}
|
|
881
950
|
async function getWorkspaceInfo(baseUrl) {
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60" fill="none">
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<ellipse cx="30" cy="19" rx="12" ry="5"/>
|
|
5
|
-
<path d="M18 19v9c0 2.8 5.4 5 12 5s12-2.2 12-5v-9"/>
|
|
6
|
-
<path d="M18 28v9c0 2.8 5.4 5 12 5s12-2.2 12-5v-9"/>
|
|
7
|
-
</g>
|
|
8
|
-
<circle cx="30" cy="28" r="2.5" fill="#fff"/>
|
|
2
|
+
<path d="M30 13 A17 17 0 1 1 13 30" stroke="#7C3AED" stroke-width="6.5" stroke-linecap="round" fill="none"/>
|
|
3
|
+
<circle cx="13" cy="30" r="6.5" fill="#7C3AED"/>
|
|
9
4
|
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackflo-labs/n8n-nodes-retainr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/stackflo-labs/n8n-nodes-retainr.git"
|
|
@@ -39,7 +39,10 @@
|
|
|
39
39
|
"e2e": "node e2e/integration-test.mjs",
|
|
40
40
|
"e2e:up": "docker compose -f e2e/docker-compose.yml up --build -d",
|
|
41
41
|
"e2e:down": "docker compose -f e2e/docker-compose.yml down",
|
|
42
|
-
"e2e:run": "npm run e2e:up && npm run e2e -- && npm run e2e:down"
|
|
42
|
+
"e2e:run": "npm run e2e:up && npm run e2e -- && npm run e2e:down",
|
|
43
|
+
"demo:up": "docker compose -f demo/docker-compose.demo.yml up --build -d",
|
|
44
|
+
"demo:down": "docker compose -f demo/docker-compose.demo.yml down",
|
|
45
|
+
"demo:record": "npm run demo:up && cd ../../products/retainr/web && node scripts/record-n8n-demo.mjs"
|
|
43
46
|
},
|
|
44
47
|
"publishConfig": {
|
|
45
48
|
"access": "public"
|