n8n-nodes-tembory 1.1.30 → 1.1.31

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,14 +2,37 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.1.2`.
5
+ Versao atual: `1.1.31`.
6
6
 
7
- ## 1.1.2
7
+ ## 1.1.31
8
8
 
9
- - Torna a entrada `LLM` obrigatoria no node.
10
- - Remove opcoes visiveis de vetor, grafo e configuracao legada da interface do node.
11
- - Forca o resumo SLM como parte do contrato de producao.
12
- - Mantem o registro de tools, inputs, outputs, status, action ledger, working memory e decision state por fluxo operacional.
9
+ - Adiciona `agentContextBudget` no payload visual do n8n com caracteres e tokens aproximados injetados no Agent.
10
+ - Mostra `loadedSections` fora do bloco de diagnostico para facilitar debug humano.
11
+ - Melhora o parsing de outputs comuns de tools do n8n, incluindo envelopes `json`, `data`, `body` e `result`.
12
+ - Mantem `outputParsed`, `capturedAt`, `toolTimestamp`, `toolStatus`, conversa e save visual por execucao.
13
+
14
+ ## Contrato operacional atual
15
+
16
+ O Tembory e memoria operacional generica. Ele nao decide regra de negocio, nao escolhe a tool pelo dominio do cliente e nao substitui o prompt do agente.
17
+
18
+ O que ele deve guardar e devolver:
19
+
20
+ - Mensagens recentes por `threadId` ou `sessionId`.
21
+ - Tool calls genericas, independente do nome da tool.
22
+ - Input enviado para a tool.
23
+ - Output bruto retornado pela tool.
24
+ - Output util em `outputParsed`, quando o formato permitir parsing seguro.
25
+ - Status da tool.
26
+ - `capturedAt`, que e quando o Tembory capturou o evento.
27
+ - `toolTimestamp`, que e o timestamp retornado pela propria tool quando existir.
28
+ - `actionLedger`, `workingMemory`, `decisionState`, `operationalState` e resumo ativo.
29
+
30
+ O que ele nao deve fazer:
31
+
32
+ - Inventar politica de tool.
33
+ - Considerar uma acao lateral concluida sem tool bem sucedida no turno correto.
34
+ - Transformar observacao ou inferencia em regra obrigatoria.
35
+ - Esconder falha de save no payload visual.
13
36
 
14
37
  ## Visao geral
15
38
 
@@ -43,13 +66,52 @@ Use a credencial `Tembory API` com a API key gerada no SaaS. A API key identific
43
66
 
44
67
  O campo `Preset Operacional` preenche defaults de producao:
45
68
 
46
- - `Produção Rápida (Thread + SLM)`: padrao recomendado.
47
- - `Produção Balanceada`: mais contexto, ainda compacto.
48
- - `Produção Econômica`: reduz resultados e secoes para economizar tokens, mantendo SLM obrigatoria.
49
- - `Produção Nano/SLM`: otimizado para modelo pequeno.
50
- - `Diagnóstico Completo`: teste e auditoria.
69
+ - `Producao Rapida (Thread + SLM)`: padrao recomendado para a maioria dos agentes.
70
+ - `Producao Balanceada`: mais contexto, ainda compacto.
71
+ - `Producao Economica`: reduz resultados e secoes para economizar tokens.
72
+ - `Producao Nano/SLM`: otimizado para modelo pequeno.
73
+ - `Diagnostico Completo`: teste e auditoria.
51
74
  - `Auditoria`: payload detalhado para depuracao.
52
75
 
76
+ Recomendacao pratica:
77
+
78
+ - Use `Producao Rapida` em producao normal.
79
+ - Use `Producao Balanceada` quando o agente precisar comparar mais historico.
80
+ - Use `Auditoria` ou `Diagnostico Completo` apenas enquanto estiver depurando.
81
+
82
+ ## Uso com `$fromAI()`
83
+
84
+ Em tools conectadas ao AI Agent, o n8n permite que o modelo preencha parametros dinamicamente com `$fromAI()`. Isso e responsabilidade do Agent e da tool do n8n. O Tembory apenas fornece contexto operacional para o Agent, incluindo conversa, tools anteriores, inputs, outputs e timestamps.
85
+
86
+ Referencia oficial do n8n: https://docs.n8n.io/advanced-ai/examples/using-the-fromai-function/
87
+
88
+ ## Payload visual do n8n
89
+
90
+ No output do node durante uma execucao, o Tembory mostra um resumo compacto para humano:
91
+
92
+ - `memorySummary.intent`: classificacao operacional leve do turno.
93
+ - `memorySummary.conversation`: contagem e ultimas mensagens.
94
+ - `memorySummary.conversationTimeline`: timeline curta com timestamps.
95
+ - `memorySummary.toolEvents`: ultimas tool calls com input, output, `outputParsed`, status e timestamps.
96
+ - `memorySummary.lastSave`: ultimo save persistido.
97
+ - `memorySummary.loadedSections`: quais blocos foram injetados no contexto do Agent.
98
+ - `memorySummary.agentContextBudget`: tamanho aproximado do contexto enviado ao Agent.
99
+
100
+ Exemplo de budget:
101
+
102
+ ```json
103
+ {
104
+ "agentContextBudget": {
105
+ "messages": 1,
106
+ "chars": 4200,
107
+ "approxTokens": 1050,
108
+ "largestSections": [
109
+ { "section": "tools", "chars": 1600, "approxTokens": 400 }
110
+ ]
111
+ }
112
+ }
113
+ ```
114
+
53
115
  ## Blocos de contexto
54
116
 
55
117
  Quando habilitados, o agente recebe:
@@ -2400,6 +2400,7 @@ const normalizeToolResultEnvelope = (value) => {
2400
2400
  }
2401
2401
  if (!parsed || typeof parsed !== 'object')
2402
2402
  return parsed;
2403
+ const keys = Object.keys(parsed);
2403
2404
  if (typeof parsed.response === 'string') {
2404
2405
  const nested = normalizeToolResultEnvelope(parsed.response);
2405
2406
  if (nested !== undefined)
@@ -2407,6 +2408,12 @@ const normalizeToolResultEnvelope = (value) => {
2407
2408
  }
2408
2409
  if (parsed.response && typeof parsed.response === 'object')
2409
2410
  return normalizeToolResultEnvelope(parsed.response);
2411
+ if (parsed.json !== undefined && keys.every((key) => ['json', 'pairedItem', 'pairedItems'].includes(key)))
2412
+ return normalizeToolResultEnvelope(parsed.json);
2413
+ for (const key of ['data', 'body', 'result', 'results', 'value']) {
2414
+ if (parsed[key] !== undefined && keys.length === 1)
2415
+ return normalizeToolResultEnvelope(parsed[key]);
2416
+ }
2410
2417
  return parsed;
2411
2418
  };
2412
2419
  const compactParsedToolOutputForSideChannel = (parsed) => {
@@ -2418,6 +2425,13 @@ const compactParsedToolOutputForSideChannel = (parsed) => {
2418
2425
  return parsed;
2419
2426
  if (parsed.output !== undefined)
2420
2427
  return cleanContextValue(stripNoisyToolFields(parsed.output));
2428
+ const keys = Object.keys(parsed);
2429
+ if (parsed.json !== undefined && keys.every((key) => ['json', 'pairedItem', 'pairedItems'].includes(key)))
2430
+ return compactParsedToolOutputForSideChannel(parsed.json);
2431
+ for (const key of ['data', 'body', 'result', 'results', 'value']) {
2432
+ if (parsed[key] !== undefined && keys.length === 1)
2433
+ return compactParsedToolOutputForSideChannel(parsed[key]);
2434
+ }
2421
2435
  return cleanContextValue(stripNoisyToolFields(parsed));
2422
2436
  };
2423
2437
  const toolResultTimestampFromParsed = (parsed) => {
@@ -2838,6 +2852,69 @@ const loadedSectionsForSideChannel = (parsed = {}, memoryAudit = {}) => cleanCon
2838
2852
  recentHighlights: Array.isArray(parsed.recentHighlights),
2839
2853
  contextHealth: Boolean(parsed.contextHealth),
2840
2854
  });
2855
+ const budgetEntryForSideChannel = (section, value) => {
2856
+ if (value === undefined || value === null)
2857
+ return undefined;
2858
+ const text = typeof value === 'string' ? value : safeStringify(value);
2859
+ if (!text)
2860
+ return undefined;
2861
+ return {
2862
+ section,
2863
+ chars: text.length,
2864
+ approxTokens: approxTokenCount(text),
2865
+ };
2866
+ };
2867
+ const agentContextBudgetForSideChannel = (messages = [], parsed = {}) => {
2868
+ const list = Array.isArray(messages) ? messages : [];
2869
+ const messageBudgets = list.map((message, index) => {
2870
+ const content = messageContentOf(message);
2871
+ const role = messageTypeOf(message) || message.role || 'message';
2872
+ return {
2873
+ index,
2874
+ role,
2875
+ chars: content.length,
2876
+ approxTokens: approxTokenCount(content),
2877
+ };
2878
+ });
2879
+ const totalChars = messageBudgets.reduce((sum, item) => sum + item.chars, 0);
2880
+ const sectionCandidates = [
2881
+ ['instruction', parsed.instruction],
2882
+ ['conversation', parsed.conversation],
2883
+ ['current_turn_focus', parsed.current_turn_focus || parsed.currentTurn || parsed.current_turn],
2884
+ ['action_directive', parsed.action_directive],
2885
+ ['turn_brief', parsed.turnBrief || parsed.turn_brief],
2886
+ ['summary', parsed.summary],
2887
+ ['working_memory', parsed.workingMemory || parsed.working_memory],
2888
+ ['decision_state', parsed.decisionState || parsed.decision_state],
2889
+ ['operational_state', parsed.operationalState || parsed.operational_state],
2890
+ ['tools', parsed.tools],
2891
+ ['action_ledger', parsed.actionLedger || parsed.action_ledger],
2892
+ ['memory_compression', parsed.memoryCompression || parsed.memory_compression],
2893
+ ['profile', parsed.profile || parsed.profileFacts || parsed.profile_facts],
2894
+ ['memory_audit', parsed.memoryAudit || parsed.memory_audit],
2895
+ ['dedupe', parsed.dedupeSummary || parsed.dedupe_summary],
2896
+ ];
2897
+ const sections = sectionCandidates
2898
+ .map(([section, value]) => budgetEntryForSideChannel(section, value))
2899
+ .filter(Boolean)
2900
+ .sort((a, b) => b.approxTokens - a.approxTokens);
2901
+ const approxTokens = approxTokenCount(list.map((message) => messageContentOf(message)).join('\n'));
2902
+ const warnings = [];
2903
+ if (approxTokens > 12000)
2904
+ warnings.push('agent context above 12k approximate tokens');
2905
+ else if (approxTokens > 8000)
2906
+ warnings.push('agent context above 8k approximate tokens');
2907
+ if (sections[0] && sections[0].approxTokens > Math.max(2000, approxTokens * 0.45))
2908
+ warnings.push(`largest section is ${sections[0].section}`);
2909
+ return cleanContextValue({
2910
+ messages: list.length,
2911
+ chars: totalChars,
2912
+ approxTokens,
2913
+ byMessage: messageBudgets.slice(0, 8),
2914
+ largestSections: sections.slice(0, 8),
2915
+ warnings,
2916
+ });
2917
+ };
2841
2918
  const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2842
2919
  const list = Array.isArray(messages) ? messages : [];
2843
2920
  const summary = { messages: list.length };
@@ -2866,6 +2943,7 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2866
2943
  const lastSave = compactLastSaveForSideChannel(memoryAudit.last_save || parsed.state?.last_save || {});
2867
2944
  const fullDedupeSummary = parsed.dedupeSummary || parsed.diagnostics?.dedupeSummary || undefined;
2868
2945
  const loadedSections = loadedSectionsForSideChannel(parsed, memoryAudit);
2946
+ const agentContextBudget = agentContextBudgetForSideChannel(list, parsed);
2869
2947
  summary.userId = parsed.userId;
2870
2948
  summary.project = parsed.project || undefined;
2871
2949
  summary.retrievalMode = parsed.retrievalMode;
@@ -2921,6 +2999,8 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2921
2999
  graph: Array.isArray(parsed.graph) ? parsed.graph.length : undefined,
2922
3000
  recentHighlights: Array.isArray(parsed.recentHighlights) ? parsed.recentHighlights.length : undefined,
2923
3001
  });
3002
+ summary.loadedSections = loadedSections;
3003
+ summary.agentContextBudget = agentContextBudget;
2924
3004
  summary.lastSave = Object.keys(lastSave).length ? lastSave : undefined;
2925
3005
  summary.dedupe = fullDedupeSummary ? compactDedupeForSideChannel(fullDedupeSummary) : undefined;
2926
3006
  summary.workingMemory = cleanContextValue({
@@ -2959,11 +3039,11 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2959
3039
  };
2960
3040
  const contextSizeOfMessages = (messages = []) => {
2961
3041
  const perMessage = (messages || []).map((message, index) => {
2962
- const content = String(message.content || '');
3042
+ const content = messageContentOf(message);
2963
3043
  return { index, role: message.role || 'system', chars: content.length, approx_tokens: approxTokenCount(content) };
2964
3044
  });
2965
3045
  const chars = perMessage.reduce((sum, item) => sum + item.chars, 0);
2966
- return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => m.content || '').join('\n')), messages: perMessage };
3046
+ return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => messageContentOf(m)).join('\n')), messages: perMessage };
2967
3047
  };
2968
3048
  const wrapTemboryMemory = (memory, ctx, memoryKey, itemIndex = 0) => new Proxy(memory, {
2969
3049
  get(target, prop) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.1.30",
3
+ "version": "1.1.31",
4
4
  "description": "Tembory node for n8n AI Agents with operational memory, tool history and decision state",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tembory.com",