n8n-nodes-tembory 1.0.35 → 1.0.37

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.
@@ -589,9 +589,7 @@ const encodeRecentMessage = (recent, threadId) => `${RECENT_MESSAGE_MARKER}${saf
589
589
  const encodeConversationLedger = (messages = [], threadId) => {
590
590
  const chronological = sortConversationChronological(messages || []);
591
591
  const userMessages = chronological.filter((msg) => /^(user|human)$/i.test(String(msg.role || '')));
592
- const firstUser = userMessages[0];
593
- const lastUser = userMessages[userMessages.length - 1];
594
- const readable = `Conversation transcript ledger. Use this ledger to answer first message, previous message, last message, conversation history and what the user already said. First user message: ${firstUser?.content || ''}. Last user message: ${lastUser?.content || ''}. `;
592
+ const readable = `Conversation transcript ledger. Use the chronological messages array to answer conversation recall, including what the user said, message order, and the user message immediately before the current request. Total messages: ${chronological.length}. User messages: ${userMessages.length}. `;
595
593
  return `${readable}${CONVERSATION_LEDGER_MARKER}${safeStringify({
596
594
  thread_id: threadId,
597
595
  generated_at: nowIso(),
@@ -1069,7 +1067,7 @@ const applyOperationalPreset = (advanced = {}) => {
1069
1067
  topK: 8,
1070
1068
  lastN: 12,
1071
1069
  toolHistoryLastN: 15,
1072
- recentMessagesLastN: 30,
1070
+ recentMessagesLastN: 50,
1073
1071
  },
1074
1072
  productionBalanced: {
1075
1073
  summarySource: 'auto',
@@ -1102,7 +1100,7 @@ const applyOperationalPreset = (advanced = {}) => {
1102
1100
  topK: 6,
1103
1101
  lastN: 8,
1104
1102
  toolHistoryLastN: 10,
1105
- recentMessagesLastN: 30,
1103
+ recentMessagesLastN: 50,
1106
1104
  vectorMemoryMaxChars: 360,
1107
1105
  contextMaxChars: 10000,
1108
1106
  connectedModelSummaryMaxChars: 1200,
@@ -1139,7 +1137,7 @@ const applyOperationalPreset = (advanced = {}) => {
1139
1137
  topK: 4,
1140
1138
  lastN: 4,
1141
1139
  toolHistoryLastN: 5,
1142
- recentMessagesLastN: 30,
1140
+ recentMessagesLastN: 50,
1143
1141
  vectorMemoryMaxChars: 260,
1144
1142
  contextMaxChars: 7000,
1145
1143
  connectedModelSummaryMaxChars: 1000,
@@ -1177,7 +1175,7 @@ const applyOperationalPreset = (advanced = {}) => {
1177
1175
  lastN: 4,
1178
1176
  maxReturn: 6,
1179
1177
  toolHistoryLastN: 6,
1180
- recentMessagesLastN: 30,
1178
+ recentMessagesLastN: 50,
1181
1179
  vectorMemoryMaxChars: 220,
1182
1180
  contextMaxChars: 7000,
1183
1181
  connectedModelSummaryMaxChars: 1200,
@@ -1207,7 +1205,7 @@ const applyOperationalPreset = (advanced = {}) => {
1207
1205
  topK: 10,
1208
1206
  lastN: 20,
1209
1207
  toolHistoryLastN: 20,
1210
- recentMessagesLastN: 30,
1208
+ recentMessagesLastN: 50,
1211
1209
  payloadFormat: 'auditJson',
1212
1210
  },
1213
1211
  };
@@ -1375,13 +1373,10 @@ const previousUserFallbackFromWorkingMemory = (query = '', workingMemory = {}) =
1375
1373
  const firstUserMessageFromConversation = (recentMessages = []) => [...(recentMessages || [])]
1376
1374
  .filter((msg) => /^(user|human)$/i.test(String(msg.role || '')))
1377
1375
  .sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime())[0] || null;
1378
- const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory = {} }) => {
1376
+ const buildConversationFrame = ({ query = '', recentMessages = [] }) => {
1379
1377
  const normalizedQuery = normalizeIntentText(query).trim();
1380
1378
  const users = recentUserMessages(recentMessages);
1381
1379
  const currentUser = users.find((msg) => normalizeIntentText(msg.content).trim() === normalizedQuery) || (query ? { role: 'user', content: query, at: nowIso() } : null);
1382
- const previousUser = previousUserMessageForQuery(query, recentMessages);
1383
- const firstUser = firstUserMessageFromConversation(recentMessages);
1384
- const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
1385
1380
  const chronological = (recentMessages || []).map((msg) => ({
1386
1381
  role: msg.role,
1387
1382
  content: truncate(msg.content, 500),
@@ -1390,8 +1385,6 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
1390
1385
  const userMessagesChronological = chronological.filter((msg) => /^(user|human)$/i.test(String(msg.role || '')));
1391
1386
  const frame = cleanContextValue({
1392
1387
  current_user_message: currentUser ? truncate(currentUser.content, 500) : truncate(query, 500),
1393
- first_user_message: firstUser ? truncate(firstUser.content, 500) : null,
1394
- previous_user_message: previousUserMessage,
1395
1388
  conversation_history_chronological: chronological,
1396
1389
  recent_messages_chronological: chronological,
1397
1390
  all_user_messages_chronological: userMessagesChronological,
@@ -1399,10 +1392,8 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
1399
1392
  .filter((msg) => normalizeIntentText(msg.content).trim() !== normalizedQuery)
1400
1393
  .slice(0, 5)
1401
1394
  .map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at })),
1402
- instruction: 'This is the authoritative short-term conversation frame. If the user asks about current/previous/last client messages or what they already said, answer from current_user_message/previous_user_message/conversation_history_chronological/all_user_messages_chronological before using vector memories, summaries, operational state, or tool history.',
1395
+ instruction: 'This is the authoritative short-term conversation frame. If the user asks about current/previous/last client messages or what they already said, answer from conversation_history_chronological/all_user_messages_chronological before using vector memories, summaries, operational state, or tool history. For the last message before the current one, use the user message immediately before current_user_message in all_user_messages_chronological.',
1403
1396
  });
1404
- if (!previousUserMessage)
1405
- frame.previous_user_message = null;
1406
1397
  return frame;
1407
1398
  };
1408
1399
  const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalState = {}, workingMemory = {} }) => {
@@ -1410,31 +1401,31 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
1410
1401
  const operationalStatus = isOperationalStatusQuery(query);
1411
1402
  if (!recall && !operationalStatus)
1412
1403
  return null;
1413
- const previousUser = previousUserMessageForQuery(query, recentMessages);
1414
- const firstUser = firstUserMessageFromConversation(recentMessages);
1415
1404
  const users = recentUserMessages(recentMessages)
1416
1405
  .filter((msg) => normalizeIntentText(msg.content).trim() !== normalizeIntentText(query).trim())
1417
- .slice(0, 3)
1406
+ .slice(0, 8)
1418
1407
  .map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at }));
