n8n-nodes-tembory 1.0.7 → 1.0.8

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.
@@ -928,6 +928,34 @@ const applyOperationalPreset = (advanced = {}) => {
928
928
  toolHistoryLastN: 5,
929
929
  recentMessagesLastN: 4,
930
930
  },
931
+ productionNano: {
932
+ compactForAgent: true,
933
+ includeContextHeader: true,
934
+ includeSummary: true,
935
+ includeScores: false,
936
+ includeDiagnostics: false,
937
+ includeRelations: false,
938
+ includeToolHistory: true,
939
+ includeToolResults: true,
940
+ persistToolFactsToMem0: false,
941
+ includeToolHistorySemanticFallback: false,
942
+ includeProfileFacts: true,
943
+ includeOperationalState: true,
944
+ includeActionLedger: false,
945
+ includeEntityTimeline: false,
946
+ includeWorkingMemory: true,
947
+ includeDecisionState: true,
948
+ includeMemoryCompression: true,
949
+ includeRecentMessages: false,
950
+ includeRecentHighlights: false,
951
+ topK: 4,
952
+ lastN: 4,
953
+ maxReturn: 6,
954
+ toolHistoryLastN: 6,
955
+ recentMessagesLastN: 2,
956
+ vectorMemoryMaxChars: 220,
957
+ contextMaxChars: 6000,
958
+ },
931
959
  audit: {
932
960
  includeContextHeader: true,
933
961
  includeSummary: true,
@@ -1308,6 +1336,105 @@ const contextMemoryText = (memory, max = 700) => {
1308
1336
  }
1309
1337
  return truncate(text, max);
1310
1338
  };
