n8n-nodes-tembory 1.0.36 → 1.0.38

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.
@@ -852,10 +852,13 @@ const makeToolEventId = (tool, sequence = 0) => {
852
852
  return `tool_${hashString(base)}`;
853
853
  };
854
854
  const canonicalToolInput = (value) => {
855
- const text = String(value === undefined || value === null ? '' : value).trim();
856
- if (text === '""' || text === "''")
855
+ const text = value === undefined || value === null
856
+ ? ''
857
+ : (typeof value === 'string' ? value : stableStringify(value));
858
+ const trimmed = String(text).trim();
859
+ if (trimmed === '""' || trimmed === "''")
857
860
  return '';
858
- return text;
861
+ return trimmed;
859
862
  };
860
863
  const normalizeToolCall = (tool, sequence = 0, defaults = {}) => {
861
864
  const at = tool.at || defaults.at || nowIso();
@@ -910,6 +913,73 @@ const readDeep = (value, visitor, seen = new Set()) => {
910
913
  for (const item of Object.values(value))
911
914
  readDeep(item, visitor, seen);
912
915
  };
916
+ const messageTypeOf = (message) => {
917
+ if (typeof (message === null || message === void 0 ? void 0 : message._getType) === 'function')
918
+ return String(message._getType() || '');
919
+ return String((message === null || message === void 0 ? void 0 : message.role) || (message === null || message === void 0 ? void 0 : message.type) || (message === null || message === void 0 ? void 0 : message.lc_kwargs && message.lc_kwargs.type) || '').toLowerCase();
920
+ };
921
+ const messageContentOf = (message) => typeof (message === null || message === void 0 ? void 0 : message.content) === 'string' ? message.content : safeStringify(message === null || message === void 0 ? void 0 : message.content);
922
+ const extractToolCallsFromMessages = (messages = []) => {
923
+ const calls = [];
924
+ const toolCallById = new Map();
925
+ const push = (tool, index = calls.length) => {
926
+ if (!tool || !(tool.name || tool.tool || tool.toolName))
927
+ return;
928
+ const normalized = normalizeToolCall({
929
+ id: tool.id || tool.callId || tool.call_id || tool.toolCallId || tool.tool_call_id,
930
+ turnId: tool.turnId || tool.turn_id,
931
+ sequence: tool.sequence || index + 1,
932
+ name: tool.name || tool.tool || tool.toolName,
933
+ input: tool.input !== undefined ? tool.input : tool.args !== undefined ? tool.args : tool.toolInput !== undefined ? tool.toolInput : '',
934
+ ok: tool.ok !== false,
935
+ result: tool.result !== undefined ? tool.result : tool.output !== undefined ? tool.output : tool.observation !== undefined ? tool.observation : '',
936
+ at: tool.at || nowIso(),
937
+ source: tool.source || 'langchain_message',
938
+ }, calls.length + 1);
939
+ calls.push(normalized);
940
+ if (normalized.id)
941
+ toolCallById.set(String(normalized.id), normalized);
942
+ };
943
+ for (const [index, message] of (messages || []).entries()) {
944
+ const type = messageTypeOf(message);
945
+ const additional = (message && (message.additional_kwargs || message.additionalKwargs)) || {};
946
+ const kwargs = (message && message.lc_kwargs) || {};
947
+ const toolCalls = message && (message.tool_calls || message.toolCalls || additional.tool_calls || additional.toolCalls || kwargs.tool_calls || kwargs.toolCalls);
948
+ if (Array.isArray(toolCalls)) {
949
+ for (const tool of toolCalls)
950
+ push({ ...tool, source: 'ai_message_tool_calls' }, index);
951
+ }
952
+ const invalidToolCalls = message && (message.invalid_tool_calls || message.invalidToolCalls || additional.invalid_tool_calls || additional.invalidToolCalls);
953
+ if (Array.isArray(invalidToolCalls)) {
954
+ for (const tool of invalidToolCalls)
955
+ push({ ...tool, ok: false, source: 'ai_message_invalid_tool_calls' }, index);
956
+ }
957
+ if (type === 'tool') {
958
+ const id = message.tool_call_id || message.toolCallId || additional.tool_call_id || additional.toolCallId || message.id;
959
+ const name = message.name || message.toolName || message.tool || additional.name || additional.tool_name || additional.toolName || kwargs.name || 'tool';
960
+ const existing = id ? toolCallById.get(String(id)) : null;
961
+ if (existing) {
962
+ existing.result = summarizeToolResult(messageContentOf(message));
963
+ existing.result_summary = existing.result;
964
+ existing.ok = true;
965
+ existing.status = 'ok';
966
+ existing.result_hash = stableHash(existing.result || '');
967
+ existing.dedupe_key = toolEventKey(existing);
968
+ }
969
+ else {
970
+ push({
971
+ id,
972
+ name,
973
+ input: '',
974
+ result: messageContentOf(message),
975
+ ok: true,
976
+ source: 'tool_message',
977
+ }, index);
978
+ }
979
+ }
980
+ }
981
+ return dedupeToolHistory(calls);
982
+ };
913
983
  const extractToolCallsFromText = (text, at = nowIso()) => {
914
984
  const calls = [];
915
985
  const source = String(text || '');
@@ -1022,15 +1092,17 @@ const getMemoryStore = (ctx) => {
1022
1092
  data.tembory.memoryCompression = data.tembory.memoryCompression || {};
1023
1093
  data.tembory.activeSummary = data.tembory.activeSummary || {};
1024
1094
  data.tembory.connectedModelSummaryCache = data.tembory.connectedModelSummaryCache || {};
1095
+ data.tembory.captureState = data.tembory.captureState || {};
1025
1096
  return data.tembory;
1026
1097
  }
1027
1098
  catch {
1028
- global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {}, activeSummary: {}, connectedModelSummaryCache: {} };
1099
+ global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {}, activeSummary: {}, connectedModelSummaryCache: {}, captureState: {} };
1029
1100
  global.__temboryMemory.workingMemory = global.__temboryMemory.workingMemory || {};
1030
1101
  global.__temboryMemory.decisionState = global.__temboryMemory.decisionState || {};
1031
1102
  global.__temboryMemory.memoryCompression = global.__temboryMemory.memoryCompression || {};
1032
1103
  global.__temboryMemory.activeSummary = global.__temboryMemory.activeSummary || {};
1033
1104
  global.__temboryMemory.connectedModelSummaryCache = global.__temboryMemory.connectedModelSummaryCache || {};
1105
+ global.__temboryMemory.captureState = global.__temboryMemory.captureState || {};
1034
1106
  return global.__temboryMemory;
1035
1107
  }
