n8n-nodes-tembory 1.0.43 → 1.0.45

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.
@@ -632,6 +632,36 @@ const encodeToolLedger = (tools = [], threadId) => {
632
632
  })),
633
633
  })}`;
634
634
  };
635
+ const encodeTurnArchive = ({ threadId, messages = [], tools = [], workingMemory = {}, decisionState = {}, memoryCompression = {}, operationalState = {} }) => {
636
+ const chronological = sortConversationChronological(messages || []);
637
+ const orderedTools = sortToolHistory(tools || []);
638
+ return `Tembory long-term turn archive. Use vector search only when short-term SLM state and recent transcript are insufficient. ${safeStringify({
639
+ marker: 'tembory_turn_archive_v1',
640
+ thread_id: threadId,
641
+ generated_at: nowIso(),
642
+ conversation: chronological.map((msg) => ({
643
+ role: msg.role || 'user',
644
+ content: msg.content || '',
645
+ at: msg.at || nowIso(),
646
+ })),
647
+ tools: orderedTools.map((tool, index) => ({
648
+ id: tool.id || tool.callId || tool.call_id || '',
649
+ turn_id: tool.turnId || tool.turn_id || '',
650
+ sequence: tool.sequence || index + 1,
651
+ name: tool.name || tool.tool || tool.toolName || '',
652
+ reason: tool.reason || tool.source || '',
653
+ input: tool.input || '',
654
+ ok: tool.ok !== false,
655
+ output: tool.result || '',
656
+ at: tool.at || nowIso(),
657
+ source: tool.source || 'n8n',
658
+ })),
659
+ working_memory: workingMemory,
660
+ decision_state: decisionState,
661
+ memory_compression: memoryCompression,
662
+ operational_state: operationalState,
663
+ })}`;
664
+ };
635
665
  const parseRecentMessageMarker = (text) => {
636
666
  if (!text || typeof text !== 'string' || !text.startsWith(RECENT_MESSAGE_MARKER))
637
667
  return null;
@@ -720,6 +750,23 @@ const parseConversationLedgerMarker = (text) => {
720
750
  return [];
721
751
  }
722
752
  };
753
+ const parseTurnArchive = (text) => {
754
+ if (!text || typeof text !== 'string')
755
+ return null;
756
+ const marker = '"marker":"tembory_turn_archive_v1"';
757
+ const markerIndex = text.indexOf(marker);
758
+ if (markerIndex < 0)
759
+ return null;
760
+ const jsonStart = text.lastIndexOf('{', markerIndex);
761
+ if (jsonStart < 0)
762
+ return null;
763
+ try {
764
+ return JSON.parse(text.slice(jsonStart));
765
+ }
766
+ catch {
767
+ return null;
768
+ }
769
+ };
723
770
  const recentMessageFromMemory = (item) => {
724
771
  const meta = metadataOf(item);
725
772
  const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
@@ -739,6 +786,16 @@ const recentMessageFromMemory = (item) => {
739
786
  const recentMessagesFromMemory = (item) => {
740
787
  const meta = metadataOf(item);
741
788
  const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
789
+ const archive = parseTurnArchive(content);
790
+ if (archive && Array.isArray(archive.conversation)) {
791
+ return archive.conversation
792
+ .filter((msg) => msg && msg.content)
793
+ .map((msg) => ({
794
+ role: msg.role || 'user',
795
+ content: truncate(msg.content, 2000),
796
+ at: msg.at || archive.generated_at || nowIso(),
797
+ }));
798
+ }
742
799
  const ledger = parseConversationLedgerMarker(content);
743
800
  if (ledger.length)
744
801
  return ledger;
@@ -803,6 +860,22 @@ const toolHistoryFromMemory = (item) => {
803
860
  const explicitToolHistoryItemsFromMemory = (item) => {
804
861
  const meta = metadataOf(item);
805
862
  const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
863
+ const archive = parseTurnArchive(content);
864
+ if (archive && Array.isArray(archive.tools)) {
865
+ return archive.tools
866
+ .filter((tool) => tool && tool.name)
867
+ .map((tool, index) => ({
868
+ id: tool.id || tool.call_id || tool.callId || '',
869
+ turnId: tool.turn_id || tool.turnId || '',
870
+ sequence: tool.sequence || index + 1,
871
+ name: String(tool.name),
872
+ input: tool.input === undefined ? '' : String(tool.input),
873
+ ok: tool.ok !== false,
874
+ result: truncate(tool.output || tool.result || '', 1000),
875
+ at: tool.at || archive.generated_at || nowIso(),
876
+ source: tool.source || 'turn_archive',
877
+ }));
878
+ }
806
879
  const ledger = parseToolLedgerMarker(content);
807
880
  if (ledger.length)
808
881
  return ledger;
@@ -1125,7 +1198,7 @@ const applyOperationalPreset = (advanced = {}) => {
1125
1198
  enableTransientSummaryCache: true,
1126
1199
  transientSummaryCacheTTLSeconds: 300,
1127
1200
  compactStateSections: true,
1128
- useVectorMemory: true,
1201
+ useVectorMemory: false,
1129
1202
  persistBackendMemories: true,
1130
1203
  includeRecentMessageProbe: false,
1131
1204
  includeToolLedgerProbe: false,
@@ -2598,7 +2671,7 @@ class Mem0Memory {
2598
2671
  { displayName: 'Máximo de Relações', name: 'maxRelations', type: 'number', default: 10 },
2599
2672
  { displayName: 'Incluir Action Ledger', name: 'includeActionLedger', type: 'boolean', default: true },
2600
2673
  { displayName: 'Incluir Entity Timeline', name: 'includeEntityTimeline', type: 'boolean', default: false },
2601
- { displayName: 'Usar Memória Vetorial no Caminho Quente', name: 'useVectorMemory', type: 'boolean', default: true },
2674
+ { displayName: 'Usar Memória Vetorial no Caminho Quente', name: 'useVectorMemory', type: 'boolean', default: false },
2602
2675
  { displayName: 'Persistir Memórias no Backend', name: 'persistBackendMemories', type: 'boolean', default: true },
2603
2676
  { displayName: 'Probe Vetorial de Mensagens Recentes', name: 'includeRecentMessageProbe', type: 'boolean', default: false },
2604
2677
  { displayName: 'Probe Vetorial de Tool Ledger', name: 'includeToolLedgerProbe', type: 'boolean', default: false },
@@ -2695,7 +2768,7 @@ class Mem0Memory {
2695
2768
  { displayName: 'App ID', name: 'appId', type: 'string', default: '' },
2696
2769
  { displayName: 'Run ID', name: 'runId', type: 'string', default: '' },
2697
2770
  { displayName: 'Top K', name: 'topK', type: 'number', typeOptions: { minValue: 1 }, default: 25, description: 'Quantidade de memórias a recuperar (modos semânticos e limites de recentes)' },
2698
- { displayName: 'Usar Memória Vetorial no Caminho Quente', name: 'useVectorMemory', type: 'boolean', default: true, description: 'Quando ligado, usa uma busca BYO mínima para recuperar memórias persistidas; probes/grafo continuam separados.' },
2771
+ { displayName: 'Usar Memória Vetorial no Caminho Quente', name: 'useVectorMemory', type: 'boolean', default: false, description: 'Quando ligado, usa busca BYO sob demanda para recuperar histórico longo. O padrão mantém o contexto quente em transcript + SLM e apenas arquiva o turno no vetor.' },
2699
2772
  { displayName: 'Persistir Memórias no Backend', name: 'persistBackendMemories', type: 'boolean', default: true, description: 'Quando desligado, mantém o estado no workflow/static data para reduzir latência.' },
2700
2773
  { displayName: 'Rerank', name: 'rerank', type: 'boolean', default: true, description: 'Reordenação por relevância (modos semânticos)', displayOptions: { show: { '/retrievalMode': ['semantic', 'semanticV2', 'hybrid'] } } },
2701
2774
  { displayName: 'Fields (lista separada por vírgula)', name: 'fields', type: 'string', default: '', description: 'Campos específicos a retornar da API (modos semânticos)', displayOptions: { show: { '/retrievalMode': ['semantic', 'semanticV2', 'hybrid'] } } },
@@ -3009,69 +3082,27 @@ class Mem0Memory {
3009
3082
  const connectedEmbedding = await getConnectedEmbedding(this, itemIndex);
3010
3083
  if (connectedEmbedding) {
3011
3084
  const ids = { user_id: body.user_id, agent_id: body.agent_id, run_id: body.run_id };
3012
- const clientMemories = [];
3013
- for (const message of messages) {
3014
- clientMemories.push(await createClientVectorMemory(connectedEmbedding, message.content, {
3015
- kind: 'conversation_message',
3016
- role: message.role,
3017
- thread_id: threadId,
3018
- project: project || undefined,
3019
- at: nowIso(),
3020
- source: 'n8n_connected_embedding',
3021
- }, ids));
3022
- }
3023
- if (adv.includeRecentMessages !== false && recentForMem0.length) {
3024
- for (const recent of recentForMem0) {
3025
- clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeRecentMessage(recent, threadId), {
3026
- kind: 'recent_message',
3027
- role: recent.role,
3028
- content: recent.content,
3029
- at: recent.at,
3030
- thread_id: threadId,
3031
- project: project || undefined,
3032
- source: 'n8n_connected_embedding',
3033
- }, ids));
3034
- }
3035
- clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeConversationLedger(recentForTurn, threadId), {
3036
- kind: 'conversation_ledger',
3037
- thread_id: threadId,
3038
- project: project || undefined,
3039
- source: 'n8n_connected_embedding',
3040
- generated_at: nowIso(),
3041
- }, ids));
3042
- }
3043
- if (adv.persistToolFactsToMem0 && toolCalls.length) {
3044
- const facts = toolCalls.map((tool) => `Tool ${tool.name} input=${tool.input}${tool.result ? ` result=${tool.result}` : ''}`).join('\n');
3045
- clientMemories.push(await createClientVectorMemory(connectedEmbedding, `Tool facts (read-only):\n${truncate(facts, 2000)}`, {
3046
- kind: 'tool_facts',
3047
- source: 'n8n_connected_embedding',
3048
- }, ids));
3049
- }
3050
- if (adv.includeToolHistory !== false && toolCalls.length) {
3051
- for (const tool of toolCalls) {
3052
- clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolCall(tool, threadId), {
3053
- kind: 'tool_history',
3054
- id: tool.id,
3055
- sequence: tool.sequence,
3056
- turn_id: tool.turnId,
3057
- name: tool.name,
3058
- input: tool.input,
3059
- ok: tool.ok,
3060
- result: tool.result,
3061
- at: tool.at,
3062
- source: tool.source || 'n8n_connected_embedding',
3063
- thread_id: threadId,
3064
- }, ids));
3065
- }
3066
- clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolLedger(toolHistoryForTurn, threadId), {
3067
- kind: 'tool_ledger',
3085
+ const archiveText = encodeTurnArchive({
3086
+ threadId,
3087
+ messages: recentForTurn,
3088
+ tools: toolHistoryForTurn,
3089
+ workingMemory: workingMemoryForTurn,
3090
+ decisionState: decisionStateForTurn,
3091
+ memoryCompression: compressionForTurn,
3092
+ operationalState: operationalStateForTurn,
3093
+ });
3094
+ await saveClientVectorMemories(this, [
3095
+ await createClientVectorMemory(connectedEmbedding, archiveText, {
3096
+ kind: 'turn_archive',
3068
3097
  thread_id: threadId,
3069
3098
  project: project || undefined,
3070
3099
  source: 'n8n_connected_embedding',
3071
3100
  generated_at: nowIso(),
3072
- }, ids));
3073
- }
3074
- await saveClientVectorMemories(this, clientMemories, ids);
3101
+ message_count: recentForTurn.length,
3102
+ tool_count: toolHistoryForTurn.length,
3103
+ latest_tool: toolHistoryForTurn.at(-1)?.name || null,
3104
+ }, ids),
3105
+ ], ids);
3075
3106
  if (adv.includeRecentMessages !== false && recentForMem0.length) {
3076
3107
  for (const recent of recentForMem0) {
3077
3108
  await safePersistLegacyMemory(this, {
@@ -3296,7 +3327,7 @@ class Mem0Memory {
3296
3327
  catch (error) {
3297
3328
  connectedAi.errors.push(`languageModel: ${error.message || String(error)}`);
3298
3329
  }
3299
- if (vectorMemoryEnabled) {
3330
+ if (vectorMemoryEnabled || backendPersistenceEnabled) {
3300
3331
  try {
3301
3332
  connectedEmbedding = await this.getInputConnectionData(n8n_workflow_1.NodeConnectionTypes.AiEmbedding, itemIndex);
3302
3333
  connectedAi.embedding = true;
@@ -3305,15 +3336,8 @@ class Mem0Memory {
3305
3336
  connectedAi.errors.push(`embedding: ${error.message || String(error)}`);
3306
3337
  }
3307
3338
  }
3308
- if (connectedEmbedding && typeof connectedEmbedding.embedQuery === 'function' && String(query || '').trim()) {
3309
- try {
3310
- await embedQueryCached(connectedEmbedding, String(query));
3311
- connectedAi.embeddingQuery = true;
3312
- }
3313
- catch (error) {
3314
- connectedAi.errors.push(`embedding.embedQuery: ${error.message || String(error)}`);
3315
- }
3316
- }
3339
+ if (connectedEmbedding && typeof connectedEmbedding.embedQuery === 'function' && String(query || '').trim())
3340
+ connectedAi.embeddingQuery = vectorMemoryEnabled;
3317
3341
  try {
3318
3342
  this.logger?.debug('Tembory loading memory variables before model invocation', {
3319
3343
  itemIndex,
@@ -3325,6 +3349,28 @@ class Mem0Memory {
3325
3349
  catch { }
3326
3350
  let payload;
3327
3351
  let vectorMemories = [];
3352
+ if (!vectorMemoryEnabled && backendPersistenceEnabled && connectedEmbedding && String(query || '').trim()) {
3353
+ try {
3354
+ const archiveBody = {
3355
+ user_id: key,
3356
+ agent_id: adv.agentId ? String(adv.agentId) : undefined,
3357
+ run_id: adv.runId ? String(adv.runId) : undefined,
3358
+ top_k: Math.max(Number(adv.lastN || 0), Number(adv.maxReturn || 12), 12),
3359
+ filters: { kind: 'turn_archive' },
3360
+ };
3361
+ const archiveRes = await searchClientVectorMemories(this, connectedEmbedding, `latest turn archive for thread ${threadId}\n${query}`, archiveBody);
3362
+ vectorMemories = normalizeResults(archiveRes).map((memory) => withTemboryScore(memory, {
3363
+ semanticScore: scoreOf(memory),
3364
+ source: 'turn_archive_lookup',
3365
+ }));
3366
+ connectedAi.embedding = true;
3367
+ connectedAi.embeddingQuery = true;
3368
+ connectedAi.turnArchiveLookup = true;
3369
+ }
3370
+ catch (error) {
3371
+ connectedAi.errors.push(`turnArchiveLookup: ${error.message || String(error)}`);
3372
+ }
3373
+ }
3328
3374
  if (vectorMemoryEnabled && (retrievalMode === 'semantic' || retrievalMode === 'semanticV2' || retrievalMode === 'hybrid')) {
3329
3375
  const body = { query: String(query || '') };
3330
3376
  // IDs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "description": "Tembory node for n8n AI Agents with profile, tools, timeline, graph and semantic memory",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tembory.com",