1339
+ const approxTokenCount = (text) => Math.ceil(String(text || '').length / 4);
1340
+ const importantJsonFields = ['reservation_id', 'confirmation_id', 'selected_from_message', 'status', 'next_step', 'available_slots', 'timezone', 'error'];
1341
+ const pickImportantFields = (value) => {
1342
+ if (value === null || value === undefined)
1343
+ return {};
1344
+ const out = {};
1345
+ const visit = (item) => {
1346
+ if (!item || typeof item !== 'object')
1347
+ return;
1348
+ if (Array.isArray(item)) {
1349
+ item.slice(0, 3).forEach(visit);
1350
+ return;
1351
+ }
1352
+ for (const key of importantJsonFields) {
1353
+ if (item[key] !== undefined && out[key] === undefined)
1354
+ out[key] = item[key];
1355
+ }
1356
+ if (item.args)
1357
+ visit(item.args);
1358
+ if (item.result)
1359
+ visit(item.result);
1360
+ };
1361
+ visit(value);
1362
+ return out;
1363
+ };
1364
+ const compactToolResult = (result, max = 360) => {
1365
+ const text = String(result || '');
1366
+ if (!text.trim())
1367
+ return undefined;
1368
+ try {
1369
+ const parsed = JSON.parse(text);
1370
+ const picked = pickImportantFields(parsed);
1371
+ if (Object.keys(picked).length)
1372
+ return truncate(safeStringify(picked), max);
1373
+ }
1374
+ catch { }
1375
+ const picked = {};
1376
+ for (const key of importantJsonFields) {
1377
+ const re = new RegExp(`["']?${key}["']?\\s*[:=]\\s*["']?([^"',}\\]]+)`, 'i');
1378
+ const match = re.exec(text);
1379
+ if (match)
1380
+ picked[key] = match[1];
1381
+ }
1382
+ if (Object.keys(picked).length)
1383
+ return truncate(safeStringify(picked), max);
1384
+ return truncate(text, max);
1385
+ };
1386
+ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResults = true) => pruneByLimit(toolHistory || [], maxItems).map((tool) => ({
1387
+ name: tool.name,
1388
+ status: tool.ok === false ? 'failed' : 'ok',
1389
+ at: tool.at,
1390
+ input: truncate(String(tool.input || ''), 180) || undefined,
1391
+ result: includeResults ? compactToolResult(tool.result, 360) : undefined,
1392
+ }));
1393
+ const compactOperationalStateForAgent = (state = {}) => {
1394
+ const agenda = state.agenda_state || {};
1395
+ return {
1396
+ last_tool: state.last_tool || null,
1397
+ tool_counts: state.tool_counts || {},
1398
+ agenda_state: {
1399
+ has_availability: Boolean(agenda.has_availability),
1400
+ has_pre_reservation: Boolean(agenda.has_pre_reservation),
1401
+ has_confirmation: Boolean(agenda.has_confirmation),
1402
+ has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1403
+ latest_availability: compactToolResult(agenda.latest_availability_result),
1404
+ latest_pre_reservation: compactToolResult(agenda.latest_pre_reservation_result),
1405
+ latest_confirmation: compactToolResult(agenda.latest_confirmation_result),
1406
+ },
1407
+ blocked_without_context: state.blocked_without_context || [],
1408
+ guidance: (state.guidance || []).slice(0, 3),
1409
+ };
1410
+ };
1411
+ const compactMemoryCompressionForAgent = (compression = {}) => ({
1412
+ turn_summary: (compression.turn_summary || []).slice(-3),
1413
+ session_tools: (((compression.session_summary || {}).tools) || []).slice(-6),
1414
+ agenda_state: ((compression.session_summary || {}).agenda_state) ? {
1415
+ has_availability: Boolean(compression.session_summary.agenda_state.has_availability),
1416
+ has_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pre_reservation),
1417
+ has_confirmation: Boolean(compression.session_summary.agenda_state.has_confirmation),
1418
+ has_pending_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pending_pre_reservation),
1419
+ } : undefined,
1420
+ active_memory_count: ((compression.workflow_summary || {}).active_memory_count) || 0,
1421
+ });
1422
+ const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], maxItems = 3) => {
1423
+ const hasStructuredTools = Array.isArray(toolHistory) && toolHistory.length > 0;
1424
+ return (vectorMemories || [])
1425
+ .map((memory) => contextMemoryText(memory, 220))
1426
+ .filter(Boolean)
1427
+ .filter((text) => !hasStructuredTools || !/^\[tool_events_extracted\]/i.test(text))
1428
+ .slice(0, maxItems);
1429
+ };
1430
+ const contextSizeOfMessages = (messages = []) => {
1431
+ const perMessage = (messages || []).map((message, index) => {
1432
+ const content = String(message.content || '');
1433
+ return { index, role: message.role || 'system', chars: content.length, approx_tokens: approxTokenCount(content) };
1434
+ });
1435
+ const chars = perMessage.reduce((sum, item) => sum + item.chars, 0);
1436
+ return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => m.content || '').join('\n')), messages: perMessage };
1437
+ };
1311
1438
  const wrapElefaiMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1312
