n8n-nodes-tembory 1.1.30 → 1.1.32

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,44 @@
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.32`.
6
6
 
7
- ## 1.1.2
7
+ ## 1.1.32
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 `Modo de Configuracao` com `Simples Recomendado` e `Avancado`.
10
+ - O modo simples usa os defaults validados nos testes reais: contexto estruturado, resumo por contexto ativo, tool history/results/action ledger, backend persistente e diagnostico pesado desligado.
11
+ - O modo avancado continua exibindo todas as opcoes existentes, sem remover nenhum controle.
12
+ - Atualiza a expressao padrao de `Consulta` para ler tambem `$json.body.chatInput`.
13
+
14
+ ## 1.1.31
15
+
16
+ - Adiciona `agentContextBudget` no payload visual do n8n com caracteres e tokens aproximados injetados no Agent.
17
+ - Mostra `loadedSections` fora do bloco de diagnostico para facilitar debug humano.
18
+ - Melhora o parsing de outputs comuns de tools do n8n, incluindo envelopes `json`, `data`, `body` e `result`.
19
+ - Mantem `outputParsed`, `capturedAt`, `toolTimestamp`, `toolStatus`, conversa e save visual por execucao.
20
+
21
+ ## Contrato operacional atual
22
+
23
+ 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.
24
+
25
+ O que ele deve guardar e devolver:
26
+
27
+ - Mensagens recentes por `threadId` ou `sessionId`.
28
+ - Tool calls genericas, independente do nome da tool.
29
+ - Input enviado para a tool.
30
+ - Output bruto retornado pela tool.
31
+ - Output util em `outputParsed`, quando o formato permitir parsing seguro.
32
+ - Status da tool.
33
+ - `capturedAt`, que e quando o Tembory capturou o evento.
34
+ - `toolTimestamp`, que e o timestamp retornado pela propria tool quando existir.
35
+ - `actionLedger`, `workingMemory`, `decisionState`, `operationalState` e resumo ativo.
36
+
37
+ O que ele nao deve fazer:
38
+
39
+ - Inventar politica de tool.
40
+ - Considerar uma acao lateral concluida sem tool bem sucedida no turno correto.
41
+ - Transformar observacao ou inferencia em regra obrigatoria.
42
+ - Esconder falha de save no payload visual.
13
43
 
14
44
  ## Visao geral
15
45
 
@@ -43,13 +73,64 @@ Use a credencial `Tembory API` com a API key gerada no SaaS. A API key identific
43
73
 
44
74
  O campo `Preset Operacional` preenche defaults de producao:
45
75
 
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.
76
+ - `Producao Rapida (Thread + SLM)`: padrao recomendado para a maioria dos agentes.
77
+ - `Producao Balanceada`: mais contexto, ainda compacto.
78
+ - `Producao Economica`: reduz resultados e secoes para economizar tokens.
79
+ - `Producao Nano/SLM`: otimizado para modelo pequeno.
80
+ - `Diagnostico Completo`: teste e auditoria.
51
81
  - `Auditoria`: payload detalhado para depuracao.
52
82
 
83
+ Recomendacao pratica:
84
+
85
+ - Use `Modo Simples Recomendado` em producao normal.
86
+ - Use `Producao Balanceada` quando o agente precisar comparar mais historico.
87
+ - Use `Auditoria` ou `Diagnostico Completo` apenas enquanto estiver depurando.
88
+
89
+ No modo simples, o node aplica automaticamente:
90
+
91
+ - `Modo de Recuperacao`: operacional estruturado.
92
+ - `Formato do Contexto`: producao estruturado.
93
+ - `Preset Operacional`: producao rapida.
94
+ - `Fonte do Resumo do SLM`: contexto ativo.
95
+ - Tool history, resultados de tools, action ledger, working memory, decision state, operational state e memory compression ligados.
96
+ - Persistencia no backend e active summary ligados.
97
+ - Diagnostico pesado, scores, semantic fallback e tool facts desligados.
98
+ - TTL de tool history em 36000 segundos.
99
+ - Ultimas tools mantidas no contexto compacto: 50.
100
+
101
+ ## Uso com `$fromAI()`
102
+
103
+ 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.
104
+
105
+ Referencia oficial do n8n: https://docs.n8n.io/advanced-ai/examples/using-the-fromai-function/
106
+
107
+ ## Payload visual do n8n
108
+
109
+ No output do node durante uma execucao, o Tembory mostra um resumo compacto para humano:
110
+
111
+ - `memorySummary.intent`: classificacao operacional leve do turno.
112
+ - `memorySummary.conversation`: contagem e ultimas mensagens.
113
+ - `memorySummary.conversationTimeline`: timeline curta com timestamps.
114
+ - `memorySummary.toolEvents`: ultimas tool calls com input, output, `outputParsed`, status e timestamps.
115
+ - `memorySummary.lastSave`: ultimo save persistido.
116
+ - `memorySummary.loadedSections`: quais blocos foram injetados no contexto do Agent.
117
+ - `memorySummary.agentContextBudget`: tamanho aproximado do contexto enviado ao Agent.
118
+
119
+ Exemplo de budget:
120
+
121
+ ```json
122
+ {
123
+ "agentContextBudget": {
124
+ "messages": 1,
125
+ "chars": 4200,
126
+ "approxTokens": 1050,
127
+ "largestSections": [
128
+ { "section": "tools", "chars": 1600, "approxTokens": 400 }
129
+ ]
130
+ }
131
+ }
132
+ ```
133
+
53
134
  ## Blocos de contexto
54
135
 
55
136
  Quando habilitados, o agente recebe:
@@ -1276,8 +1276,44 @@ const mergeRemoteThreadState = (store, key, state) => {
1276
1276
  if (typeof state.activeSummary === 'string' && state.activeSummary)
1277
1277
  store.activeSummary[key] = state.activeSummary;
1278
1278
  };
1279
+ const SIMPLE_MODE_OVERRIDES = {
1280
+ operationPreset: 'productionFast',
1281
+ summarySource: 'activeContext',
1282
+ includeContextHeader: true,
1283
+ compactStateSections: true,
1284
+ includeSummary: true,
1285
+ includeToolHistory: true,
1286
+ includeToolResults: true,
1287
+ includeProfileFacts: true,
1288
+ includeOperationalState: true,
1289
+ includeActionLedger: true,
1290
+ includeWorkingMemory: true,
1291
+ includeDecisionState: true,
1292
+ includeMemoryCompression: true,
1293
+ includeRecentMessages: true,
1294
+ includeRecentHighlights: false,
1295
+ includeEntityTimeline: false,
1296
+ persistBackendMemories: true,
1297
+ persistActiveSummary: true,
1298
+ includeActiveSummary: true,
1299
+ enableTransientSummaryCache: true,
1300
+ includeScores: false,
1301
+ includeDiagnostics: false,
1302
+ persistToolFactsToTembory: false,
1303
+ includeToolHistorySemanticFallback: false,
1304
+ contextMaxChars: 10000,
1305
+ toolHistoryLastN: 50,
1306
+ toolHistoryTTLSeconds: 36000,
1307
+ recentMessagesLastN: 50,
1308
+ connectedModelSummaryInputMaxChars: 4200,
1309
+ connectedModelSummaryMaxChars: 1200,
1310
+ activeSummaryMaxChars: 1800,
1311
+ activeSummaryRetentionDays: 30,
1312
+ };
1279
1313
  const applyOperationalPreset = (advanced = {}) => {
1280
- const preset = String(advanced.operationPreset || 'productionFast');
1314
+ const configurationMode = String(advanced.configurationMode || 'advanced');
1315
+ const simpleOverrides = configurationMode === 'simple' ? SIMPLE_MODE_OVERRIDES : {};
1316
+ const preset = String(simpleOverrides.operationPreset || advanced.operationPreset || 'productionFast');
1281
1317
  const presets = {
1282
1318
  productionFast: {
1283
1319
  summarySource: 'activeContext',
@@ -1491,7 +1527,7 @@ const applyOperationalPreset = (advanced = {}) => {
1491
1527
  },
1492
1528
  };
1493
1529
  const presetKey = preset === 'lab' ? 'diagnostic' : preset;
1494
- const resolved = { ...(presets[presetKey] || {}), ...advanced };
1530
+ const resolved = { ...(presets[presetKey] || {}), ...advanced, ...simpleOverrides, configurationMode };
1495
1531
  if (/^production/i.test(presetKey)) {
1496
1532
  resolved.compactForAgent = true;
1497
1533
  resolved.compactStateSections = true;
@@ -1534,7 +1570,12 @@ const readAdvancedOptions = (ctx, itemIndex) => {
1534
1570
  grouped = flattenAdvancedGroups(ctx.getNodeParameter('advancedGroups', itemIndex, {}) || {});
1535
1571
  }
1536
1572
  catch { }
1537
- return { ...legacy, ...grouped };
1573
+ let configurationMode = 'simple';
1574
+ try {
1575
+ configurationMode = String(ctx.getNodeParameter('configurationMode', itemIndex, 'simple') || 'simple');
1576
+ }
1577
+ catch { }
1578
+ return { ...legacy, ...grouped, configurationMode };
1538
1579
  };
1539
1580
  const pruneByLimit = (items, limit) => items.slice(Math.max(0, items.length - Math.max(1, Number(limit) || 1)));
1540
1581
  const dedupeRecentMessages = (items) => {
@@ -2400,6 +2441,7 @@ const normalizeToolResultEnvelope = (value) => {
2400
2441
  }
2401
2442
  if (!parsed || typeof parsed !== 'object')
2402
2443
  return parsed;
2444
+ const keys = Object.keys(parsed);
2403
2445
  if (typeof parsed.response === 'string') {
2404
2446
  const nested = normalizeToolResultEnvelope(parsed.response);
2405
2447
  if (nested !== undefined)
@@ -2407,6 +2449,12 @@ const normalizeToolResultEnvelope = (value) => {
2407
2449
  }
2408
2450
  if (parsed.response && typeof parsed.response === 'object')
2409
2451
  return normalizeToolResultEnvelope(parsed.response);
2452
+ if (parsed.json !== undefined && keys.every((key) => ['json', 'pairedItem', 'pairedItems'].includes(key)))
2453
+ return normalizeToolResultEnvelope(parsed.json);
2454
+ for (const key of ['data', 'body', 'result', 'results', 'value']) {
2455
+ if (parsed[key] !== undefined && keys.length === 1)
2456
+ return normalizeToolResultEnvelope(parsed[key]);
2457
+ }
2410
2458
  return parsed;
2411
2459
  };
2412
2460
  const compactParsedToolOutputForSideChannel = (parsed) => {
@@ -2418,6 +2466,13 @@ const compactParsedToolOutputForSideChannel = (parsed) => {
2418
2466
  return parsed;
2419
2467
  if (parsed.output !== undefined)
2420
2468
  return cleanContextValue(stripNoisyToolFields(parsed.output));
2469
+ const keys = Object.keys(parsed);
2470
+ if (parsed.json !== undefined && keys.every((key) => ['json', 'pairedItem', 'pairedItems'].includes(key)))
2471
+ return compactParsedToolOutputForSideChannel(parsed.json);
2472
+ for (const key of ['data', 'body', 'result', 'results', 'value']) {
2473
+ if (parsed[key] !== undefined && keys.length === 1)
2474
+ return compactParsedToolOutputForSideChannel(parsed[key]);
2475
+ }
2421
2476
  return cleanContextValue(stripNoisyToolFields(parsed));
2422
2477
  };
2423
2478
  const toolResultTimestampFromParsed = (parsed) => {
@@ -2838,6 +2893,69 @@ const loadedSectionsForSideChannel = (parsed = {}, memoryAudit = {}) => cleanCon
2838
2893
  recentHighlights: Array.isArray(parsed.recentHighlights),
2839
2894
  contextHealth: Boolean(parsed.contextHealth),
2840
2895
  });
2896
+ const budgetEntryForSideChannel = (section, value) => {
2897
+ if (value === undefined || value === null)
2898
+ return undefined;
2899
+ const text = typeof value === 'string' ? value : safeStringify(value);
2900
+ if (!text)
2901
+ return undefined;
2902
+ return {
2903
+ section,
2904
+ chars: text.length,
2905
+ approxTokens: approxTokenCount(text),
2906
+ };
2907
+ };
2908
+ const agentContextBudgetForSideChannel = (messages = [], parsed = {}) => {
2909
+ const list = Array.isArray(messages) ? messages : [];
2910
+ const messageBudgets = list.map((message, index) => {
2911
+ const content = messageContentOf(message);
2912
+ const role = messageTypeOf(message) || message.role || 'message';
2913
+ return {
2914
+ index,
2915
+ role,
2916
+ chars: content.length,
2917
+ approxTokens: approxTokenCount(content),
2918
+ };
2919
+ });
2920
+ const totalChars = messageBudgets.reduce((sum, item) => sum + item.chars, 0);
2921
+ const sectionCandidates = [
2922
+ ['instruction', parsed.instruction],
2923
+ ['conversation', parsed.conversation],
2924
+ ['current_turn_focus', parsed.current_turn_focus || parsed.currentTurn || parsed.current_turn],
2925
+ ['action_directive', parsed.action_directive],
2926
+ ['turn_brief', parsed.turnBrief || parsed.turn_brief],
2927
+ ['summary', parsed.summary],
2928
+ ['working_memory', parsed.workingMemory || parsed.working_memory],
2929
+ ['decision_state', parsed.decisionState || parsed.decision_state],
2930
+ ['operational_state', parsed.operationalState || parsed.operational_state],
2931
+ ['tools', parsed.tools],
2932
+ ['action_ledger', parsed.actionLedger || parsed.action_ledger],
2933
+ ['memory_compression', parsed.memoryCompression || parsed.memory_compression],
2934
+ ['profile', parsed.profile || parsed.profileFacts || parsed.profile_facts],
2935
+ ['memory_audit', parsed.memoryAudit || parsed.memory_audit],
2936
+ ['dedupe', parsed.dedupeSummary || parsed.dedupe_summary],
2937
+ ];
2938
+ const sections = sectionCandidates
2939
+ .map(([section, value]) => budgetEntryForSideChannel(section, value))
2940
+ .filter(Boolean)
2941
+ .sort((a, b) => b.approxTokens - a.approxTokens);
2942
+ const approxTokens = approxTokenCount(list.map((message) => messageContentOf(message)).join('\n'));
2943
+ const warnings = [];
2944
+ if (approxTokens > 12000)
2945
+ warnings.push('agent context above 12k approximate tokens');
2946
+ else if (approxTokens > 8000)
2947
+ warnings.push('agent context above 8k approximate tokens');
2948
+ if (sections[0] && sections[0].approxTokens > Math.max(2000, approxTokens * 0.45))
2949
+ warnings.push(`largest section is ${sections[0].section}`);
2950
+ return cleanContextValue({
2951
+ messages: list.length,
2952
+ chars: totalChars,
2953
+ approxTokens,
2954
+ byMessage: messageBudgets.slice(0, 8),
2955
+ largestSections: sections.slice(0, 8),
2956
+ warnings,
2957
+ });
2958
+ };
2841
2959
  const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2842
2960
  const list = Array.isArray(messages) ? messages : [];
2843
2961
  const summary = { messages: list.length };
@@ -2866,6 +2984,7 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2866
2984
  const lastSave = compactLastSaveForSideChannel(memoryAudit.last_save || parsed.state?.last_save || {});
2867
2985
  const fullDedupeSummary = parsed.dedupeSummary || parsed.diagnostics?.dedupeSummary || undefined;
2868
2986
  const loadedSections = loadedSectionsForSideChannel(parsed, memoryAudit);
2987
+ const agentContextBudget = agentContextBudgetForSideChannel(list, parsed);
2869
2988
  summary.userId = parsed.userId;
2870
2989
  summary.project = parsed.project || undefined;
2871
2990
  summary.retrievalMode = parsed.retrievalMode;
@@ -2921,6 +3040,8 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2921
3040
  graph: Array.isArray(parsed.graph) ? parsed.graph.length : undefined,
2922
3041
  recentHighlights: Array.isArray(parsed.recentHighlights) ? parsed.recentHighlights.length : undefined,
2923
3042
  });
3043
+ summary.loadedSections = loadedSections;
3044
+ summary.agentContextBudget = agentContextBudget;
2924
3045
  summary.lastSave = Object.keys(lastSave).length ? lastSave : undefined;
2925
3046
  summary.dedupe = fullDedupeSummary ? compactDedupeForSideChannel(fullDedupeSummary) : undefined;
2926
3047
  summary.workingMemory = cleanContextValue({
@@ -2959,11 +3080,11 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
2959
3080
  };
2960
3081
  const contextSizeOfMessages = (messages = []) => {
2961
3082
  const perMessage = (messages || []).map((message, index) => {
2962
- const content = String(message.content || '');
3083
+ const content = messageContentOf(message);
2963
3084
  return { index, role: message.role || 'system', chars: content.length, approx_tokens: approxTokenCount(content) };
2964
3085
  });
2965
3086
  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 };
3087
+ return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => messageContentOf(m)).join('\n')), messages: perMessage };
2967
3088
  };
2968
3089
  const wrapTemboryMemory = (memory, ctx, memoryKey, itemIndex = 0) => new Proxy(memory, {
2969
3090
  get(target, prop) {
@@ -3435,10 +3556,34 @@ class TemboryMemory {
3435
3556
  default: '={{ $json.project || $json.projectId || $json.accountId || $json.body?.account?.id || "" }}',
3436
3557
  description: 'Namespace operacional do workflow para separar clientes/workspaces. Nao e o Project ID da credencial; a API key do Tembory ja identifica o projeto comercial.',
3437
3558
  },
3559
+ {
3560
+ displayName: 'Modo de Configuração',
3561
+ name: 'configurationMode',
3562
+ type: 'options',
3563
+ options: [
3564
+ {
3565
+ name: 'Simples Recomendado',
3566
+ value: 'simple',
3567
+ description: 'Usa os defaults validados em producao: contexto estruturado, SLM por contexto ativo, tool history/results/action ledger, backend persistente e diagnostico pesado desligado.',
3568
+ },
3569
+ {
3570
+ name: 'Avançado',
3571
+ value: 'advanced',
3572
+ description: 'Exibe todas as opcoes para ajuste fino de memoria, resumo, auditoria, TTL e payload.',
3573
+ },
3574
+ ],
3575
+ default: 'simple',
3576
+ description: 'No modo simples o Tembory escolhe a configuracao recomendada. No modo avancado todos os campos continuam disponiveis.',
3577
+ },
3438
3578
  {
3439
3579
  displayName: 'Modo de Recuperação de Contexto',
3440
3580
  name: 'retrievalMode',
3441
3581
  type: 'options',
3582
+ displayOptions: {
3583
+ show: {
3584
+ configurationMode: ['advanced'],
3585
+ },
3586
+ },
3442
3587
  options: [
3443
3588
  { name: 'Operacional Estruturado', value: 'basic', description: 'Usa estado da thread, histórico de tools, mensagens recentes e resumo ativo, sem busca vetorial' },
3444
3589
  { name: 'Resumo', value: 'summary', description: 'Retorna um resumo simples em texto' },
@@ -3450,13 +3595,18 @@ class TemboryMemory {
3450
3595
  displayName: 'Consulta',
3451
3596
  name: 'query',
3452
3597
  type: 'string',
3453
- default: '={{ $json.query || $json.lastUserMessage || $json.chatInput || $json.body?.consolidated_text || $json.body?.message || $json.body?.text || "" }}',
3598
+ default: '={{ $json.query || $json.lastUserMessage || $json.chatInput || $json.body?.chatInput || $json.body?.consolidated_text || $json.body?.message || $json.body?.text || "" }}',
3454
3599
  description: 'Mensagem atual usada para montar o contexto operacional, inferir intenção e recuperar memórias relevantes. Em AI Agent, mantenha a expressão padrão para ler o chatInput do item.',
3455
3600
  },
3456
3601
  {
3457
3602
  displayName: 'Chave de Memória',
3458
3603
  name: 'memoryKey',
3459
3604
  type: 'string',
3605
+ displayOptions: {
3606
+ show: {
3607
+ configurationMode: ['advanced'],
3608
+ },
3609
+ },
3460
3610
  default: 'chat_history',
3461
3611
  description: 'Chave sob a qual a memória será retornada',
3462
3612
  },
@@ -3464,6 +3614,11 @@ class TemboryMemory {
3464
3614
  displayName: 'Formato do Contexto',
3465
3615
  name: 'payloadFormat',
3466
3616
  type: 'options',
3617
+ displayOptions: {
3618
+ show: {
3619
+ configurationMode: ['advanced'],
3620
+ },
3621
+ },
3467
3622
  options: [
3468
3623
  { name: 'Produção Estruturado', value: 'structured' },
3469
3624
  { name: 'Auditoria Texto', value: 'auditText' },
@@ -3478,6 +3633,11 @@ class TemboryMemory {
3478
3633
  displayName: 'Configuração Tembory',
3479
3634
  name: 'advancedGroups',
3480
3635
  type: 'fixedCollection',
3636
+ displayOptions: {
3637
+ show: {
3638
+ configurationMode: ['advanced'],
3639
+ },
3640
+ },
3481
3641
  placeholder: 'Adicionar Grupo',
3482
3642
  default: '',
3483
3643
  typeOptions: {
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.32",
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",