1036
1108
  };
@@ -1824,9 +1896,20 @@ const compactEntityTimelineForAgent = (timeline = [], maxItems = 8) => pruneByLi
1824
1896
  at: item.at,
1825
1897
  source: item.source_type || item.source,
1826
1898
  }));
1899
+ const isConversationEchoMemory = (memory) => {
1900
+ const meta = metadataOf(memory);
1901
+ const kind = String(meta.kind || meta.type || '').toLowerCase();
1902
+ if (/^(recent_message|conversation_ledger|conversation_message)$/i.test(kind))
1903
+ return true;
1904
+ const raw = memoryText(memory);
1905
+ if (parseRecentMessageMarker(raw) || parseConversationLedgerMarker(raw).length)
1906
+ return true;
1907
+ return /__tembory_conversation_ledger_v1__/.test(String(raw || ''));
1908
+ };
1827
1909
  const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], maxItems = 3) => {
1828
1910
  const hasStructuredTools = Array.isArray(toolHistory) && toolHistory.length > 0;
1829
1911
  return (vectorMemories || [])
1912
+ .filter((memory) => !isConversationEchoMemory(memory))
1830
1913
  .map((memory) => contextMemoryText(memory, 220))
1831
1914
  .filter(Boolean)
1832
1915
  .filter((text) => !/^\[recent_message\]/i.test(text))
@@ -2202,7 +2285,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
2202
2285
  sections.push({
2203
2286
  section: 'vector',
2204
2287
  title: 'Vector memories',
2205
- value: compactStateSections ? compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.maxReturn || adv.topK || 4)) : vectorMemories.map((m) => {
2288
+ value: compactStateSections ? compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.maxReturn || adv.topK || 4)) : vectorMemories.filter((m) => !isConversationEchoMemory(m)).map((m) => {
2206
2289
  const scoreMeta = scoreMetaOf(m);
2207
2290
  const rawScore = scoreOf(m);
2208
2291
  return {
@@ -2634,6 +2717,18 @@ class Mem0Memory {
2634
2717
  const memoryKey = this.getNodeParameter('memoryKey', itemIndex);
2635
2718
  let currentMessages = [];
2636
2719
  const loadCache = new Map();
2720
+ const recordMemoryEvent = (action, payload = {}, error) => {
2721
+ const { index } = this.addInputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, [
2722
+ [{ json: { action, ...(payload.input !== undefined ? { input: payload.input } : {}), ...(payload.values !== undefined ? { values: payload.values } : {}) } }],
2723
+ ]);
2724
+ if (error) {
2725
+ this.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, error);
2726
+ return;
2727
+ }
2728
+ this.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, [
2729
+ [{ json: { action, ...payload } }],
2730
+ ]);
2731
+ };
2637
2732
  const memory = {
2638
2733
  memoryKeys: [memoryKey],
2639
2734
  inputKey: 'input',
@@ -2643,21 +2738,25 @@ class Mem0Memory {
2643
2738
  getMessages: async () => currentMessages,
2644
2739
  addMessage: async (message) => {
2645
2740
  currentMessages.push(message);
2646
- await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [message]);
2741
+ const saved = await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [message]);
2742
+ recordMemoryEvent('chatHistory.addMessage', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: 1 });
2647
2743
  },
2648
2744
  addUserMessage: async (message) => {
2649
2745
  const baseMessage = toBaseMessage({ role: 'user', content: message });
2650
2746
  currentMessages.push(baseMessage);
2651
- await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
2747
+ const saved = await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
2748
+ recordMemoryEvent('chatHistory.addUserMessage', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: 1 });
2652
2749
  },