1439
  get(target, prop) {
1313
1440
  if (prop === 'loadMemoryVariables') {
@@ -1377,13 +1504,80 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1377
1504
  const includeHeader = adv.includeContextHeader !== false;
1378
1505
  const includeSummary = adv.includeSummary !== false;
1379
1506
  const includeScores = adv.includeScores !== false;
1507
+ const compactForAgent = Boolean(adv.compactForAgent);
1380
1508
  const sections = [];
1381
1509
  if (includeHeader) {
1382
1510
  sections.push({
1383
1511
  section: 'context_header',
1384
1512
  title: 'Tembory context',
1385
- value: 'Use this context as read-only memory. Prefer it over guessing. Do not mention internal section names to the user. Treat next_expected_action as an instruction, not as a suggestion. If it says to call a tool now, call that tool instead of asking the user the same question again. If the user asks to continue, chooses a slot, says ok/sim, reserve, confirm, update, cancel, or performs any downstream action that depends on a prior tool result, first verify the required prior result in tool_history, recent_messages, or vector memories. If the required prior result is absent, do not call the downstream tool; ask for the missing context or call the appropriate prerequisite tool.',
1513
+ value: compactForAgent
1514
+ ? 'Read-only memory. Follow next_expected_action when present. Before calling downstream tools, verify required prior tool context in tool_history or operational_state. Do not repeat tools listed in do_not_repeat_tools.'
1515
+ : 'Use this context as read-only memory. Prefer it over guessing. Do not mention internal section names to the user. Treat next_expected_action as an instruction, not as a suggestion. If it says to call a tool now, call that tool instead of asking the user the same question again. If the user asks to continue, chooses a slot, says ok/sim, reserve, confirm, update, cancel, or performs any downstream action that depends on a prior tool result, first verify the required prior result in tool_history, recent_messages, or vector memories. If the required prior result is absent, do not call the downstream tool; ask for the missing context or call the appropriate prerequisite tool.',
1516
+ });
1517
+ }
1518
+ if (compactForAgent) {
1519
+ if (includeSummary) {
1520
+ const summary = compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.summaryMaxFacts || 3));
1521
+ sections.push({ section: 'summary', title: 'Summary', value: summary.length ? summary : null, why_null: summary.length ? undefined : 'no non-tool vector memories to summarize' });
1522
+ }
1523
+ sections.push({
1524
+ section: 'working_memory',
1525
+ title: 'Working memory',
1526
+ value: adv.includeWorkingMemory === false ? null : (workingMemory || {}),
1527
+ why_null: adv.includeWorkingMemory === false ? 'working memory disabled' : undefined,
1386
1528
  });
1529
+ sections.push({
1530
+ section: 'decision_state',
1531
+ title: 'Decision state',
1532
+ value: adv.includeDecisionState === false ? null : (decisionState || {}),
1533
+ why_null: adv.includeDecisionState === false ? 'decision state disabled' : undefined,
1534
+ });
1535
+ sections.push({
1536
+ section: 'operational_state',
1537
+ title: 'Operational state',
1538
+ value: adv.includeOperationalState === false ? null : compactOperationalStateForAgent(operationalState || {}),
1539
+ why_null: adv.includeOperationalState === false ? 'operational state disabled' : undefined,
1540
+ });
1541
+ sections.push({
1542
+ section: 'tool_history',
1543
+ title: 'Tool history',
1544
+ value: {
1545
+ enabled: adv.includeToolHistory !== false,
1546
+ items: adv.includeToolHistory === false ? [] : compactToolHistoryForAgent(toolHistory, adv.toolHistoryLastN || 6, adv.includeToolResults !== false),
1547
+ },
1548
+ });
1549
+ sections.push({
1550
+ section: 'profile_facts',
1551
+ title: 'Profile facts',
1552
+ value: adv.includeProfileFacts === false ? null : renderProfileFacts(profileFacts),
1553
+ why_null: adv.includeProfileFacts === false ? 'profile facts disabled' : undefined,
1554
+ });
1555
+ sections.push({
1556
+ section: 'memory_compression',
1557
+ title: 'Memory compression',
1558
+ value: adv.includeMemoryCompression === false ? null : compactMemoryCompressionForAgent(memoryCompression || {}),
1559
+ why_null: adv.includeMemoryCompression === false ? 'memory compression disabled' : undefined,
1560
+ });
1561
+ const audit = {
1562
+ kind: 'elefai.brain.context.v1',
1563
+ meta: { user_id: userId, payloadFormat, query, compact_for_agent: true },
1564
+ sections,
1565
+ diagnostics,
1566
+ };
1567
+ const renderCompactSection = (section) => {
1568
+ if (section.value === null || section.value === undefined)
1569
+ return `## ${section.title}\n${section.why_null || 'empty'}`;
1570
+ if (typeof section.value === 'string')
1571
+ return `## ${section.title}\n${section.value}`;
1572
+ if (Array.isArray(section.value))
1573
+ return `## ${section.title}\n${section.value.length ? section.value.map((item) => typeof item === 'string' ? `- ${item}` : `- ${truncate(safeStringify(item), 420)}`).join('\n') : '(empty)'}`;
1574
+ return `## ${section.title}\n${truncate(safeStringify(section.value), 900)}`;
1575
+ };
1576
+ if (payloadFormat === 'auditJson')
1577
+ return [{ role: 'system', content: JSON.stringify(audit, null, 2) }];
1578
+ if (payloadFormat === 'auditBlocks')
1579
+ return sections.map((section) => ({ role: 'system', content: renderCompactSection(section) }));
1580
+ return [{ role: 'system', content: truncate(sections.map(renderCompactSection).join('\n\n'), Number(adv.contextMaxChars || 6000)) }];
1387
1581
  }