1419
1408
  const toolState = (operationalState || {}).tool_state || {};
1420
- const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
1409
+ const chronological = (recentMessages || []).map((msg) => ({
1410
+ role: msg.role,
1411
+ content: truncate(msg.content, 500),
1412
+ at: msg.at,
1413
+ }));
1414
+ const userMessagesChronological = chronological.filter((msg) => /^(user|human)$/i.test(String(msg.role || '')));
1421
1415
  const focus = cleanContextValue({
1422
1416
  current_user_request: truncate(query, 500),
1423
1417
  intent: recall ? 'conversation_recall' : 'tool_or_status_question',
1424
- first_user_message: firstUser ? truncate(firstUser.content, 500) : null,
1425
- previous_user_message: previousUserMessage,
1426
1418
  recent_user_messages: users,
1419
+ all_user_messages_chronological: recall ? userMessagesChronological : undefined,
1427
1420
  tool_status: operationalStatus ? {
1428
1421
  executed_tools: toolState.names || [],
1429
1422
  last_successful_tool: toolState.last_successful_tool || null,
1430
1423
  failed_by_name: toolState.failed_by_name || {},
1431
1424
  } : undefined,
1432
1425
  instruction: recall
1433
- ? 'Answer the user meta-question from previous_user_message/recent_user_messages and the chronological transcript. Do not call tools for recall-only questions.'
1426
+ ? 'Answer the user meta-question from conversation_history_chronological/all_user_messages_chronological. Do not call tools for recall-only questions and do not infer missing turns from vector memories or summaries.'
1434
1427
  : 'Answer status questions from tool_state, tool_history and action_ledger. Do not call tools unless the agent prompt requires it.',
1435
1428
  });