2653
2750
  addAIChatMessage: async (message) => {
2654
2751
  const baseMessage = toBaseMessage({ role: 'assistant', content: message });
2655
2752
  currentMessages.push(baseMessage);
2656
- await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
2753
+ const saved = await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
2754
+ recordMemoryEvent('chatHistory.addAIChatMessage', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: 1 });
2657
2755
  },
2658
2756
  addMessages: async (messages) => {
2659
2757
  currentMessages.push(...messages);
2660
- await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, messages);
2758
+ const saved = await Mem0Memory.prototype.saveMessagesForItem.call(this, itemIndex, messages);
2759
+ recordMemoryEvent('chatHistory.addMessages', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: Array.isArray(messages) ? messages.length : 0 });
2661
2760
  },
2662
2761
  clear: async () => {
2663
2762
  currentMessages = [];
@@ -2722,25 +2821,16 @@ class Mem0Memory {
2722
2821
  async saveMessagesForItem(itemIndex, messages = []) {
2723
2822
  const inputValues = {};
2724
2823
  const outputValues = {};
2725
- const toolCalls = [];
2824
+ const toolCalls = extractToolCallsFromMessages(messages);
2726
2825
  const inputParts = [];
2727
2826
  const outputParts = [];
2728
2827
  for (const message of messages || []) {
2729
- const type = typeof (message === null || message === void 0 ? void 0 : message._getType) === 'function' ? message._getType() : String((message === null || message === void 0 ? void 0 : message.role) || '');
2730
- const content = typeof (message === null || message === void 0 ? void 0 : message.content) === 'string' ? message.content : safeStringify(message === null || message === void 0 ? void 0 : message.content);
2828
+ const type = messageTypeOf(message);
2829
+ const content = messageContentOf(message);
2731
2830
  if (type === 'human' || type === 'user')
2732
2831
  inputParts.push(content);
2733
2832
  else if (type === 'ai' || type === 'assistant')
2734
2833
  outputParts.push(content);
2735
- else if (type === 'tool') {
2736
- toolCalls.push({
2737
- name: message.name || message.toolName || message.tool || (message.additional_kwargs && (message.additional_kwargs.name || message.additional_kwargs.tool_name)) || 'tool',
2738
- input: '',
2739
- ok: true,
2740
- result: summarizeToolResult(content),
2741
- at: nowIso(),
2742
- });
2743
- }
2744
2834
  }
2745
2835
  if (inputParts.length)
2746
2836
  inputValues.input = inputParts.join('\n');
@@ -2751,8 +2841,11 @@ class Mem0Memory {
2751
2841
  outputValues.output = outputParts.join('\n').trim();
2752
2842
  if (toolContext.length)
2753
2843
  outputValues.__temboryToolCalls = toolContext;
2754
- if (inputValues.input || outputValues.output)
2844
+ if (inputValues.input || outputValues.output || toolContext.length) {
2755
2845
  await Mem0Memory.prototype.saveContextForItem.call(this, itemIndex, inputValues, outputValues);
2846
+ return { saved: true, input: inputValues, output: outputValues, toolCalls: toolContext };
2847
+ }
2848
+ return { saved: false, input: inputValues, output: outputValues, toolCalls: toolContext };
2756
2849
  }
2757
2850
  async saveContextForItem(itemIndex, inputValues = {}, outputValues = {}) {
2758
2851
  const threadId = this.getNodeParameter('threadId', itemIndex);
@@ -2764,6 +2857,14 @@ class Mem0Memory {
2764
2857
  const rawOutput = pickText(outputValues, ['output', 'response', 'text', 'answer']);
2765
2858
  const output = cleanAssistantTranscriptText(rawOutput);
2766
2859
  const toolCalls = extractToolCalls(outputValues);
2860
+ store.captureState[key] = {
2861
+ last_save_at: nowIso(),
2862
+ last_save_had_input: Boolean(input),
2863
+ last_save_had_output: Boolean(output),
2864
+ last_save_tool_calls_captured: toolCalls.length,
2865
+ last_save_tool_names: toolCalls.map((tool) => tool.name).filter(Boolean).slice(0, 20),
2866
+ last_save_capture_sources: Array.from(new Set(toolCalls.map((tool) => tool.source).filter(Boolean))),
2867
+ };
2767
2868
  const recentForMem0 = [];
2768
2869
  const profileFromTurn = extractProfileFactsFromText(input, 'user_message', nowIso());
2769
2870
  if (Object.keys(profileFromTurn).length) {
@@ -3374,12 +3475,47 @@ class Mem0Memory {
3374
3475
  connectedAi.errors.push(`recentMessageProbe: ${error.message || String(error)}`);
3375
3476
  }
3376
3477
  }
3478
+ if (connectedEmbedding && adv.includeToolHistory !== false) {
3479
+ try {
3480
+ const toolProbe = [
3481
+ 'Tool history ledger',
3482
+ 'tools called in this thread tool inputs tool outputs tool status tool order action ledger',
3483
+ query,
3484
+ ].filter(Boolean).join('\n');
3485
+ const seen = new Set(vectorMemories.map((memory) => stableHash(memoryText(memory) || safeStringify(memory))));
3486
+ for (const kind of ['tool_ledger', 'tool_history']) {
3487
+ const toolBody = {
3488
+ user_id: key,
3489
+ agent_id: adv.agentId ? String(adv.agentId) : undefined,
3490
+ run_id: adv.runId ? String(adv.runId) : undefined,
3491
+ top_k: Math.max(Number(adv.toolHistoryLastN || 10) * 4, 40),
3492
+ filters: { kind },
3493
+ };
3494
+ const toolRes = await searchClientVectorMemories(this, connectedEmbedding, toolProbe, toolBody);
3495
+ const toolMemories = normalizeResults(toolRes).map((memory) => withTemboryScore(memory, {
3496
+ semanticScore: scoreOf(memory),
3497
+ source: `${kind}_probe`,
3498
+ }));
3499
+ for (const memory of toolMemories) {
3500
+ const hash = stableHash(memoryText(memory) || safeStringify(memory));
3501
+ if (!seen.has(hash)) {
3502
+ seen.add(hash);
3503
+ vectorMemories.push(memory);
3504
+ }
3505
+ }
3506
+ }
3507
+ connectedAi.toolLedgerProbe = true;
3508
+ }
3509
+ catch (error) {
3510
+ connectedAi.errors.push(`toolLedgerProbe: ${error.message || String(error)}`);
3511
+ }
3512
+ }
3377
3513
  let persistedRecentMessages = [];
3378
3514
  let persistedToolHistory = [];
3379
3515
  let persistedMemoryItems = [];
3380
3516
  if (adv.includeRecentMessages !== false || adv.includeToolHistory !== false) {
3381
3517
  try {
3382
- const qs = { user_id: key };
3518
+ const qs = { user_id: key, limit: Math.max(Number(adv.recentMessagesLastN || 50) * 8, Number(adv.toolHistoryLastN || 10) * 12, 200) };
3383
3519
  if (adv.agentId)
3384
3520
  qs.agent_id = String(adv.agentId);
3385
3521
  if (adv.runId)
@@ -3524,6 +3660,14 @@ class Mem0Memory {
3524
3660
  graph: graph.length,
3525
3661
  project: project || null,
3526
3662
  memoryNamespace: key,
3663
+ captureState: store.captureState[key] || {
3664
+ last_save_at: null,
3665
+ last_save_had_input: false,
3666
+ last_save_had_output: false,
3667
+ last_save_tool_calls_captured: 0,
3668
+ last_save_tool_names: [],
3669
+ last_save_capture_sources: [],
3670
+ },
3527
3671
  connectedAi,
3528
3672
  activeSummary: summaryDiagnostics,
3529
3673
  };
@@ -3768,6 +3912,7 @@ exports.Mem0Memory = Mem0Memory;
3768
3912
  exports.__private = {
3769
3913
  extractToolCallsFromText,
3770
3914
  extractToolCalls,
3915
+ extractToolCallsFromMessages,
3771
3916
  toolHistoryItemsFromMemory,
3772
3917
  explicitToolHistoryItemsFromMemory,
3773
3918
  toolHistoryFromMemory,
@@ -3820,6 +3965,8 @@ exports.__private = {
3820
3965
  embedQueryCached,
3821
3966
  compactToolResult,
3822
3967
  compactToolHistoryForAgent,
3968
+ compactVectorMemoriesForAgent,
3969
+ isConversationEchoMemory,
3823
3970
  compactOperationalStateForAgent,
3824
3971
  activeSummaryIsFresh,
3825
3972
  readActiveSummary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
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",