n8n-nodes-tembory 1.1.39 → 1.1.41

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,19 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.1.39`.
5
+ Versao atual: `1.1.41`.
6
+
7
+ ## 1.1.41
8
+
9
+ - Padroniza a saida visual do node com `visualSchema`, `memorySummary` e `toolLog` tanto no `loadMemoryVariables` quanto no `saveContext`.
10
+ - Mantem `toolEvents` para compatibilidade, mas agora tambem mostra `inputParsed`, `outputParsed`, timestamps e `facts` com IDs operacionais quando existirem.
11
+ - Aumenta a visibilidade do historico recente de tools no resumo visual para evitar esconder eventos importantes no debug humano.
12
+
13
+ ## 1.1.40
14
+
15
+ - Extrai fatos operacionais path aware de inputs e outputs de tools, preservando IDs como `serviceId`, `providerId`, `locationId`, `customerId`, `reservationId` e `bookingId`.
16
+ - Injeta esses fatos em `operationalState`, `actionLedger`, `toolLedger` e `action_directive`.
17
+ - Corrige o risco de o Agent usar `serviceId` antigo de memoria vetorial quando o `check_availabilities` mais recente ja retornou o ID correto para o `Booking`.
6
18
 
7
19
  ## 1.1.39
8
20
 
@@ -489,6 +489,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
489
489
  const name = String(tool.name || 'unknown_tool');
490
490
  const compactInput = compactToolPayload(safeParseToolPayload(tool.input));
491
491
  const compactResult = maybeToolResult(tool, includeResults);
492
+ const facts = extractToolOperationalFacts(tool);
492
493
  const reason = truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260);
493
494
  toolCountsByName[name] = (toolCountsByName[name] || 0) + 1;
494
495
  latestByName[name] = {
@@ -496,6 +497,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
496
497
  ok: tool.ok !== false,
497
498
  at: tool.at || null,
498
499
  input: compactInput,
500
+ facts,
499
501
  result: compactResult,
500
502
  reason,
501
503
  source: tool.source || 'unknown',
@@ -507,6 +509,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
507
509
  at: tool.at || null,
508
510
  reason,
509
511
  input: compactInput,
512
+ facts,
510
513
  output: compactResult,
511
514
  }));
512
515
  if (tool.ok === false)
@@ -538,6 +541,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
538
541
  last_successful_tool: successfulTools.length ? {
539
542
  name: successfulTools[successfulTools.length - 1].name,
540
543
  at: successfulTools[successfulTools.length - 1].at || null,
544
+ facts: extractToolOperationalFacts(successfulTools[successfulTools.length - 1]),
541
545
  result: maybeToolResult(successfulTools[successfulTools.length - 1], includeResults),
542
546
  } : null,
543
547
  },
@@ -556,6 +560,7 @@ const deriveActionLedger = (toolHistory = [], maxItems = 20, includeResults = tr
556
560
  status: tool.ok === false ? 'failed' : 'ok',
557
561
  reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260),
558
562
  input: compactToolPayload(safeParseToolPayload(tool.input)),
563
+ facts: extractToolOperationalFacts(tool),
559
564
  result: maybeToolResult(tool, includeResults),
560
565
  at: tool.at || null,
561
566
  source: tool.source || 'unknown',
@@ -2094,16 +2099,25 @@ const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) =>
2094
2099
  return null;
2095
2100
  const toolState = (operationalState || {}).tool_state || {};
2096
2101
  const latestForTool = ((toolState.latest_by_name || {})[tool]) || null;
2102
+ const recentToolEvidence = pruneByLimit((toolState.recent_sequence || [])
2103
+ .filter((item) => item && item.status !== 'failed')
2104
+ .map((item) => cleanContextValue({
2105
+ tool: item.tool,
2106
+ at: item.at,
2107
+ facts: item.facts,
2108
+ }))
2109
+ .filter((item) => item && item.facts), 6);
2097
2110
  return cleanContextValue({
2098
2111
  required_tool: tool,
2099
2112
  next_expected_action: next,
2113
+ evidence_from_recent_tools: recentToolEvidence,
2100
2114
  tool_state: {
2101
2115
  last_tool: (operationalState || {}).last_tool || null,
2102
2116
  required_tool_last_result: latestForTool,
2103
2117
  counts_by_name: toolState.counts_by_name || {},
2104
2118
  failed_by_name: toolState.failed_by_name || {},
2105
2119
  },
2106
- instruction: `If the agent prompt requires this action, call ${tool} now using tool_state/action_ledger as evidence. Do not infer domain policy from memory; memory only provides prior conversation, decisions, inputs and outputs.`,
2120
+ instruction: `call ${tool} now if the agent prompt requires it. Use tool evidence for exact IDs. Do not complete side effects from memory.`,
2107
2121
  });
2108
2122
  };
2109
2123
  const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
@@ -2520,6 +2534,152 @@ const normalizeToolResultEnvelope = (value) => {
2520
2534
  }
2521
2535
  return parsed;
2522
2536
  };
2537
+ const normalizeFactKey = (key = '') => String(key || '').toLowerCase().replace(/[^a-z0-9]/g, '');
2538
+ const scalarFactValue = (value) => {
2539
+ if (value === undefined || value === null || value === '')
2540
+ return undefined;
2541
+ if (['string', 'number', 'boolean'].includes(typeof value))
2542
+ return value;
2543
+ return undefined;
2544
+ };
2545
+ const pushCandidate = (facts, bucket, value, path) => {
2546
+ const scalar = scalarFactValue(value);
2547
+ if (scalar === undefined)
2548
+ return;
2549
+ facts[bucket] = facts[bucket] || [];
2550
+ if (!facts[bucket].some((item) => String(item.value) === String(scalar) && item.path === path)) {
2551
+ facts[bucket].push({ value: scalar, path });
2552
+ }
2553
+ };
2554
+ const firstCandidateValue = (items = []) => items.length ? items[0].value : undefined;
2555
+ const extractOperationalFactsFromValue = (value, maxCandidates = 8) => {
2556
+ const parsed = normalizeToolResultEnvelope(value);
2557
+ const facts = {
2558
+ serviceIdCandidates: [],
2559
+ providerIdCandidates: [],
2560
+ locationIdCandidates: [],
2561
+ customerIdCandidates: [],
2562
+ reservationIdCandidates: [],
2563
+ bookingIdCandidates: [],
2564
+ confirmationIdCandidates: [],
2565
+ serviceNameCandidates: [],
2566
+ providerNameCandidates: [],
2567
+ statusCandidates: [],
2568
+ timeCandidates: [],
2569
+ dateCandidates: [],
2570
+ };
2571
+ const seen = new Set();
2572
+ let visited = 0;
2573
+ const visit = (item, path = []) => {
2574
+ if (visited > 220 || item === undefined || item === null)
2575
+ return;
2576
+ if (typeof item !== 'object')
2577
+ return;
2578
+ if (seen.has(item))
2579
+ return;
2580
+ seen.add(item);
2581
+ visited += 1;
2582
+ if (Array.isArray(item)) {
2583
+ item.slice(0, 12).forEach((entry, index) => visit(entry, path.concat(String(index))));
2584
+ return;
2585
+ }
2586
+ for (const [rawKey, rawValue] of Object.entries(item)) {
2587
+ const key = normalizeFactKey(rawKey);
2588
+ const nextPath = path.concat(rawKey);
2589
+ const pathText = nextPath.join('.');
2590
+ const parentText = path.join('.').toLowerCase();
2591
+ const parentSuggestsService = /(^|\.)(service|services|servico|servicos|especialidade|especialidades)(\.|$)/i.test(parentText);
2592
+ const parentSuggestsProvider = /(^|\.)(provider|providers|veterinario|veterinarios|vet|vets|professional|profissional|profissionais)(\.|$)/i.test(parentText);
2593
+ const parentSuggestsLocation = /(^|\.)(location|locations|local|locais|unidade|unidades)(\.|$)/i.test(parentText);
2594
+ if (key === 'serviceid' || key === 'servicoid' || (key === 'id' && parentSuggestsService))
2595
+ pushCandidate(facts, 'serviceIdCandidates', rawValue, pathText);
2596
+ if (key === 'providerid' || key === 'veterinarioid' || key === 'vetid' || (key === 'id' && parentSuggestsProvider))
2597
+ pushCandidate(facts, 'providerIdCandidates', rawValue, pathText);
2598
+ if (key === 'locationid' || key === 'localid' || key === 'unidadeid' || (key === 'id' && parentSuggestsLocation))
2599
+ pushCandidate(facts, 'locationIdCandidates', rawValue, pathText);
2600
+ if (key === 'customerid' || key === 'clienteid')
2601
+ pushCandidate(facts, 'customerIdCandidates', rawValue, pathText);
2602
+ if (key === 'reservationid' || key === 'reservaid')
2603
+ pushCandidate(facts, 'reservationIdCandidates', rawValue, pathText);
2604
+ if (key === 'bookingid' || key === 'appointmentid' || key === 'agendamentoid')
2605
+ pushCandidate(facts, 'bookingIdCandidates', rawValue, pathText);
2606
+ if (key === 'confirmationid')
2607
+ pushCandidate(facts, 'confirmationIdCandidates', rawValue, pathText);
2608
+ if (key === 'servicename' || key === 'servico' || key === 'especialidade' || (key === 'name' && parentSuggestsService))
2609
+ pushCandidate(facts, 'serviceNameCandidates', rawValue, pathText);
2610
+ if (key === 'providername' || key === 'veterinarioname' || key === 'nomeveterinario' || (key === 'name' && parentSuggestsProvider))
2611
+ pushCandidate(facts, 'providerNameCandidates', rawValue, pathText);
2612
+ if (key === 'status' || key === 'state')
2613
+ pushCandidate(facts, 'statusCandidates', rawValue, pathText);
2614
+ if (key === 'horario' || key === 'horarios' || key === 'time' || key === 'start' || key === 'end')
2615
+ pushCandidate(facts, 'timeCandidates', rawValue, pathText);
2616
+ if (key === 'date' || key === 'data' || key === 'datadisponivel')
2617
+ pushCandidate(facts, 'dateCandidates', rawValue, pathText);
2618
+ if (rawValue && typeof rawValue === 'object')
2619
+ visit(rawValue, nextPath);
2620
+ }
2621
+ };
2622
+ visit(parsed);
2623
+ const compactCandidates = (items = []) => items.slice(0, maxCandidates);
2624
+ return cleanContextValue({
2625
+ ids: {
2626
+ serviceId: firstCandidateValue(facts.serviceIdCandidates),
2627
+ providerId: firstCandidateValue(facts.providerIdCandidates),
2628
+ locationId: firstCandidateValue(facts.locationIdCandidates),
2629
+ customerId: firstCandidateValue(facts.customerIdCandidates),
2630
+ reservationId: firstCandidateValue(facts.reservationIdCandidates),
2631
+ bookingId: firstCandidateValue(facts.bookingIdCandidates),
2632
+ confirmationId: firstCandidateValue(facts.confirmationIdCandidates),
2633
+ },
2634
+ labels: {
2635
+ serviceName: firstCandidateValue(facts.serviceNameCandidates),
2636
+ providerName: firstCandidateValue(facts.providerNameCandidates),
2637
+ status: firstCandidateValue(facts.statusCandidates),
2638
+ date: firstCandidateValue(facts.dateCandidates),
2639
+ time: firstCandidateValue(facts.timeCandidates),
2640
+ },
2641
+ candidates: {
2642
+ serviceIds: compactCandidates(facts.serviceIdCandidates),
2643
+ providerIds: compactCandidates(facts.providerIdCandidates),
2644
+ locationIds: compactCandidates(facts.locationIdCandidates),
2645
+ },
2646
+ });
2647
+ };
2648
+ const mergeOperationalFactObjects = (primary = {}, secondary = {}) => {
2649
+ const pick = (path) => {
2650
+ const read = (source) => path.reduce((acc, key) => acc && acc[key] !== undefined ? acc[key] : undefined, source);
2651
+ return read(primary) !== undefined ? read(primary) : read(secondary);
2652
+ };
2653
+ return cleanContextValue({
2654
+ ids: {
2655
+ serviceId: pick(['ids', 'serviceId']),
2656
+ providerId: pick(['ids', 'providerId']),
2657
+ locationId: pick(['ids', 'locationId']),
2658
+ customerId: pick(['ids', 'customerId']),
2659
+ reservationId: pick(['ids', 'reservationId']),
2660
+ bookingId: pick(['ids', 'bookingId']),
2661
+ confirmationId: pick(['ids', 'confirmationId']),
2662
+ },
2663
+ labels: {
2664
+ serviceName: pick(['labels', 'serviceName']),
2665
+ providerName: pick(['labels', 'providerName']),
2666
+ status: pick(['labels', 'status']),
2667
+ date: pick(['labels', 'date']),
2668
+ time: pick(['labels', 'time']),
2669
+ },
2670
+ candidates: {
2671
+ serviceIds: ((primary.candidates || {}).serviceIds || (secondary.candidates || {}).serviceIds || []).slice(0, 8),
2672
+ providerIds: ((primary.candidates || {}).providerIds || (secondary.candidates || {}).providerIds || []).slice(0, 8),
2673
+ locationIds: ((primary.candidates || {}).locationIds || (secondary.candidates || {}).locationIds || []).slice(0, 8),
2674
+ },
2675
+ });
2676
+ };
2677
+ const extractToolOperationalFacts = (tool = {}) => {
2678
+ const inputFacts = extractOperationalFactsFromValue(safeParseToolPayload(tool.input));
2679
+ const outputFacts = extractOperationalFactsFromValue(tool.result);
2680
+ const facts = mergeOperationalFactObjects(outputFacts, inputFacts);
2681
+ return Object.keys(facts || {}).length ? facts : undefined;
2682
+ };
2523
2683
  const compactParsedToolOutputForSideChannel = (parsed) => {
2524
2684
  if (parsed === undefined || parsed === null)
2525
2685
  return undefined;
@@ -2559,6 +2719,7 @@ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResul
2559
2719
  at: tool.at,
2560
2720
  reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 140),
2561
2721
  input: truncate(String(tool.input || ''), 500) || undefined,
2722
+ facts: extractToolOperationalFacts(tool),
2562
2723
  result: includeResults ? compactToolResult(tool.result, 1200) : undefined,
2563
2724
  }));
2564
2725
  const cleanContextValue = (value) => {
@@ -2641,7 +2802,7 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
2641
2802
  const compactOperationalStateForAgent = (state = {}) => {
2642
2803
  const counts = state.tool_counts || {};
2643
2804
  const toolState = state.tool_state || {};
2644
- return {
2805
+ return cleanContextValue({
2645
2806
  last_tool: state.last_tool || null,
2646
2807
  tool_counts: {
2647
2808
  total: counts.total || 0,
@@ -2655,8 +2816,7 @@ const compactOperationalStateForAgent = (state = {}) => {
2655
2816
  last_successful_tool: toolState.last_successful_tool || undefined,
2656
2817
  } : undefined,
2657
2818
  blocked_without_context: state.blocked_without_context || [],
2658
- guidance: (state.guidance || []).slice(0, 3),
2659
- };
2819
+ });
2660
2820
  };
2661
2821
  const compactMemoryCompressionForAgent = (compression = {}) => ({
2662
2822
  turn_summary: (compression.turn_summary || []).slice(-3),
@@ -2846,6 +3006,16 @@ const invokeConnectedModelSummary = async (connectedLanguageModel, summaryInput,
2846
3006
  ]);
2847
3007
  return cleanModelSummaryText(response, Number(adv.connectedModelSummaryMaxChars || 1200));
2848
3008
  };
3009
+ const VISUAL_SCHEMA_VERSION = 'tembory.visual.v1';
3010
+ const TOOL_LOG_VISUAL_SCHEMA_VERSION = 'tembory.visual.v1.toolLog';
3011
+ const SIDE_CHANNEL_TOOL_EVENT_MAX = 12;
3012
+ const SIDE_CHANNEL_SAVE_TOOL_EVENT_MAX = 20;
3013
+ const compactParsedToolInputForSideChannel = (input) => {
3014
+ const parsed = safeParseToolPayload(input);
3015
+ if (!parsed || typeof parsed !== 'object')
3016
+ return undefined;
3017
+ return cleanContextValue(stripNoisyToolFields(parsed));
3018
+ };
2849
3019
  const compactToolAuditForSideChannel = (tool = {}) => {
2850
3020
  const capturedAt = tool.at || tool.timestamp;
2851
3021
  const rawResult = tool.result !== undefined ? tool.result : tool.output !== undefined ? tool.output : tool.observation;
@@ -2859,10 +3029,36 @@ const compactToolAuditForSideChannel = (tool = {}) => {
2859
3029
  toolTimestamp: toolResultTimestampFromParsed(parsedResult),
2860
3030
  toolStatus: parsedResult && typeof parsedResult === 'object' && !Array.isArray(parsedResult) ? parsedResult.status : undefined,
2861
3031
  input: truncate(String(tool.input || tool.tool_args || tool.normalized_args || ''), 300) || undefined,
3032
+ inputParsed: compactParsedToolInputForSideChannel(tool.input || tool.tool_args || tool.normalized_args),
2862
3033
  output: compactToolResult(rawResult, 800),
2863
3034
  outputParsed: compactParsedToolOutputForSideChannel(parsedResult),
3035
+ facts: extractToolOperationalFacts(tool),
2864
3036
  });
2865
3037
  };
3038
+ const compactToolEventsForSideChannel = (toolItems = [], maxItems = SIDE_CHANNEL_TOOL_EVENT_MAX) => pruneByLimit(toolItems || [], maxItems)
3039
+ .map((tool) => compactToolAuditForSideChannel(tool || {}))
3040
+ .filter((tool) => tool && Object.keys(tool).length);
3041
+ const buildToolLogForSideChannel = (toolEvents = [], totalCount, maxItems = SIDE_CHANNEL_TOOL_EVENT_MAX) => {
3042
+ const events = Array.isArray(toolEvents) ? toolEvents : [];
3043
+ const count = typeof totalCount === 'number' ? totalCount : events.length;
3044
+ return {
3045
+ schema: TOOL_LOG_VISUAL_SCHEMA_VERSION,
3046
+ count,
3047
+ names: events.map((tool) => tool.name).filter(Boolean).slice(0, maxItems),
3048
+ events,
3049
+ truncated: count > events.length,
3050
+ maxEvents: maxItems,
3051
+ };
3052
+ };
3053
+ const keepVisibleToolLogArrays = (payload = {}) => {
3054
+ if (payload.toolLog) {
3055
+ if (!Array.isArray(payload.toolLog.names))
3056
+ payload.toolLog.names = [];
3057
+ if (!Array.isArray(payload.toolLog.events))
3058
+ payload.toolLog.events = [];
3059
+ }
3060
+ return payload;
3061
+ };
2866
3062
  const compactMessageForSideChannel = (message = {}) => {
2867
3063
  const type = messageTypeOf(message) || String(message.role || 'message').toLowerCase();
2868
3064
  const role = type === 'human' ? 'user' : type === 'ai' ? 'assistant' : type || 'message';
@@ -2913,21 +3109,21 @@ const compactConversationTimelineForSideChannel = (conversation = {}, maxItems =
2913
3109
  const summarizeSaveContextForSideChannel = (input = {}, output = {}, chatHistory = []) => {
2914
3110
  const inputMessage = input?.input || input?.chatInput || input?.query || input?.question || input?.text || input?.message || '';
2915
3111
  const outputMessage = output?.output || output?.response || output?.text || output?.message || output?.answer || '';
2916
- const toolEvents = extractToolCalls(output || {})
2917
- .map((tool) => compactToolAuditForSideChannel(tool || {}))
2918
- .filter((tool) => tool && Object.keys(tool).length)
2919
- .slice(0, 20);
2920
- return cleanContextValue({
3112
+ const toolCalls = extractToolCalls(output || {});
3113
+ const toolEvents = compactToolEventsForSideChannel(toolCalls, SIDE_CHANNEL_SAVE_TOOL_EVENT_MAX);
3114
+ return keepVisibleToolLogArrays(cleanContextValue({
3115
+ visualSchema: VISUAL_SCHEMA_VERSION,
2921
3116
  messagesAfterSave: Array.isArray(chatHistory) ? chatHistory.length : 0,
2922
3117
  savedMessages: Array.isArray(chatHistory)
2923
3118
  ? chatHistory.slice(-6).map((message) => compactMessageForSideChannel(message))
2924
3119
  : undefined,
2925
3120
  userInput: inputMessage ? truncate(String(inputMessage), 500) : undefined,
2926
3121
  assistantOutput: outputMessage ? truncate(String(outputMessage), 700) : undefined,
2927
- toolCallsCaptured: toolEvents.length || undefined,
3122
+ toolCallsCaptured: toolCalls.length,
2928
3123
  toolNames: toolEvents.map((tool) => tool.name).filter(Boolean),
3124
+ toolLog: buildToolLogForSideChannel(toolEvents, toolCalls.length, SIDE_CHANNEL_SAVE_TOOL_EVENT_MAX),
2929
3125
  toolEvents,
2930
- });
3126
+ }));
2931
3127
  };
2932
3128
  const compactMemoryEventPayload = (payload = {}) => {
2933
3129
  const compact = { ...(payload || {}) };
@@ -2937,18 +3133,20 @@ const compactMemoryEventPayload = (payload = {}) => {
2937
3133
  .map((tool) => (tool && (tool.name || tool.tool_name || tool.tool)) || '')
2938
3134
  .filter(Boolean)
2939
3135
  .slice(0, 12);
2940
- compact.toolEvents = compact.toolCalls
2941
- .map((tool) => compactToolAuditForSideChannel(tool || {}))
2942
- .filter((tool) => tool && Object.keys(tool).length)
2943
- .slice(0, 8);
3136
+ compact.toolEvents = compactToolEventsForSideChannel(compact.toolCalls, SIDE_CHANNEL_TOOL_EVENT_MAX);
3137
+ compact.toolLog = buildToolLogForSideChannel(compact.toolEvents, compact.toolCalls.length, SIDE_CHANNEL_TOOL_EVENT_MAX);
2944
3138
  delete compact.toolCalls;
2945
3139
  }
3140
+ if (!compact.visualSchema)
3141
+ compact.visualSchema = VISUAL_SCHEMA_VERSION;
3142
+ if (Array.isArray(compact.toolEvents) && !compact.toolLog)
3143
+ compact.toolLog = buildToolLogForSideChannel(compact.toolEvents, compact.toolCallsCaptured, SIDE_CHANNEL_TOOL_EVENT_MAX);
2946
3144
  for (const key of ['input', 'output', 'values', 'chatHistory', 'context', 'contextText', 'diagnostics']) {
2947
3145
  if (compact[key] !== undefined)
2948
3146
  compact[`${key}Chars`] = typeof compact[key] === 'string' ? compact[key].length : safeStringify(compact[key]).length;
2949
3147
  delete compact[key];
2950
3148
  }
2951
- return compact;
3149
+ return keepVisibleToolLogArrays(compact);
2952
3150
  };
2953
3151
  const compactLastSaveForSideChannel = (lastSave = {}) => cleanContextValue({
2954
3152
  saved: lastSave.saved,
@@ -3099,6 +3297,7 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
3099
3297
  const fullDedupeSummary = parsed.dedupeSummary || parsed.diagnostics?.dedupeSummary || undefined;
3100
3298
  const loadedSections = loadedSectionsForSideChannel(parsed, memoryAudit);
3101
3299
  const agentContextBudget = agentContextBudgetForSideChannel(list, parsed);
3300
+ summary.visualSchema = VISUAL_SCHEMA_VERSION;
3102
3301
  summary.userId = parsed.userId;
3103
3302
  summary.project = parsed.project || undefined;
3104
3303
  summary.retrievalMode = parsed.retrievalMode;
@@ -3122,12 +3321,11 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
3122
3321
  } : undefined,
3123
3322
  });
3124
3323
  summary.conversationTimeline = compactConversationTimelineForSideChannel(conversation, 4);
3324
+ const visibleToolEvents = compactToolEventsForSideChannel(toolItems, SIDE_CHANNEL_TOOL_EVENT_MAX);
3125
3325
  summary.toolCount = toolItems.length || tools.count || parsed.operationalState?.tool_counts?.total || undefined;
3126
3326
  summary.toolNames = toolItems.map((tool) => tool.name || tool.tool_name).filter(Boolean).slice(0, 12);
3127
- summary.toolEvents = toolItems
3128
- .slice(-3)
3129
- .map((tool) => compactToolAuditForSideChannel(tool || {}))
3130
- .filter((tool) => tool && Object.keys(tool).length);
3327
+ summary.toolLog = buildToolLogForSideChannel(visibleToolEvents, toolItems.length || summary.toolCount || visibleToolEvents.length, SIDE_CHANNEL_TOOL_EVENT_MAX);
3328
+ summary.toolEvents = visibleToolEvents;
3131
3329
  summary.lastTool = tools.last_successful_tool
3132
3330
  ? {
3133
3331
  name: tools.last_successful_tool.name,
@@ -3217,7 +3415,7 @@ const wrapTemboryMemory = (memory, ctx, memoryKey, itemIndex = 0) => new Proxy(m
3217
3415
  if (prop === 'loadMemoryVariables') {
3218
3416
  return async (values = {}) => {
3219
3417
  const { index } = ctx.addInputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, [
3220
- [{ json: { action: 'loadMemoryVariables', valuesChars: JSON.stringify(values || {}).length } }],
3418
+ [{ json: { action: 'loadMemoryVariables', visualSchema: VISUAL_SCHEMA_VERSION, valuesChars: JSON.stringify(values || {}).length } }],
3221
3419
  ]);
3222
3420
  try {
3223
3421
  const response = await target.loadMemoryVariables(values);
@@ -3227,6 +3425,7 @@ const wrapTemboryMemory = (memory, ctx, memoryKey, itemIndex = 0) => new Proxy(m
3227
3425
  [{
3228
3426
  json: {
3229
3427
  action: 'loadMemoryVariables',
3428
+ visualSchema: VISUAL_SCHEMA_VERSION,
3230
3429
  messages,
3231
3430
  memorySummary: summarizeMemoryMessagesForSideChannel(memoryMessages),
3232
3431
  },
@@ -3428,7 +3627,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
3428
3627
  statusAnswerMaterial: sectionValue('status_answer_material'),
3429
3628
  action_directive: directive ? cleanContextValue({
3430
3629
  required_tool: directive.required_tool,
3431
- next_expected_action: directive.next_expected_action,
3630
+ evidence_from_recent_tools: directive.evidence_from_recent_tools,
3432
3631
  instruction: directive.instruction,
3433
3632
  }) : undefined,
3434
3633
  turnBrief: compactTurnBriefForAgent(sectionValue('turn_brief')),
@@ -3879,14 +4078,15 @@ class TemboryMemory {
3879
4078
  const loadCache = new Map();
3880
4079
  const recordMemoryEvent = (action, payload = {}, error) => {
3881
4080
  const { index } = this.addInputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, [
3882
- [{ json: { action } }],
4081
+ [{ json: { action, visualSchema: VISUAL_SCHEMA_VERSION } }],
3883
4082
  ]);
3884
4083
  if (error) {
3885
4084
  this.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, error);
3886
4085
  return;
3887
4086
  }
4087
+ const compactPayload = compactMemoryEventPayload(payload);
3888
4088
  this.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, [
3889
- [{ json: { action, ...compactMemoryEventPayload(payload) } }],
4089
+ [{ json: { action, visualSchema: VISUAL_SCHEMA_VERSION, memorySummary: compactPayload, ...compactPayload } }],
3890
4090
  ]);
3891
4091
  };
3892
4092
  const memory = {
@@ -5222,6 +5422,7 @@ class TemboryMemory {
5222
5422
  }
5223
5423
  const json = {
5224
5424
  action: 'loadMemoryVariables',
5425
+ visualSchema: VISUAL_SCHEMA_VERSION,
5225
5426
  messages: Array.isArray(payload) ? payload.length : 0,
5226
5427
  memorySummary: summarizeMemoryMessagesForSideChannel(payload),
5227
5428
  };
@@ -5298,6 +5499,8 @@ exports.__private = {
5298
5499
  contextSizeOfMessages,
5299
5500
  embedQueryCached,
5300
5501
  compactToolResult,
5502
+ compactToolAuditForSideChannel,
5503
+ buildToolLogForSideChannel,
5301
5504
  compactToolHistoryForAgent,
5302
5505
  compactVectorMemoriesForAgent,
5303
5506
  isConversationEchoMemory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.1.39",
3
+ "version": "1.1.41",
4
4
  "description": "Tembory node for n8n AI Agents with operational memory, tool history and decision state",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tembory.com",