n8n-nodes-tembory 1.0.7 → 1.0.9

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.
@@ -43,7 +43,7 @@ const getConnectedEmbedding = async (ctx, itemIndex) => {
43
43
  };
44
44
  const createClientVectorMemory = async (embedding, text, metadata, ids = {}) => ({
45
45
  text: truncate(text, 4000),
46
- embedding: await embedding.embedQuery(String(text || '')),
46
+ embedding: await embedQueryCached(embedding, String(text || '')),
47
47
  user_id: ids.user_id,
48
48
  agent_id: ids.agent_id,
49
49
  run_id: ids.run_id,
@@ -67,7 +67,7 @@ const searchClientVectorMemories = async (ctx, embedding, query, body = {}) => {
67
67
  return GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/elefai/v1/vector-memories/search', {
68
68
  ...body,
69
69
  query: text,
70
- embedding: await embedding.embedQuery(text),
70
+ embedding: await embedQueryCached(embedding, text),
71
71
  });
72
72
  };
73
73
  const stableStringify = (value) => {
@@ -87,6 +87,31 @@ const hashString = (value) => {
87
87
  return (hash >>> 0).toString(36);
88
88
  };
89
89
  const stableHash = (value) => hashString(typeof value === 'string' ? value : stableStringify(value));
90
+ const embeddingCacheByModel = new WeakMap();
91
+ const MAX_EMBEDDING_CACHE_ITEMS = 500;
92
+ const embedQueryCached = async (embedding, text) => {
93
+ const query = String(text || '');
94
+ if (!embedding || typeof embedding.embedQuery !== 'function')
95
+ return undefined;
96
+ let cache = embeddingCacheByModel.get(embedding);
97
+ if (!cache) {
98
+ cache = new Map();
99
+ embeddingCacheByModel.set(embedding, cache);
100
+ }
101
+ const key = stableHash(query);
102
+ if (cache.has(key))
103
+ return cache.get(key);
104
+ const promise = Promise.resolve(embedding.embedQuery(query)).catch((error) => {
105
+ cache.delete(key);
106
+ throw error;
107
+ });
108
+ cache.set(key, promise);
109
+ if (cache.size > MAX_EMBEDDING_CACHE_ITEMS) {
110
+ const firstKey = cache.keys().next().value;
111
+ cache.delete(firstKey);
112
+ }
113
+ return promise;
114
+ };
90
115
  const pickText = (values, preferredKeys) => {
91
116
  for (const key of preferredKeys) {
92
117
  const value = values === null || values === void 0 ? void 0 : values[key];
@@ -928,6 +953,34 @@ const applyOperationalPreset = (advanced = {}) => {
928
953
  toolHistoryLastN: 5,
929
954
  recentMessagesLastN: 4,
930
955
  },
956
+ productionNano: {
957
+ compactForAgent: true,
958
+ includeContextHeader: true,
959
+ includeSummary: true,
960
+ includeScores: false,
961
+ includeDiagnostics: false,
962
+ includeRelations: false,
963
+ includeToolHistory: true,
964
+ includeToolResults: true,
965
+ persistToolFactsToMem0: false,
966
+ includeToolHistorySemanticFallback: false,
967
+ includeProfileFacts: true,
968
+ includeOperationalState: true,
969
+ includeActionLedger: false,
970
+ includeEntityTimeline: false,
971
+ includeWorkingMemory: true,
972
+ includeDecisionState: true,
973
+ includeMemoryCompression: true,
974
+ includeRecentMessages: false,
975
+ includeRecentHighlights: false,
976
+ topK: 4,
977
+ lastN: 4,
978
+ maxReturn: 6,
979
+ toolHistoryLastN: 6,
980
+ recentMessagesLastN: 2,
981
+ vectorMemoryMaxChars: 220,
982
+ contextMaxChars: 6000,
983
+ },
931
984
  audit: {
932
985
  includeContextHeader: true,
933
986
  includeSummary: true,
@@ -1308,6 +1361,105 @@ const contextMemoryText = (memory, max = 700) => {
1308
1361
  }
1309
1362
  return truncate(text, max);
1310
1363
  };
1364
+ const approxTokenCount = (text) => Math.ceil(String(text || '').length / 4);
1365
+ const importantJsonFields = ['reservation_id', 'confirmation_id', 'selected_from_message', 'status', 'next_step', 'available_slots', 'timezone', 'error'];
1366
+ const pickImportantFields = (value) => {
1367
+ if (value === null || value === undefined)
1368
+ return {};
1369
+ const out = {};
1370
+ const visit = (item) => {
1371
+ if (!item || typeof item !== 'object')
1372
+ return;
1373
+ if (Array.isArray(item)) {
1374
+ item.slice(0, 3).forEach(visit);
1375
+ return;
1376
+ }
1377
+ for (const key of importantJsonFields) {
1378
+ if (item[key] !== undefined && out[key] === undefined)
1379
+ out[key] = item[key];
1380
+ }
1381
+ if (item.args)
1382
+ visit(item.args);
1383
+ if (item.result)
1384
+ visit(item.result);
1385
+ };
1386
+ visit(value);
1387
+ return out;
1388
+ };
1389
+ const compactToolResult = (result, max = 360) => {
1390
+ const text = String(result || '');
1391
+ if (!text.trim())
1392
+ return undefined;
1393
+ try {
1394
+ const parsed = JSON.parse(text);
1395
+ const picked = pickImportantFields(parsed);
1396
+ if (Object.keys(picked).length)
1397
+ return truncate(safeStringify(picked), max);
1398
+ }
1399
+ catch { }
1400
+ const picked = {};
1401
+ for (const key of importantJsonFields) {
1402
+ const re = new RegExp(`["']?${key}["']?\\s*[:=]\\s*["']?([^"',}\\]]+)`, 'i');
1403
+ const match = re.exec(text);
1404
+ if (match)
1405
+ picked[key] = match[1];
1406
+ }
1407
+ if (Object.keys(picked).length)
1408
+ return truncate(safeStringify(picked), max);
1409
+ return truncate(text, max);
1410
+ };
1411
+ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResults = true) => pruneByLimit(toolHistory || [], maxItems).map((tool) => ({
1412
+ name: tool.name,
1413
+ status: tool.ok === false ? 'failed' : 'ok',
1414
+ at: tool.at,
1415
+ input: truncate(String(tool.input || ''), 180) || undefined,
1416
+ result: includeResults ? compactToolResult(tool.result, 360) : undefined,
1417
+ }));
1418
+ const compactOperationalStateForAgent = (state = {}) => {
1419
+ const agenda = state.agenda_state || {};
1420
+ return {
1421
+ last_tool: state.last_tool || null,
1422
+ tool_counts: state.tool_counts || {},
1423
+ agenda_state: {
1424
+ has_availability: Boolean(agenda.has_availability),
1425
+ has_pre_reservation: Boolean(agenda.has_pre_reservation),
1426
+ has_confirmation: Boolean(agenda.has_confirmation),
1427
+ has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1428
+ latest_availability: compactToolResult(agenda.latest_availability_result),
1429
+ latest_pre_reservation: compactToolResult(agenda.latest_pre_reservation_result),
1430
+ latest_confirmation: compactToolResult(agenda.latest_confirmation_result),
1431
+ },
1432
+ blocked_without_context: state.blocked_without_context || [],
1433
+ guidance: (state.guidance || []).slice(0, 3),
1434
+ };
1435
+ };
1436
+ const compactMemoryCompressionForAgent = (compression = {}) => ({
1437
+ turn_summary: (compression.turn_summary || []).slice(-3),
1438
+ session_tools: (((compression.session_summary || {}).tools) || []).slice(-6),
1439
+ agenda_state: ((compression.session_summary || {}).agenda_state) ? {
1440
+ has_availability: Boolean(compression.session_summary.agenda_state.has_availability),
1441
+ has_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pre_reservation),
1442
+ has_confirmation: Boolean(compression.session_summary.agenda_state.has_confirmation),
1443
+ has_pending_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pending_pre_reservation),
1444
+ } : undefined,
1445
+ active_memory_count: ((compression.workflow_summary || {}).active_memory_count) || 0,
1446
+ });
1447
+ const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], maxItems = 3) => {
1448
+ const hasStructuredTools = Array.isArray(toolHistory) && toolHistory.length > 0;
1449
+ return (vectorMemories || [])
1450
+ .map((memory) => contextMemoryText(memory, 220))
1451
+ .filter(Boolean)
1452
+ .filter((text) => !hasStructuredTools || !/^\[tool_events_extracted\]/i.test(text))
1453
+ .slice(0, maxItems);
1454
+ };
1455
+ const contextSizeOfMessages = (messages = []) => {
1456
+ const perMessage = (messages || []).map((message, index) => {
1457
+ const content = String(message.content || '');
1458
+ return { index, role: message.role || 'system', chars: content.length, approx_tokens: approxTokenCount(content) };
1459
+ });
1460
+ const chars = perMessage.reduce((sum, item) => sum + item.chars, 0);
1461
+ return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => m.content || '').join('\n')), messages: perMessage };
1462
+ };
1311
1463
  const wrapElefaiMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1312
