n8n-nodes-tembory 1.0.11 → 1.0.13

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 CHANGED
@@ -2,12 +2,16 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.0.11`.
5
+ Versao atual: `1.0.13`.
6
6
 
7
7
  O Tembory entrega contexto rico para o AI Agent sem depender apenas do historico textual da conversa. Ele combina memoria semantica, working memory, decision state, fatos estaveis do lead, historico de tools, estado operacional, action ledger, timeline de entidades, compressao de memoria, grafo, mensagens recentes e diagnosticos.
8
8
 
9
9
  Nos presets de producao, o contexto e organizado como um pacote acionavel: secoes vazias sao removidas, evidencias de tools ficam canonicas em `tool_history`/`operational_state`, e blocos como `decision_state`, `working_memory` e `memory_compression` sao compactados sem perder IDs, proximas acoes, slots, reservas, confirmacoes e bloqueios de repeticao.
10
10
 
11
+ Quando um modelo barato esta conectado ao Tembory, os presets de producao usam esse SLM para gerar um resumo ativo do contexto. Em modo `auto`, ele organiza tanto o estado atual (`working_memory`, `tool_history`, `operational_state`, `decision_state`) quanto memorias vetoriais recuperadas. Assim o primeiro turno ja pode receber highlights uteis mesmo antes de haver resultados do banco vetorial.
12
+
13
+ O resumo do SLM usa limites conservadores e cache curto por input para evitar chamadas duplicadas quando o AI Agent carrega a memoria mais de uma vez no mesmo turno.
14
+
11
15
  ## Smoke tecnico
12
16
 
13
17
  Antes de publicar uma versao, rode:
@@ -862,13 +862,15 @@ const getMemoryStore = (ctx) => {
862
862
  data.tembory.workingMemory = data.tembory.workingMemory || {};
863
863
  data.tembory.decisionState = data.tembory.decisionState || {};
864
864
  data.tembory.memoryCompression = data.tembory.memoryCompression || {};
865
+ data.tembory.connectedModelSummaryCache = data.tembory.connectedModelSummaryCache || {};
865
866
  return data.tembory;
866
867
  }
867
868
  catch {
868
- global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {} };
869
+ global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {}, connectedModelSummaryCache: {} };
869
870
  global.__temboryMemory.workingMemory = global.__temboryMemory.workingMemory || {};
870
871
  global.__temboryMemory.decisionState = global.__temboryMemory.decisionState || {};
871
872
  global.__temboryMemory.memoryCompression = global.__temboryMemory.memoryCompression || {};
873
+ global.__temboryMemory.connectedModelSummaryCache = global.__temboryMemory.connectedModelSummaryCache || {};
872
874
  return global.__temboryMemory;
873
875
  }
874
876
  };
@@ -882,6 +884,8 @@ const applyOperationalPreset = (advanced = {}) => {
882
884
  const preset = String(advanced.operationPreset || 'custom');
883
885
  const presets = {
884
886
  diagnostic: {
887
+ summarySource: 'auto',
888
+ includeConnectedModelSummary: true,
885
889
  includeContextHeader: true,
886
890
  includeSummary: true,
887
891
  includeScores: true,
@@ -906,6 +910,8 @@ const applyOperationalPreset = (advanced = {}) => {
906
910
  recentMessagesLastN: 8,
907
911
  },
908
912
  productionBalanced: {
913
+ summarySource: 'auto',
914
+ includeConnectedModelSummary: true,
909
915
  compactStateSections: true,
910
916
  includeContextHeader: true,
911
917
  includeSummary: true,
@@ -931,8 +937,12 @@ const applyOperationalPreset = (advanced = {}) => {
931
937
  recentMessagesLastN: 6,
932
938
  vectorMemoryMaxChars: 360,
933
939
  contextMaxChars: 10000,
940
+ connectedModelSummaryMaxChars: 900,
941
+ connectedModelSummaryInputMaxChars: 3200,
934
942
  },
935
943
  productionCheap: {
944
+ summarySource: 'activeContext',
945
+ includeConnectedModelSummary: true,
936
946
  compactStateSections: true,
937
947
  includeContextHeader: true,
938
948
  includeSummary: false,
@@ -958,8 +968,12 @@ const applyOperationalPreset = (advanced = {}) => {
958
968
  recentMessagesLastN: 4,
959
969
  vectorMemoryMaxChars: 260,
960
970
  contextMaxChars: 7000,
971
+ connectedModelSummaryMaxChars: 700,
972
+ connectedModelSummaryInputMaxChars: 2400,
961
973
  },
962
974
  productionNano: {
975
+ summarySource: 'auto',
976
+ includeConnectedModelSummary: true,
963
977
  compactForAgent: true,
964
978
  includeContextHeader: true,
965
979
  includeSummary: true,
@@ -986,8 +1000,12 @@ const applyOperationalPreset = (advanced = {}) => {
986
1000
  recentMessagesLastN: 2,
987
1001
  vectorMemoryMaxChars: 220,
988
1002
  contextMaxChars: 6000,
1003
+ connectedModelSummaryMaxChars: 450,
1004
+ connectedModelSummaryInputMaxChars: 1500,
989
1005
  },
990
1006
  audit: {
1007
+ summarySource: 'auto',
1008
+ includeConnectedModelSummary: true,
991
1009
  includeContextHeader: true,
992
1010
  includeSummary: true,
993
1011
  includeScores: true,
@@ -1524,6 +1542,87 @@ const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], ma
1524
1542
  .filter((text) => !hasStructuredTools || !/^\[tool_events_extracted\]/i.test(text))
1525
1543
  .slice(0, maxItems);
1526
1544
  };
1545
+ const modelResponseText = (response) => {
1546
+ const content = response === null || response === void 0 ? void 0 : response.content;
1547
+ if (typeof content === 'string')
1548
+ return content;
1549
+ if (Array.isArray(content)) {
1550
+ return content.map((item) => {
1551
+ if (typeof item === 'string')
1552
+ return item;
1553
+ if (item && typeof item === 'object' && typeof item.text === 'string')
1554
+ return item.text;
1555
+ if (item && typeof item === 'object' && typeof item.content === 'string')
1556
+ return item.content;
1557
+ return '';
1558
+ }).filter(Boolean).join('\n');
1559
+ }
1560
+ if (typeof (response === null || response === void 0 ? void 0 : response.text) === 'string')
1561
+ return response.text;
1562
+ return response === undefined || response === null ? '' : String(response);
1563
+ };
1564
+ const cleanModelSummaryText = (value, max = 900) => {
1565
+ let text = modelResponseText(value).trim();
1566
+ if (!text)
1567
+ return '';
1568
+ try {
1569
+ const parsed = JSON.parse(text);
1570
+ text = modelResponseText(parsed) || (Array.isArray(parsed) ? parsed.map(modelResponseText).filter(Boolean).join('\n') : '');
1571
+ if (!text && parsed && typeof parsed === 'object') {
1572
+ const bullets = parsed.highlights || parsed.current_summary || parsed.summary || parsed.facts;
1573
+ if (Array.isArray(bullets))
1574
+ text = bullets.map((item) => `- ${typeof item === 'string' ? item : safeStringify(item)}`).join('\n');
1575
+ }
1576
+ }
1577
+ catch { }
1578
+ text = text
1579
+ .replace(/^\s*\[\s*\{\s*"type"\s*:\s*"text"\s*,\s*"text"\s*:\s*"/, '')
1580
+ .replace(/"\s*,\s*"annotations"\s*:\s*\[\s*\]\s*\}\s*\]\s*$/, '')
1581
+ .replace(/\\n/g, '\n')
1582
+ .trim();
1583
+ return truncate(text, max);
1584
+ };
1585
+ const buildConnectedModelSummaryInput = ({ query, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, toolHistory, recentMessages, vectorMemories, highlights, adv }) => {
1586
+ const source = String(adv.summarySource || 'auto');
1587
+ if (source === 'off' || source === 'disabled' || adv.includeConnectedModelSummary === false)
1588
+ return '';
1589
+ const includeVectors = source === 'auto' || source === 'vectorOnly' || source === 'vectors';
1590
+ const includeActive = source === 'auto' || source === 'activeContext' || source === 'active';
1591
+ const payload = cleanContextValue({
1592
+ query: String(query || ''),
1593
+ active_context: includeActive ? {
1594
+ profile_facts: renderProfileFacts(profileFacts),
1595
+ working_memory: compactWorkingMemoryForAgent(workingMemory || {}),
1596
+ decision_state: compactDecisionStateForAgent(decisionState || {}),
1597
+ operational_state: compactOperationalStateForAgent(operationalState || {}),
1598
+ memory_compression: compactMemoryCompressionForAgent(memoryCompression || {}),
1599
+ tool_history: compactToolHistoryForAgent(toolHistory || [], adv.summaryToolHistoryLastN || adv.toolHistoryLastN || 6, adv.includeToolResults !== false),
1600
+ recent_messages: pruneByLimit(recentMessages || [], adv.summaryRecentMessagesLastN || 4).map((message) => cleanContextValue({
1601
+ role: message.role,
1602
+ content: truncate(message.content, 240),
1603
+ at: message.at,
1604
+ })),
1605
+ highlights: (highlights || []).slice(0, adv.summaryHighlightsMaxItems || 4),
1606
+ } : undefined,
1607
+ vector_memories: includeVectors ? compactVectorMemoriesForAgent(vectorMemories || [], toolHistory || [], adv.summaryMaxFacts || 4) : undefined,
1608
+ });
1609
+ const hasActiveSignal = includeActive && Boolean((payload.active_context && Object.keys(payload.active_context).length) || payload.query);
1610
+ const hasVectorSignal = includeVectors && Array.isArray(payload.vector_memories) && payload.vector_memories.length > 0;
1611
+ if (!hasActiveSignal && !hasVectorSignal)
1612
+ return '';
1613
+ return truncate(safeStringify(payload), Number(adv.connectedModelSummaryInputMaxChars || 2000));
1614
+ };
1615
+ const invokeConnectedModelSummary = async (connectedLanguageModel, summaryInput, adv = {}) => {
1616
+ if (!connectedLanguageModel || typeof connectedLanguageModel.invoke !== 'function' || !summaryInput)
1617
+ return '';
1618
+ const response = await connectedLanguageModel.invoke([
1619
+ toBaseMessage({
1620
+ role: 'user',
1621
+ content: `Organize the Tembory memory context for the next agent turn. Return only 3 to 6 short Portuguese bullets. No JSON, no markdown table, no field-by-field dump. Preserve important IDs, dates, tool names, next action, do-not-repeat instructions, and contradictions. Do not invent facts.\n\nContext:\n${summaryInput}`,
1622
+ }),
1623
+ ]);
1624
+ return cleanModelSummaryText(response, Number(adv.connectedModelSummaryMaxChars || 700));
1625
+ };
1527
1626
  const contextSizeOfMessages = (messages = []) => {
1528
1627
  const perMessage = (messages || []).map((message, index) => {
1529
1628
  const content = String(message.content || '');
@@ -1601,7 +1700,7 @@ const wrapTemboryMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1601
1700
  return target[prop];
1602
1701
  },
1603
1702
  });
1604
- const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, actionLedger, entityTimeline, vectorMemories, recentMessages, toolHistory, highlights, graph, diagnostics, adv }) => {
1703
+ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, actionLedger, entityTimeline, vectorMemories, recentMessages, toolHistory, highlights, graph, diagnostics, connectedModelSummary, adv }) => {
1605
1704
  const includeHeader = adv.includeContextHeader !== false;
1606
1705
  const includeSummary = adv.includeSummary !== false;
1607
1706
  const includeScores = adv.includeScores !== false;
@@ -1622,6 +1721,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1622
1721
  const summary = compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.summaryMaxFacts || 3));
1623
1722
  sections.push({ section: 'summary', title: 'Summary', value: summary.length ? summary : null, why_null: summary.length ? undefined : 'no non-tool vector memories to summarize' });
1624
1723
  }
1724
+ if (connectedModelSummary && adv.includeConnectedModelSummary !== false) {
1725
+ sections.push({ section: 'connected_model_summary', title: 'SLM summary', value: connectedModelSummary });
1726
+ }
1625
1727
  sections.push({
1626
1728
  section: 'working_memory',
1627
1729
  title: 'Working memory',
@@ -1687,6 +1789,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1687
1789
  : vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 360)).filter(Boolean);
1688
1790
  sections.push({ section: 'summary', title: 'Summary', value: summary.length ? summary : null, why_null: summary.length ? undefined : 'no vector memories to summarize' });
1689
1791
  }
1792
+ if (connectedModelSummary && adv.includeConnectedModelSummary !== false) {
1793
+ sections.push({ section: 'connected_model_summary', title: 'SLM summary', value: connectedModelSummary });
1794
+ }
1690
1795
  sections.push({
1691
1796
  section: 'working_memory',
1692
1797
  title: 'Working memory',
@@ -1995,6 +2100,22 @@ class Mem0Memory {
1995
2100
  { displayName: 'Organizar Seções de Produção', name: 'compactStateSections', type: 'boolean', default: false, description: 'Renderiza blocos de produção com campos canônicos, remove seções vazias e evita duplicar evidências já presentes em tool_history/operational_state.' },
1996
2101
  { displayName: 'Máximo de Caracteres do Contexto', name: 'contextMaxChars', type: 'number', default: 6000, description: 'Limite final aplicado ao contexto entregue ao agente em modos estruturados de produção.' },
1997
2102
  { displayName: 'Incluir Resumo', name: 'includeSummary', type: 'boolean', default: true },
2103
+ { displayName: 'Incluir Resumo do SLM', name: 'includeConnectedModelSummary', type: 'boolean', default: true, description: 'Usa o modelo barato conectado ao Tembory para organizar contexto ativo e/ou memórias vetoriais em bullets curtos.' },
2104
+ {
2105
+ displayName: 'Fonte do Resumo do SLM',
2106
+ name: 'summarySource',
2107
+ type: 'options',
2108
+ options: [
2109
+ { name: 'Automático', value: 'auto' },
2110
+ { name: 'Contexto Ativo', value: 'activeContext' },
2111
+ { name: 'Somente Vetores', value: 'vectorOnly' },
2112
+ { name: 'Desligado', value: 'off' },
2113
+ ],
2114
+ default: 'auto',
2115
+ description: 'Define se o SLM resume working memory/tool state, memórias vetoriais, ambos ou nada.',
2116
+ },
2117
+ { displayName: 'Máximo de Caracteres de Entrada do SLM', name: 'connectedModelSummaryInputMaxChars', type: 'number', default: 3000 },
2118
+ { displayName: 'Máximo de Caracteres do Resumo do SLM', name: 'connectedModelSummaryMaxChars', type: 'number', default: 900 },
1998
2119
  { displayName: 'Máximo de Fatos no Resumo', name: 'summaryMaxFacts', type: 'number', default: 4 },
1999
2120
  { displayName: 'Incluir Scores', name: 'includeScores', type: 'boolean', default: true },
2000
2121
  { displayName: 'Incluir Diagnóstico', name: 'includeDiagnostics', type: 'boolean', default: false },
@@ -2623,34 +2744,6 @@ class Mem0Memory {
2623
2744
  }
2624
2745
  catch { }
2625
2746
  }
2626
- let connectedModelSummary = '';
2627
- if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && vectorMemories.length && adv.includeSummary !== false) {
2628
- try {
2629
- const facts = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 500)).filter(Boolean).join('\n') || '(no vector memories found)';
2630
- if (facts) {
2631
- const response = await connectedLanguageModel.invoke([
2632
- toBaseMessage({
2633
- role: 'user',
2634
- content: `Summarize available Tembory context as concise factual bullets. If there are no memories, return exactly "(empty)".\n\nQuery: ${String(query || '')}\n\nMemories:\n${truncate(facts, 3000)}`,
2635
- }),
2636
- ]);
2637
- connectedModelSummary = truncate(response?.content || response?.text || String(response || ''), 1200);
2638
- connectedAi.languageModelSummary = true;
2639
- }
2640
- }
2641
- catch (error) {
2642
- connectedAi.errors.push(`languageModel.invoke: ${error.message || String(error)}`);
2643
- }
2644
- }
2645
- const diagnostics = {
2646
- vectorMemories: vectorMemories.length,
2647
- recentMessages: recentMessages.length,
2648
- toolHistory: toolHistory.length,
2649
- graph: graph.length,
2650
- project: project || null,
2651
- memoryNamespace: key,
2652
- connectedAi,
2653
- };
2654
2747
  const includeToolResults = adv.includeToolResults !== false;
2655
2748
  const operationalState = deriveOperationalState(toolHistory, renderProfileFacts(profileFacts), recentMessages, includeToolResults);
2656
2749
  const actionLedger = deriveActionLedger(toolHistory, adv.actionLedgerMaxItems || adv.toolHistoryLastN || 20, includeToolResults);
@@ -2677,6 +2770,57 @@ class Mem0Memory {
2677
2770
  vectorMemories,
2678
2771
  maxItems: adv.compressionMaxItems || 6,
2679
2772
  });
2773
+ let connectedModelSummary = '';
2774
+ if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && adv.includeSummary !== false && adv.includeConnectedModelSummary !== false) {
2775
+ try {
2776
+ const summaryInput = buildConnectedModelSummaryInput({
2777
+ query,
2778
+ profileFacts,
2779
+ workingMemory,
2780
+ decisionState,
2781
+ memoryCompression,
2782
+ operationalState,
2783
+ toolHistory,
2784
+ recentMessages,
2785
+ vectorMemories,
2786
+ highlights,
2787
+ adv,
2788
+ });
2789
+ const cacheKey = summaryInput ? stableHash({
2790
+ key,
2791
+ source: adv.summarySource || 'auto',
2792
+ input: summaryInput,
2793
+ max: adv.connectedModelSummaryMaxChars || 700,
2794
+ }) : '';
2795
+ const cached = cacheKey ? store.connectedModelSummaryCache[cacheKey] : null;
2796
+ if (cached && cached.summary && Date.now() - Number(cached.at || 0) < Number(adv.connectedModelSummaryCacheTTLSeconds || 60) * 1000) {
2797
+ connectedModelSummary = cached.summary;
2798
+ connectedAi.languageModelSummaryCached = true;
2799
+ }
2800
+ else {
2801
+ connectedModelSummary = await invokeConnectedModelSummary(connectedLanguageModel, summaryInput, adv);
2802
+ if (cacheKey && connectedModelSummary) {
2803
+ store.connectedModelSummaryCache[cacheKey] = { summary: connectedModelSummary, at: Date.now() };
2804
+ const keys = Object.keys(store.connectedModelSummaryCache);
2805
+ for (const oldKey of keys.slice(0, Math.max(0, keys.length - Number(adv.connectedModelSummaryCacheMaxItems || 50))))
2806
+ delete store.connectedModelSummaryCache[oldKey];
2807
+ }
2808
+ }
2809
+ connectedAi.languageModelSummary = Boolean(connectedModelSummary);
2810
+ }
2811
+ catch (error) {
2812
+ connectedAi.errors.push(`languageModel.invoke: ${error.message || String(error)}`);
2813
+ }
2814
+ }
2815
+ const diagnostics = {
2816
+ vectorMemories: vectorMemories.length,
2817
+ recentMessages: recentMessages.length,
2818
+ toolHistory: toolHistory.length,
2819
+ graph: graph.length,
2820
+ project: project || null,
2821
+ memoryNamespace: key,
2822
+ connectedAi,
2823
+ };
2680
2824
  const contextHealth = deriveContextHealth({
2681
2825
  userId: key,
2682
2826
  project,
@@ -2714,6 +2858,7 @@ class Mem0Memory {
2714
2858
  highlights,
2715
2859
  graph,
2716
2860
  diagnostics,
2861
+ connectedModelSummary,
2717
2862
  adv,
2718
2863
  });
2719
2864
  diagnostics.contextSize = contextSizeOfMessages(payload);
@@ -2949,4 +3094,7 @@ exports.__private = {
2949
3094
  compactToolResult,
2950
3095
  compactToolHistoryForAgent,
2951
3096
  compactOperationalStateForAgent,
3097
+ buildConnectedModelSummaryInput,
3098
+ cleanModelSummaryText,
3099
+ invokeConnectedModelSummary,
2952
3100
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
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",