1388
1582
  if (includeSummary) {
1389
1583
  const summary = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 360)).filter(Boolean);
@@ -1654,6 +1848,7 @@ class Mem0Memory {
1654
1848
  { name: 'Diagnóstico Completo', value: 'diagnostic' },
1655
1849
  { name: 'Produção Balanceada', value: 'productionBalanced' },
1656
1850
  { name: 'Produção Econômica', value: 'productionCheap' },
1851
+ { name: 'Produção Nano/SLM', value: 'productionNano' },
1657
1852
  { name: 'Auditoria', value: 'audit' },
1658
1853
  ],
1659
1854
  default: 'custom',
@@ -1673,6 +1868,8 @@ class Mem0Memory {
1673
1868
  { displayName: 'MMR (diversidade)', name: 'mmr', type: 'boolean', default: true, description: 'Aplicar diversidade de resultados (Maximal Marginal Relevance)', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
1674
1869
  { displayName: 'MMR Lambda', name: 'mmrLambda', type: 'number', typeOptions: { minValue: 0, maxValue: 1, numberPrecision: 2 }, default: 0.5, description: 'Equilíbrio entre relevância e diversidade no MMR', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
1675
1870
  { displayName: 'Incluir Cabeçalho de Contexto', name: 'includeContextHeader', type: 'boolean', default: true },
1871
+ { displayName: 'Compactar Contexto Para Agente', name: 'compactForAgent', type: 'boolean', default: false, description: 'Entrega ao agente apenas o estado acionável: working memory, decisões, estado operacional e histórico de tools compacto. Mantém detalhes completos no audit/debug.' },
1872
+ { displayName: 'Máximo de Caracteres do Contexto', name: 'contextMaxChars', type: 'number', default: 6000, description: 'Limite final aplicado quando Compactar Contexto Para Agente estiver ativo.' },
1676
1873
  { displayName: 'Incluir Resumo', name: 'includeSummary', type: 'boolean', default: true },
1677
1874
  { displayName: 'Máximo de Fatos no Resumo', name: 'summaryMaxFacts', type: 'number', default: 4 },
1678
1875
  { displayName: 'Incluir Scores', name: 'includeScores', type: 'boolean', default: true },
@@ -2362,6 +2559,7 @@ class Mem0Memory {
2362
2559
  diagnostics,
2363
2560
  adv,
2364
2561
  });
2562
+ diagnostics.contextSize = contextSizeOfMessages(payload);
2365
2563
  const normalizedVectorMemories = vectorMemories.map((m) => {
2366
2564
  const scoreMeta = scoreMetaOf(m);
2367
2565
  const rawScore = scoreOf(m);
@@ -2404,6 +2602,7 @@ class Mem0Memory {
2404
2602
  includeOperationalState: adv.includeOperationalState !== false,
2405
2603
  includeActionLedger: adv.includeActionLedger !== false,
2406
2604
  includeEntityTimeline: adv.includeEntityTimeline !== false,
2605
+ compactForAgent: Boolean(adv.compactForAgent),
2407
2606
  includeWorkingMemory: adv.includeWorkingMemory !== false,
2408
2607
  includeDecisionState: adv.includeDecisionState !== false,
2409
2608
  includeMemoryCompression: adv.includeMemoryCompression !== false,
@@ -2587,4 +2786,9 @@ exports.__private = {
2587
2786
  mergeProfileFacts,
2588
2787
  renderProfileFacts,
2589
2788
  isNoisyProfileValue,
2789
+ approxTokenCount,
2790
+ contextSizeOfMessages,
2791
+ compactToolResult,
2792
+ compactToolHistoryForAgent,
2793
+ compactOperationalStateForAgent,
2590
2794
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
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",
@@ -24,7 +24,8 @@
24
24
  "lintfix": "tslint --fix -p tsconfig.json -c tslint.json",
25
25
  "test": "node --test test/*.test.js",
26
26
  "smoke:n8n:multiturn": "node scripts/smoke-n8n-multiturn-tools.js",
27
- "prepublishOnly": "npm run build"
27
+ "simulate:agent-context": "node scripts/simulate-agent-context.js",
28
+ "prepublishOnly": "npm test"
28
29
  },
29
30
  "files": [
30
31
  "dist",
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ const Module = require('node:module');
3
+
4
+ const originalLoad = Module._load;
5
+ Module._load = function patchedLoad(request, parent, isMain) {
6
+ if (request === 'n8n-workflow') {
7
+ return {
8
+ NodeConnectionTypes: {
9
+ AiMemory: 'ai_memory',
10
+ AiLanguageModel: 'ai_languageModel',
11
+ AiEmbedding: 'ai_embedding',
12
+ },
13
+ NodeApiError: class NodeApiError extends Error {},
14
+ };
15
+ }
16
+ if (request === '@langchain/core/messages') {
17
+ return {
18
+ HumanMessage: class HumanMessage { constructor(content) { this.content = content; } _getType() { return 'human'; } },
19
+ AIMessage: class AIMessage { constructor(content) { this.content = content; } _getType() { return 'ai'; } },
20
+ SystemMessage: class SystemMessage { constructor(content) { this.content = content; } _getType() { return 'system'; } },
21
+ };
22
+ }
23
+ return originalLoad.call(this, request, parent, isMain);
24
+ };
25
+
26
+ const core = require('../dist/nodes/Mem0/Mem0Memory.node.js').__private;
27
+
28
+ const toolHistory = [
29
+ {
30
+ id: 'tool_ry3twg',
31
+ name: 'agenda_consultar_disponibilidade',
32
+ input: '',
33
+ ok: true,
34
+ at: '2026-05-14T05:34:12.460Z',
35
+ source: 'used_tools_text',
36
+ result: '[{"args":{"tool":"agenda_consultar_disponibilidade","available_slots":"2026-05-12T09:00,2026-05-12T14:00,2026-05-13T10:30","timezone":"America/Sao_Paulo","next_step":"escolher_um_horario"}}]',
37
+ },
38
+ {
39
+ id: 'tool_mu9qd2',
40
+ name: 'agenda_pre_reservar_horario',
41
+ input: '',
42
+ ok: true,
43
+ at: '2026-05-14T05:34:34.017Z',
44
+ source: 'used_tools_text',
45
+ result: '[{"args":{"tool":"agenda_pre_reservar_horario","reservation_id":"RES-ELEFAI-LAB-001","selected_from_message":"dia 13","status":"pre_reserved","next_step":"confirmar_agendamento"}}]',
46
+ },
47
+ ];
48
+
49
+ const vectorMemories = [
50
+ {
51
+ memory: '[Used tools: Tool: agenda_pre_reservar_horario, Input: , Result: [{"args":{"tool":"agenda_pre_reservar_horario","reservation_id":"RES-ELEFAI-LAB-001","selected_from_message":"dia 13","status":"pre_reserved","next_step":"confirmar_agendamento"}}]] Calling agenda_pre_reservar_horario with input: {"id":"call_bfHlltcjI1vCagRkav0fj8Ha"}\nPré-reserva feita para o dia 13.\nSe quiser, posso confirmar agora.',
52
+ created_at: '2026-05-14T05:34:35.373020Z',
53
+ elefaiScore: { semanticScore: 0.71, recencyScore: 0.99, hybridScore: 0.81, source: 'semantic' },
54
+ },
55
+ {
56
+ memory: '[Used tools: Tool: agenda_consultar_disponibilidade, Input: , Result: [{"args":{"tool":"agenda_consultar_disponibilidade","available_slots":"2026-05-12T09:00,2026-05-12T14:00,2026-05-13T10:30","timezone":"America/Sao_Paulo","next_step":"escolher_um_horario"}}]] Temos estes horários vagos:\n- 12/05 às 09:00\n- 12/05 às 14:00\n- 13/05 às 10:30',
57
+ created_at: '2026-05-14T05:34:14.717988Z',
58
+ elefaiScore: { semanticScore: 0.7, recencyScore: 0.99, hybridScore: 0.8, source: 'semantic' },
59
+ },
60
+ { memory: 'Oi! Como posso ajudar?', created_at: '2026-05-14T05:33:47.194630Z' },
61
+ { memory: 'oi', created_at: '2026-05-14T05:33:47.194355Z' },
62
+ ];
63
+
64
+ const operationalState = core.deriveOperationalState(toolHistory, {}, [], true);
65
+ const workingMemory = {
66
+ current_goal: 'call agenda_confirmar_agendamento now; do not ask the same confirmation question again',
67
+ current_task: 'call agenda_confirmar_agendamento now; do not ask the same confirmation question again',
68
+ last_user_intent: 'affirm',
69
+ last_user_message: 'ok',
70
+ next_expected_action: 'call agenda_confirmar_agendamento now; do not ask the same confirmation question again',
71
+ };
72
+ const decisionState = core.deriveDecisionState({ query: 'ok', toolHistory, operationalState, workingMemory });
73
+ const memoryCompression = core.deriveMemoryCompression({ toolHistory, operationalState, vectorMemories });
74
+ const common = {
75
+ payloadFormat: 'structured',
76
+ query: 'ok',
77
+ userId: 'd48292bd4c9f4eccb9120092e3acd073',
78
+ profileFacts: {},
79
+ workingMemory,
80
+ decisionState,
81
+ memoryCompression,
82
+ operationalState,
83
+ actionLedger: core.deriveActionLedger(toolHistory, 20, true),
84
+ entityTimeline: core.deriveEntityTimeline({}, [], [], toolHistory),
85
+ vectorMemories,
86
+ recentMessages: [],
87
+ toolHistory,
88
+ highlights: [
89
+ 'tool agenda_consultar_disponibilidade: [{"args":{"available_slots":"2026-05-12T09:00,2026-05-12T14:00,2026-05-13T10:30"}}]',
90
+ 'tool agenda_pre_reservar_horario: [{"args":{"reservation_id":"RES-ELEFAI-LAB-001","status":"pre_reserved"}}]',
91
+ ],
92
+ graph: [],
93
+ diagnostics: {},
94
+ };
95
+
96
+ const modes = [
97
+ ['productionBalanced', core.applyOperationalPreset({ operationPreset: 'productionBalanced' })],
98
+ ['productionNano', core.applyOperationalPreset({ operationPreset: 'productionNano' })],
99
+ ];
100
+
101
+ for (const [name, adv] of modes) {
102
+ const messages = core.buildContextMessages({ ...common, adv });
103
+ const text = messages.map((message) => message.content).join('\n\n');
104
+ console.log(JSON.stringify({
105
+ mode: name,
106
+ size: core.contextSizeOfMessages(messages),
107
+ has_next_expected_action: /next_expected_action/.test(text),
108
+ has_do_not_repeat_tools: /do_not_repeat_tools/.test(text),
109
+ has_reservation_id: /RES-ELEFAI-LAB-001/.test(text),
110
+ has_available_slots: /available_slots/.test(text),
111
+ has_duplicate_calling_text: /Calling agenda_pre_reservar_horario/.test(text),
112
+ }, null, 2));
113
+ }