1464
  get(target, prop) {
1313
1465
  if (prop === 'loadMemoryVariables') {
@@ -1317,11 +1469,15 @@ const wrapElefaiMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1317
1469
  ]);
1318
1470
  try {
1319
1471
  const response = await target.loadMemoryVariables(values);
1320
- const chatHistory = snapshotJson(response[memoryKey] || response.chatHistory || []);
1472
+ const cacheHit = Boolean(response.elefaiBrainDiagnostics && response.elefaiBrainDiagnostics.cacheHit);
1473
+ const chatHistory = cacheHit
1474
+ ? [{ cached: true, messages: Array.isArray(response[memoryKey] || response.chatHistory) ? (response[memoryKey] || response.chatHistory).length : 0 }]
1475
+ : snapshotJson(response[memoryKey] || response.chatHistory || []);
1321
1476
  ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, [
1322
1477
  [{
1323
1478
  json: {
1324
1479
  action: 'loadMemoryVariables',
1480
+ cached: cacheHit || undefined,
1325
1481
  chatHistory,
1326
1482
  context: response.elefaiBrainContext,
1327
1483
  contextText: response.elefaiBrainContextText,
@@ -1377,14 +1533,81 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1377
1533
  const includeHeader = adv.includeContextHeader !== false;
1378
1534
  const includeSummary = adv.includeSummary !== false;
1379
1535
  const includeScores = adv.includeScores !== false;
1536
+ const compactForAgent = Boolean(adv.compactForAgent);
1380
1537
  const sections = [];
1381
1538
  if (includeHeader) {
1382
1539
  sections.push({
1383
1540
  section: 'context_header',
1384
1541
  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.',
1542
+ value: compactForAgent
1543
+ ? '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.'
1544
+ : '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.',
1386
1545
  });
1387
1546
  }
1547
+ if (compactForAgent) {
1548
+ if (includeSummary) {
1549
+ const summary = compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.summaryMaxFacts || 3));
1550
+ sections.push({ section: 'summary', title: 'Summary', value: summary.length ? summary : null, why_null: summary.length ? undefined : 'no non-tool vector memories to summarize' });
1551
+ }
1552
+ sections.push({
1553
+ section: 'working_memory',
1554
+ title: 'Working memory',
1555
+ value: adv.includeWorkingMemory === false ? null : (workingMemory || {}),
1556
+ why_null: adv.includeWorkingMemory === false ? 'working memory disabled' : undefined,
1557
+ });
1558
+ sections.push({
1559
+ section: 'decision_state',
1560
+ title: 'Decision state',
1561
+ value: adv.includeDecisionState === false ? null : (decisionState || {}),
1562
+ why_null: adv.includeDecisionState === false ? 'decision state disabled' : undefined,
1563
+ });
1564
+ sections.push({
1565
+ section: 'operational_state',
1566
+ title: 'Operational state',
1567
+ value: adv.includeOperationalState === false ? null : compactOperationalStateForAgent(operationalState || {}),
1568
+ why_null: adv.includeOperationalState === false ? 'operational state disabled' : undefined,
1569
+ });
1570
+ sections.push({
1571
+ section: 'tool_history',
1572
+ title: 'Tool history',
1573
+ value: {
1574
+ enabled: adv.includeToolHistory !== false,
1575
+ items: adv.includeToolHistory === false ? [] : compactToolHistoryForAgent(toolHistory, adv.toolHistoryLastN || 6, adv.includeToolResults !== false),
1576
+ },
1577
+ });
1578
+ sections.push({
1579
+ section: 'profile_facts',
1580
+ title: 'Profile facts',
1581
+ value: adv.includeProfileFacts === false ? null : renderProfileFacts(profileFacts),
1582
+ why_null: adv.includeProfileFacts === false ? 'profile facts disabled' : undefined,
1583
+ });
1584
+ sections.push({
1585
+ section: 'memory_compression',
1586
+ title: 'Memory compression',
1587
+ value: adv.includeMemoryCompression === false ? null : compactMemoryCompressionForAgent(memoryCompression || {}),
1588
+ why_null: adv.includeMemoryCompression === false ? 'memory compression disabled' : undefined,
1589
+ });
1590
+ const audit = {
1591
+ kind: 'elefai.brain.context.v1',
1592
+ meta: { user_id: userId, payloadFormat, query, compact_for_agent: true },
1593
+ sections,
1594
+ diagnostics,
1595
+ };
1596
+ const renderCompactSection = (section) => {
1597
+ if (section.value === null || section.value === undefined)
1598
+ return `## ${section.title}\n${section.why_null || 'empty'}`;
1599
+ if (typeof section.value === 'string')
1600
+ return `## ${section.title}\n${section.value}`;
1601
+ if (Array.isArray(section.value))
1602
+ return `## ${section.title}\n${section.value.length ? section.value.map((item) => typeof item === 'string' ? `- ${item}` : `- ${truncate(safeStringify(item), 420)}`).join('\n') : '(empty)'}`;
1603
+ return `## ${section.title}\n${truncate(safeStringify(section.value), 900)}`;
1604
+ };
1605
+ if (payloadFormat === 'auditJson')
1606
+ return [{ role: 'system', content: JSON.stringify(audit, null, 2) }];
1607
+ if (payloadFormat === 'auditBlocks')
1608
+ return sections.map((section) => ({ role: 'system', content: renderCompactSection(section) }));
1609
+ return [{ role: 'system', content: truncate(sections.map(renderCompactSection).join('\n\n'), Number(adv.contextMaxChars || 6000)) }];
1610
+ }
1388
1611
  if (includeSummary) {
1389
1612
  const summary = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 360)).filter(Boolean);
1390
1613
  sections.push({ section: 'summary', title: 'Summary', value: summary.length ? summary : null, why_null: summary.length ? undefined : 'no vector memories to summarize' });
@@ -1654,6 +1877,7 @@ class Mem0Memory {
1654
1877
  { name: 'Diagnóstico Completo', value: 'diagnostic' },
1655
1878
  { name: 'Produção Balanceada', value: 'productionBalanced' },
1656
1879
  { name: 'Produção Econômica', value: 'productionCheap' },
1880
+ { name: 'Produção Nano/SLM', value: 'productionNano' },
1657
1881
  { name: 'Auditoria', value: 'audit' },
1658
1882
  ],
1659
1883
  default: 'custom',
@@ -1673,6 +1897,8 @@ class Mem0Memory {
1673
1897
  { displayName: 'MMR (diversidade)', name: 'mmr', type: 'boolean', default: true, description: 'Aplicar diversidade de resultados (Maximal Marginal Relevance)', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
1674
1898
  { 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
1899
  { displayName: 'Incluir Cabeçalho de Contexto', name: 'includeContextHeader', type: 'boolean', default: true },
1900
+ { 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.' },
1901
+ { 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
1902
  { displayName: 'Incluir Resumo', name: 'includeSummary', type: 'boolean', default: true },
1677
1903
  { displayName: 'Máximo de Fatos no Resumo', name: 'summaryMaxFacts', type: 'number', default: 4 },
1678
1904
  { displayName: 'Incluir Scores', name: 'includeScores', type: 'boolean', default: true },
@@ -1706,6 +1932,7 @@ class Mem0Memory {
1706
1932
  async supplyData(itemIndex) {
1707
1933
  const memoryKey = this.getNodeParameter('memoryKey', itemIndex);
1708
1934
  let currentMessages = [];
1935
+ const loadCache = new Map();
1709
1936
  const memory = {
1710
1937
  memoryKeys: [memoryKey],
1711
1938
  inputKey: 'input',
@@ -1736,7 +1963,38 @@ class Mem0Memory {
1736
1963
  },
1737
1964
  },
1738
1965
  loadMemoryVariables: async (inputValues = {}) => {
1739
- const result = await Mem0Memory.prototype.loadMemoryVariablesForItem.call(this, itemIndex, inputValues);
1966
+ const cacheKey = stableHash({ itemIndex, inputValues, memoryKey });
1967
+ let result;
1968
+ if (loadCache.has(cacheKey)) {
1969
+ result = snapshotJson(loadCache.get(cacheKey));
1970
+ result.response = result.response || {};
1971
+ result.response.elefaiBrainDiagnostics = {
1972
+ ...(result.response.elefaiBrainDiagnostics || {}),
1973
+ cacheHit: true,
1974
+ cacheScope: 'supplyData.loadMemoryVariables',
1975
+ };
1976
+ if (result.response.elefaiBrainContext) {
1977
+ result.response.elefaiBrainContext = {
1978
+ kind: result.response.elefaiBrainContext.kind,
1979
+ userId: result.response.elefaiBrainContext.userId,
1980
+ project: result.response.elefaiBrainContext.project,
1981
+ query: result.response.elefaiBrainContext.query,
1982
+ retrievalMode: result.response.elefaiBrainContext.retrievalMode,
1983
+ payloadFormat: result.response.elefaiBrainContext.payloadFormat,
1984
+ contextQualityScore: result.response.elefaiBrainContext.contextQualityScore,
1985
+ cacheHit: true,
1986
+ };
1987
+ }
1988
+ delete result.response.elefaiBrainContextText;
1989
+ }
1990
+ else {
1991
+ result = await Mem0Memory.prototype.loadMemoryVariablesForItem.call(this, itemIndex, inputValues);
1992
+ loadCache.set(cacheKey, snapshotJson(result));
1993
+ if (loadCache.size > 20) {
1994
+ const firstKey = loadCache.keys().next().value;
1995
+ loadCache.delete(firstKey);
1996
+ }
1997
+ }
1740
1998
  const chatHistory = (result.response[memoryKey] || []).map(toBaseMessage);
1741
1999
  const extra = { ...(result.response || {}) };
1742
2000
  delete extra[memoryKey];
@@ -1748,6 +2006,7 @@ class Mem0Memory {
1748
2006
  };
1749
2007
  },
1750
2008
  saveContext: async (inputValues = {}, outputValues = {}) => {
2009
+ loadCache.clear();
1751
2010
  const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
1752
2011
  const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
1753
2012
  if (input)
@@ -2030,7 +2289,7 @@ class Mem0Memory {
2030
2289
  }
2031
2290
  if (connectedEmbedding && typeof connectedEmbedding.embedQuery === 'function' && String(query || '').trim()) {
2032
2291
  try {
2033
- await connectedEmbedding.embedQuery(String(query));
2292
+ await embedQueryCached(connectedEmbedding, String(query));
2034
2293
  connectedAi.embeddingQuery = true;
2035
2294
  }
2036
2295
  catch (error) {
@@ -2270,10 +2529,10 @@ class Mem0Memory {
2270
2529
  catch { }
2271
2530
  }
2272
2531
  let connectedModelSummary = '';
2273
- if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && (vectorMemories.length || String(query || '').trim()) && adv.includeSummary !== false) {
2532
+ if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && vectorMemories.length && adv.includeSummary !== false) {
2274
2533
  try {
2275
2534
  const facts = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 500)).filter(Boolean).join('\n') || '(no vector memories found)';
2276
- if (facts || String(query || '').trim()) {
2535
+ if (facts) {
2277
2536
  const response = await connectedLanguageModel.invoke([
2278
2537
  toBaseMessage({
2279
2538
  role: 'user',
@@ -2362,6 +2621,7 @@ class Mem0Memory {
2362
2621
  diagnostics,
2363
2622
  adv,
2364
2623
  });
2624
+ diagnostics.contextSize = contextSizeOfMessages(payload);
2365
2625
  const normalizedVectorMemories = vectorMemories.map((m) => {
2366
2626
  const scoreMeta = scoreMetaOf(m);
2367
2627
  const rawScore = scoreOf(m);
@@ -2404,6 +2664,7 @@ class Mem0Memory {
2404
2664
  includeOperationalState: adv.includeOperationalState !== false,
2405
2665
  includeActionLedger: adv.includeActionLedger !== false,
2406
2666
  includeEntityTimeline: adv.includeEntityTimeline !== false,
2667
+ compactForAgent: Boolean(adv.compactForAgent),
2407
2668
  includeWorkingMemory: adv.includeWorkingMemory !== false,
2408
2669
  includeDecisionState: adv.includeDecisionState !== false,
2409
2670
  includeMemoryCompression: adv.includeMemoryCompression !== false,
@@ -2587,4 +2848,10 @@ exports.__private = {
2587
2848
  mergeProfileFacts,
2588
2849
  renderProfileFacts,
2589
2850
  isNoisyProfileValue,
2851
+ approxTokenCount,
2852
+ contextSizeOfMessages,
2853
+ embedQueryCached,
2854
+ compactToolResult,
2855
+ compactToolHistoryForAgent,
2856
+ compactOperationalStateForAgent,
2590
2857
  };
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.9",
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
+ }