1436
- if (!previousUserMessage)
1437
- focus.previous_user_message = null;
1438
1429
  return focus;
1439
1430
  };
1440
1431
  const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) => {
@@ -1490,7 +1481,7 @@ const deriveNextExpectedAction = (intent, operationalState = {}) => {
1490
1481
  if (intent === 'profile_update')
1491
1482
  return 'save stable profile facts and continue the conversation';
1492
1483
  if (intent === 'conversation_recall')
1493
- return 'answer directly using previous_user_message and conversation_history_chronological; do not call tools for recall-only questions';
1484
+ return 'answer directly using conversation_history_chronological/all_user_messages_chronological; do not call tools for recall-only questions';
1494
1485
  if (intent === 'operational_status_question')
1495
1486
  return 'answer status questions from tool_state, tool_history and action_ledger; do not call tools unless the agent prompt requires it';
1496
1487
  return 'answer using retrieved context and avoid unnecessary tool calls';
@@ -2474,7 +2465,7 @@ class Mem0Memory {
2474
2465
  values: [
2475
2466
  { displayName: 'Incluir Profile Facts', name: 'includeProfileFacts', type: 'boolean', default: true },
2476
2467
  { displayName: 'Incluir Mensagens Recentes', name: 'includeRecentMessages', type: 'boolean', default: true },
2477
- { displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default: 30 },
2468
+ { displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default: 50 },
2478
2469
  { displayName: 'Incluir Highlights Recentes', name: 'includeRecentHighlights', type: 'boolean', default: true },
2479
2470
  { displayName: 'Máximo de Highlights', name: 'recentHighlightsMaxItems', type: 'number', default: 6 },
2480
2471
  { displayName: 'Incluir Grafo', name: 'includeRelations', type: 'boolean', default: true },
@@ -2629,7 +2620,7 @@ class Mem0Memory {
2629
2620
  { displayName: 'Incluir Entity Timeline', name: 'includeEntityTimeline', type: 'boolean', default: true, description: 'Injeta uma timeline compacta de entidades, fatos de perfil, relações do grafo e eventos importantes da sessão.' },
2630
2621
  { displayName: 'Incluir Compressão de Memória', name: 'includeMemoryCompression', type: 'boolean', default: true, description: 'Injeta resumos compactos de turno, sessão, entidades e workflow para reduzir ruído.' },
2631
2622
  { displayName: 'Incluir Mensagens Recentes', name: 'includeRecentMessages', type: 'boolean', default: true },
2632
- { displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default: 30 },
2623
+ { displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default: 50 },
2633
2624
  { displayName: 'Incluir Highlights Recentes', name: 'includeRecentHighlights', type: 'boolean', default: true },
2634
2625
  { displayName: 'Máximo de Highlights', name: 'recentHighlightsMaxItems', type: 'number', default: 6 },
2635
2626
  ],
@@ -3352,7 +3343,7 @@ class Mem0Memory {
3352
3343
  try {
3353
3344
  const transcriptProbe = [
3354
3345
  'recent conversation transcript',
3355
- 'first user message earliest message last user messages previous message full conversation',
3346
+ 'chronological user messages earliest message latest message message immediately before current request full conversation',
3356
3347
  query,
3357
3348
  ].filter(Boolean).join('\n');
3358
3349
  const transcriptBody = {
@@ -3383,12 +3374,47 @@ class Mem0Memory {
3383
3374
  connectedAi.errors.push(`recentMessageProbe: ${error.message || String(error)}`);
3384
3375
  }
3385
3376
  }
3377
+ if (connectedEmbedding && adv.includeToolHistory !== false) {
3378
+ try {
3379
+ const toolProbe = [
3380
+ 'Tool history ledger',
3381
+ 'tools called in this thread tool inputs tool outputs tool status tool order action ledger',
3382
+ query,
3383
+ ].filter(Boolean).join('\n');
3384
+ const seen = new Set(vectorMemories.map((memory) => stableHash(memoryText(memory) || safeStringify(memory))));
3385
+ for (const kind of ['tool_ledger', 'tool_history']) {
3386
+ const toolBody = {
3387
+ user_id: key,
3388
+ agent_id: adv.agentId ? String(adv.agentId) : undefined,
3389
+ run_id: adv.runId ? String(adv.runId) : undefined,
3390
+ top_k: Math.max(Number(adv.toolHistoryLastN || 10) * 4, 40),
3391
+ filters: { kind },
3392
+ };
3393
+ const toolRes = await searchClientVectorMemories(this, connectedEmbedding, toolProbe, toolBody);
3394
+ const toolMemories = normalizeResults(toolRes).map((memory) => withTemboryScore(memory, {
3395
+ semanticScore: scoreOf(memory),
3396
+ source: `${kind}_probe`,
3397
+ }));
3398
+ for (const memory of toolMemories) {
3399
+ const hash = stableHash(memoryText(memory) || safeStringify(memory));
3400
+ if (!seen.has(hash)) {
3401
+ seen.add(hash);
3402
+ vectorMemories.push(memory);
3403
+ }
3404
+ }
3405
+ }
3406
+ connectedAi.toolLedgerProbe = true;
3407
+ }
3408
+ catch (error) {
3409
+ connectedAi.errors.push(`toolLedgerProbe: ${error.message || String(error)}`);
3410
+ }
3411
+ }
3386
3412
  let persistedRecentMessages = [];
3387
3413
  let persistedToolHistory = [];
3388
3414
  let persistedMemoryItems = [];
3389
3415
  if (adv.includeRecentMessages !== false || adv.includeToolHistory !== false) {
3390
3416
  try {
3391
- const qs = { user_id: key };
3417
+ const qs = { user_id: key, limit: Math.max(Number(adv.recentMessagesLastN || 50) * 8, Number(adv.toolHistoryLastN || 10) * 12, 200) };
3392
3418
  if (adv.agentId)
3393
3419
  qs.agent_id = String(adv.agentId);
3394
3420
  if (adv.runId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
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",
@@ -42,8 +42,40 @@ function requiredToolFromText(text) {
42
42
  return /"required_tool":"([^"]+)"/.exec(text)?.[1] || null;
43
43
  }
44
44
 
45
- function previousUserMessageFromText(text) {
46
- return /"previous_user_message":"((?:\\"|[^"])*)"/.exec(text)?.[1]?.replace(/\\"/g, '"') || '';
45
+ function conversationFrameFromText(text) {
46
+ const match = /## Conversation frame\n([\s\S]*?)(?:\n\n## |\n## |$)/.exec(text);
47
+ if (!match) return null;
48
+ try {
49
+ return JSON.parse(match[1].trim());
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ function previousUserMessageFromText(text, currentMessage) {
56
+ const frame = conversationFrameFromText(text);
57
+ const userMessages = Array.isArray(frame?.all_user_messages_chronological)
58
+ ? frame.all_user_messages_chronological
59
+ : [];
60
+ const normalizedCurrent = normalizeText(currentMessage).trim();
61
+ for (let index = userMessages.length - 1; index >= 0; index -= 1) {
62
+ const message = userMessages[index];
63
+ if (normalizeText(message?.content).trim() === normalizedCurrent) {
64
+ return userMessages[index - 1]?.content || '';
65
+ }
66
+ }
67
+ return userMessages.at(-2)?.content || '';
68
+ }
69
+
70
+ function previousUserMessageFromState(messages, currentMessage) {
71
+ const userMessages = (messages || []).filter((message) => /^(user|human)$/i.test(String(message.role || '')));
72
+ const normalizedCurrent = normalizeText(currentMessage).trim();
73
+ for (let index = userMessages.length - 1; index >= 0; index -= 1) {
74
+ if (normalizeText(userMessages[index]?.content).trim() === normalizedCurrent) {
75
+ return userMessages[index - 1]?.content || '';
76
+ }
77
+ }
78
+ return userMessages.at(-2)?.content || '';
47
79
  }
48
80
 
49
81
  function normalizeText(value) {
@@ -105,7 +137,8 @@ function answerFor({ userMessage, toolCalls, state, contextText }) {
105
137
  .join('\n');
106
138
  }
107
139
  if (/qual.*ultima mensagem antes/.test(normalized)) {
108
- const previous = previousUserMessageFromText(contextText)
140
+ const previous = previousUserMessageFromText(contextText, userMessage)
141
+ || previousUserMessageFromState(state.recentMessages, userMessage)
109
142
  || core.previousUserFallbackFromWorkingMemory(userMessage, state.workingMemory);
110
143
  return previous ? `Sua última mensagem antes desta foi: "${previous}".` : 'Não encontrei mensagem anterior.';
111
144
  }