n8n-nodes-tembory 1.0.22 → 1.0.24

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,7 +2,18 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.0.22`.
5
+ Versao atual: `1.0.24`.
6
+
7
+ ## 1.0.24
8
+
9
+ - Corrige falha `409 Backend LLM/embedder is disabled` no `saveContext` quando o node usa embeddings conectados pelo n8n.
10
+ - Writes legados de transcript/tool markers agora sao best-effort no modo BYO-AI, sem derrubar a execucao do agente.
11
+ - Remove o prefixo tecnico `THREAD:<id>` usado em testes MCP antes de montar query/contexto de memoria.
12
+
13
+ ## 1.0.23
14
+
15
+ - Corrige o frame de conversa para nao repetir a mensagem atual em `previous_user_message` quando ainda nao existe mensagem anterior real.
16
+ - Mantem `previous_user_message: null` explicitamente em perguntas de recall no primeiro turno, evitando respostas erradas sobre "ultima mensagem".
6
17
 
7
18
  ## 1.0.22
8
19
 
@@ -70,6 +70,22 @@ const searchClientVectorMemories = async (ctx, embedding, query, body = {}) => {
70
70
  embedding: await embedQueryCached(embedding, text),
71
71
  });
72
72
  };
73
+ const safePersistLegacyMemory = async (ctx, body, diagnostics = {}) => {
74
+ try {
75
+ return await GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/v1/memories/', body);
76
+ }
77
+ catch (error) {
78
+ try {
79
+ ctx.logger?.warn('Tembory skipped legacy memory write after BYO-AI vector persistence', {
80
+ ...diagnostics,
81
+ message: error.message || String(error),
82
+ });
83
+ }
84
+ catch { }
85
+ return null;
86
+ }
87
+ };
88
+ const stripThreadTestPrefix = (value = '') => String(value || '').replace(/^THREAD:[A-Za-z0-9_-]+\s+/, '');
73
89
  const stableStringify = (value) => {
74
90
  if (value === null || typeof value !== 'object')
75
91
  return JSON.stringify(value);
@@ -1177,19 +1193,30 @@ const previousUserMessageForQuery = (query = '', recentMessages = []) => {
1177
1193
  return users[1] || null;
1178
1194
  return users[0] || null;
1179
1195
  };
1196
+ const previousUserFallbackFromWorkingMemory = (query = '', workingMemory = {}) => {
1197
+ const candidate = workingMemory === null || workingMemory === void 0 ? void 0 : workingMemory.last_user_message;
1198
+ if (!candidate)
1199
+ return null;
1200
+ const normalizedQuery = normalizeIntentText(query).trim();
1201
+ const normalizedCandidate = normalizeIntentText(candidate).trim();
1202
+ if (normalizedQuery && normalizedCandidate === normalizedQuery)
1203
+ return null;
1204
+ return candidate;
1205
+ };
1180
1206
  const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory = {} }) => {
1181
1207
  const normalizedQuery = normalizeIntentText(query).trim();
1182
1208
  const users = recentUserMessages(recentMessages);
1183
1209
  const currentUser = users.find((msg) => normalizeIntentText(msg.content).trim() === normalizedQuery) || (query ? { role: 'user', content: query, at: nowIso() } : null);
1184
1210
  const previousUser = previousUserMessageForQuery(query, recentMessages);
1211
+ const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
1185
1212
  const chronological = pruneByLimit(recentMessages || [], 10).map((msg) => ({
1186
1213
  role: msg.role,
1187
1214
  content: truncate(msg.content, 500),
1188
1215
  at: msg.at,
1189
1216
  }));
1190
- return cleanContextValue({
1217
+ const frame = cleanContextValue({
1191
1218
  current_user_message: currentUser ? truncate(currentUser.content, 500) : truncate(query, 500),
1192
- previous_user_message: previousUser ? truncate(previousUser.content, 500) : (workingMemory === null || workingMemory === void 0 ? void 0 : workingMemory.last_user_message) || null,
1219
+ previous_user_message: previousUserMessage,
1193
1220
  recent_messages_chronological: chronological,
1194
1221
  recent_user_messages: users
1195
1222
  .filter((msg) => normalizeIntentText(msg.content).trim() !== normalizedQuery)
@@ -1197,6 +1224,9 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
1197
1224
  .map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at })),
1198
1225
  instruction: 'This is the authoritative short-term conversation frame. If the user asks about the last/current/previous client message, answer from previous_user_message/recent_user_messages before using vector memories, summaries, operational state, or tool history.',
1199
1226
  });
1227
+ if (!previousUserMessage)
1228
+ frame.previous_user_message = null;
1229
+ return frame;
1200
1230
  };
1201
1231
  const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalState = {}, workingMemory = {} }) => {
1202
1232
  const recall = isConversationRecallQuery(query);
@@ -1209,10 +1239,11 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
1209
1239
  .slice(0, 3)
1210
1240
  .map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at }));
1211
1241
  const agenda = (operationalState || {}).agenda_state || {};
1212
- return cleanContextValue({
1242
+ const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
1243
+ const focus = cleanContextValue({
1213
1244
  current_user_request: truncate(query, 500),
1214
1245
  intent: recall ? 'conversation_recall' : 'agenda_status_question',
1215
- previous_user_message: previousUser ? truncate(previousUser.content, 500) : (workingMemory === null || workingMemory === void 0 ? void 0 : workingMemory.last_user_message) || null,
1246
+ previous_user_message: previousUserMessage,
1216
1247
  recent_user_messages: users,
1217
1248
  agenda_status: agendaStatus ? {
1218
1249
  reservation_status: agenda.has_confirmation && !agenda.has_pending_pre_reservation
@@ -1232,6 +1263,9 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
1232
1263
  ? 'Answer the user meta-question from previous_user_message/recent_user_messages. Do not answer with agenda availability unless the user asks for availability.'
1233
1264
  : 'Answer whether the appointment/reservation is already scheduled using agenda_status. Do not offer availability as the main answer unless there is no schedule/reservation state.',
1234
1265
  });
1266
+ if (!previousUserMessage)
1267
+ focus.previous_user_message = null;
1268
+ return focus;
1235
1269
  };
1236
1270
  const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
1237
1271
  const text = normalizeIntentText(query);
@@ -2598,7 +2632,7 @@ class Mem0Memory {
2598
2632
  const adv = applyOperationalPreset(readAdvancedOptions(this, itemIndex));
2599
2633
  const store = getMemoryStore(this);
2600
2634
  const key = userKeyFrom(threadId, adv, project);
2601
- const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
2635
+ const input = stripThreadTestPrefix(pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']));
2602
2636
  const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
2603
2637
  const toolCalls = extractToolCalls(outputValues);
2604
2638
  const recentForMem0 = [];
@@ -2740,7 +2774,7 @@ class Mem0Memory {
2740
2774
  await saveClientVectorMemories(this, clientMemories, ids);
2741
2775
  if (adv.includeRecentMessages !== false && recentForMem0.length) {
2742
2776
  for (const recent of recentForMem0) {
2743
- await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', {
2777
+ await safePersistLegacyMemory(this, {
2744
2778
  messages: [{ role: 'system', content: encodeRecentMessage(recent, threadId) }],
2745
2779
  infer: false,
2746
2780
  user_id: body.user_id,
@@ -2755,12 +2789,15 @@ class Mem0Memory {
2755
2789
  project: project || undefined,
2756
2790
  source: 'tembory_transcript',
2757
2791
  },
2792
+ }, {
2793
+ kind: 'recent_message',
2794
+ user_id: body.user_id,
2758
2795
  });
2759
2796
  }
2760
2797
  }
2761
2798
  if (adv.includeToolHistory !== false && toolCalls.length) {
2762
2799
  for (const tool of toolCalls) {
2763
- await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', {
2800
+ await safePersistLegacyMemory(this, {
2764
2801
  messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
2765
2802
  infer: false,
2766
2803
  user_id: body.user_id,
@@ -2780,6 +2817,10 @@ class Mem0Memory {
2780
2817
  thread_id: threadId,
2781
2818
  project: project || undefined,
2782
2819
  },
2820
+ }, {
2821
+ kind: 'tool_history',
2822
+ user_id: body.user_id,
2823
+ tool: tool.name,
2783
2824
  });
2784
2825
  }
2785
2826
  }
@@ -2874,7 +2915,7 @@ class Mem0Memory {
2874
2915
  const store = getMemoryStore(this);
2875
2916
  const key = userKeyFrom(threadId, adv, project);
2876
2917
  const queryParam = this.getNodeParameter('query', itemIndex, '');
2877
- const query = asSearchQuery(queryParam) || asSearchQuery(inputValues.query) || asSearchQuery(inputValues.input) || asSearchQuery(inputValues.chatInput);
2918
+ const query = stripThreadTestPrefix(asSearchQuery(queryParam) || asSearchQuery(inputValues.query) || asSearchQuery(inputValues.input) || asSearchQuery(inputValues.chatInput));
2878
2919
  let connectedLanguageModel;
2879
2920
  let connectedEmbedding;
2880
2921
  const connectedAi = {
@@ -3487,6 +3528,7 @@ exports.__private = {
3487
3528
  explicitToolHistoryItemsFromMemory,
3488
3529
  toolHistoryFromMemory,
3489
3530
  recentMessageFromMemory,
3531
+ previousUserFallbackFromWorkingMemory,
3490
3532
  dedupeToolHistory,
3491
3533
  applyToolHistoryWindow,
3492
3534
  dedupeRecentMessages,
@@ -3498,6 +3540,8 @@ exports.__private = {
3498
3540
  applyOperationalPreset,
3499
3541
  flattenAdvancedGroups,
3500
3542
  asSearchQuery,
3543
+ safePersistLegacyMemory,
3544
+ stripThreadTestPrefix,
3501
3545
  canonicalToolInput,
3502
3546
  buildContextMessages,
3503
3547
  inferToolGuard,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
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",