n8n-nodes-tembory 1.1.38 → 1.1.40

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.38`.
5
+ Versao atual: `1.1.40`.
6
+
7
+ ## 1.1.40
8
+
9
+ - Extrai fatos operacionais path aware de inputs e outputs de tools, preservando IDs como `serviceId`, `providerId`, `locationId`, `customerId`, `reservationId` e `bookingId`.
10
+ - Injeta esses fatos em `operationalState`, `actionLedger`, `toolLedger` e `action_directive`.
11
+ - 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`.
12
+
13
+ ## 1.1.39
14
+
15
+ - Ajusta a semantica de repeticao de tools: `do_not_repeat_tools` fica restrito a turnos de recall, status operacional ou pedido explicito para nao chamar tools.
16
+ - Em turnos normais, o Agent recebe `repeat_tool_policy` e `avoid_repeating_tools_unless_needed`, deixando claro que tools anteriores sao evidencia reutilizavel, nao ferramentas proibidas.
17
+ - Compacta `decisionState.tool_decision_state` para evitar duplicar outputs longos de tools no contexto balanceado.
6
18
 
7
19
  ## 1.1.38
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',
@@ -2046,6 +2051,8 @@ const buildTurnBriefForAgent = ({ query = '', recentMessages = [], toolHistory =
2046
2051
  status: lastTool.status || (lastTool.ok === false ? 'failed' : 'ok'),
2047
2052
  at: lastTool.at || lastTool.timestamp,
2048
2053
  } : undefined,
2054
+ repeat_tool_policy: (decisionState || {}).repeat_tool_policy,
2055
+ avoid_repeating_tools_unless_needed: ((decisionState || {}).avoid_repeating_tools_unless_needed || []).slice(0, 12),
2049
2056
  do_not_repeat_tools: ((decisionState || {}).do_not_repeat_tools || []).slice(0, 12),
2050
2057
  guidance: turnBriefGuidanceForIntent(currentIntent),
2051
2058
  });
@@ -2092,16 +2099,25 @@ const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) =>
2092
2099
  return null;
2093
2100
  const toolState = (operationalState || {}).tool_state || {};
2094
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);
2095
2110
  return cleanContextValue({
2096
2111
  required_tool: tool,
2097
2112
  next_expected_action: next,
2113
+ evidence_from_recent_tools: recentToolEvidence,
2098
2114
  tool_state: {
2099
2115
  last_tool: (operationalState || {}).last_tool || null,
2100
2116
  required_tool_last_result: latestForTool,
2101
2117
  counts_by_name: toolState.counts_by_name || {},
2102
2118
  failed_by_name: toolState.failed_by_name || {},
2103
2119
  },
2104
- 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.`,
2105
2121
  });
2106
2122
  };
2107
2123
  const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
@@ -2151,6 +2167,19 @@ const shouldCarryPreviousGoal = (intent = '', previousGoal = '') => {
2151
2167
  return false;
2152
2168
  return ['general_message', 'profile_update', 'unknown'].includes(intent);
2153
2169
  };
2170
+ const toolNameSuggestsSideEffect = (name = '') => /(?:^|[_\-. ])(book|booking|reserv\w*|agend\w*|confirm\w*|cancel\w*|cri\w*|create|update|upsert|set|send|enviar|registr\w*|cadastr\w*|charge|cobran\w*|pagamento|ticket)(?:$|[_\-. ])/i.test(String(name || ''));
2171
+ const deriveToolRepeatPolicy = ({ intent = '', query = '', executedTools = [], toolState = {} }) => {
2172
+ const tools = Array.from(new Set(executedTools.filter(Boolean)));
2173
+ const strictMemoryOnlyTurn = intent === 'conversation_recall' || intent === 'operational_status_question' || hasNoToolRequested(query);
2174
+ const sideEffectToolCandidates = tools.filter(toolNameSuggestsSideEffect);
2175
+ return cleanContextValue({
2176
+ mode: strictMemoryOnlyTurn ? 'answer_from_memory_when_possible' : 'conditional_reuse',
2177
+ avoid_repeating_tools_unless_needed: tools,
2178
+ legacy_do_not_repeat_applies: strictMemoryOnlyTurn,
2179
+ side_effect_tool_candidates: sideEffectToolCandidates,
2180
+ instruction: 'Prior tools are evidence, not forbidden tools. Reuse outputs when sufficient; call tools for fresh data, new scope, or current side effects.',
2181
+ });
2182
+ };
2154
2183
  const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [], toolHistory = [], operationalState = {}, previous = {} }) => {
2155
2184
  const intent = inferUserIntent(query, recentMessages);
2156
2185
  const chronological = sortConversationChronological(recentMessages || []);
@@ -2225,7 +2254,6 @@ const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [
2225
2254
  };
2226
2255
  const deriveDecisionState = ({ query = '', toolHistory = [], operationalState = {}, workingMemory = {} }) => {
2227
2256
  const decisions = [];
2228
- const doNotRepeatTools = [];
2229
2257
  const intent = workingMemory.last_user_intent || inferUserIntent(query, []);
2230
2258
  const now = nowIso();
2231
2259
  const toolState = operationalState.tool_state || {};
@@ -2244,9 +2272,9 @@ const deriveDecisionState = ({ query = '', toolHistory = [], operationalState =
2244
2272
  tool: extra.tool,
2245
2273
  });
2246
2274
  };
2247
- for (const name of Object.keys(toolState.counts_by_name || {})) {
2248
- doNotRepeatTools.push(name);
2249
- }
2275
+ const executedTools = Array.from(new Set(Object.keys(toolState.counts_by_name || {})));
2276
+ const toolRepeatPolicy = deriveToolRepeatPolicy({ intent, query, executedTools, toolState });
2277
+ const doNotRepeatTools = toolRepeatPolicy.legacy_do_not_repeat_applies ? executedTools : [];
2250
2278
  if (toolState.last_successful_tool)
2251
2279
  pushDecision('last_successful_tool_recorded', `latest successful tool is ${toolState.last_successful_tool.name}`, 'tool_state records successful tool execution', 'tool_orchestration', { confidence: 0.9, tool: toolState.last_successful_tool.name });
2252
2280
  for (const [name, count] of Object.entries(toolState.failed_by_name || {}))
@@ -2254,15 +2282,18 @@ const deriveDecisionState = ({ query = '', toolHistory = [], operationalState =
2254
2282
  return {
2255
2283
  active_decisions: decisions,
2256
2284
  do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
2285
+ repeat_tool_policy: toolRepeatPolicy,
2286
+ avoid_repeating_tools_unless_needed: toolRepeatPolicy.avoid_repeating_tools_unless_needed,
2257
2287
  current_intent: intent,
2258
2288
  tool_decision_state: {
2259
- executed_tools: Object.keys(toolState.counts_by_name || {}),
2289
+ executed_tools: executedTools,
2260
2290
  failed_tools: Object.keys(toolState.failed_by_name || {}),
2261
2291
  last_successful_tool: toolState.last_successful_tool || null,
2262
2292
  do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
2293
+ repeat_tool_policy: toolRepeatPolicy,
2263
2294
  evaluated_at: now,
2264
2295
  },
2265
- conflict_policy: 'prefer active decisions with newer timestamps; ignore superseded or expired memories unless auditing',
2296
+ conflict_policy: 'prefer newer active decisions; ignore superseded or expired memories unless auditing',
2266
2297
  decision_count: decisions.length,
2267
2298
  latest_tool: toolHistory && toolHistory.length ? {
2268
2299
  name: toolHistory[toolHistory.length - 1].name,
@@ -2503,6 +2534,152 @@ const normalizeToolResultEnvelope = (value) => {
2503
2534
  }
2504
2535
  return parsed;
2505
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
+ };
2506
2683
  const compactParsedToolOutputForSideChannel = (parsed) => {
2507
2684
  if (parsed === undefined || parsed === null)
2508
2685
  return undefined;
@@ -2542,6 +2719,7 @@ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResul
2542
2719
  at: tool.at,
2543
2720
  reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 140),
2544
2721
  input: truncate(String(tool.input || ''), 500) || undefined,
2722
+ facts: extractToolOperationalFacts(tool),
2545
2723
  result: includeResults ? compactToolResult(tool.result, 1200) : undefined,
2546
2724
  }));
2547
2725
  const cleanContextValue = (value) => {
@@ -2580,6 +2758,26 @@ const compactWorkingMemoryForAgent = (memory = {}) => cleanContextValue({
2580
2758
  next_expected_action: memory.next_expected_action,
2581
2759
  updated_at: memory.updated_at,
2582
2760
  });
2761
+ const compactToolDecisionStateForAgent = (toolDecisionState = {}) => {
2762
+ const lastTool = toolDecisionState.last_successful_tool || null;
2763
+ return cleanContextValue({
2764
+ executed_tools: (toolDecisionState.executed_tools || []).slice(0, 12),
2765
+ failed_tools: (toolDecisionState.failed_tools || []).slice(0, 12),
2766
+ last_successful_tool: lastTool ? cleanContextValue({
2767
+ name: lastTool.name || lastTool.tool_name || lastTool.tool,
2768
+ status: lastTool.status || (lastTool.ok === false ? 'failed' : 'ok'),
2769
+ at: lastTool.at || lastTool.timestamp,
2770
+ }) : undefined,
2771
+ do_not_repeat_tools: (toolDecisionState.do_not_repeat_tools || []).slice(0, 12),
2772
+ evaluated_at: toolDecisionState.evaluated_at,
2773
+ });
2774
+ };
2775
+ const compactRepeatToolPolicyForAgent = (policy = {}) => cleanContextValue({
2776
+ mode: policy.mode,
2777
+ legacy_do_not_repeat_applies: policy.legacy_do_not_repeat_applies,
2778
+ side_effect_tool_candidates: (policy.side_effect_tool_candidates || []).slice(0, 12),
2779
+ instruction: policy.instruction,
2780
+ });
2583
2781
  const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
2584
2782
  current_intent: state.current_intent,
2585
2783
  active_decisions: (state.active_decisions || []).slice(-4).map((decision) => cleanContextValue({
@@ -2592,18 +2790,19 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
2592
2790
  at: decision.at || decision.updated_at,
2593
2791
  })),
2594
2792
  do_not_repeat_tools: state.do_not_repeat_tools,
2595
- tool_decision_state: state.tool_decision_state,
2793
+ repeat_tool_policy: state.repeat_tool_policy ? compactRepeatToolPolicyForAgent(state.repeat_tool_policy) : undefined,
2794
+ avoid_repeating_tools_unless_needed: state.avoid_repeating_tools_unless_needed,
2795
+ tool_decision_state: state.tool_decision_state ? compactToolDecisionStateForAgent(state.tool_decision_state) : undefined,
2596
2796
  latest_tool: state.latest_tool ? cleanContextValue({
2597
2797
  name: state.latest_tool.name,
2598
2798
  status: state.latest_tool.status || (state.latest_tool.ok === false ? 'failed' : 'ok'),
2599
2799
  at: state.latest_tool.at,
2600
2800
  }) : undefined,
2601
- conflict_policy: state.conflict_policy,
2602
2801
  });
2603
2802
  const compactOperationalStateForAgent = (state = {}) => {
2604
2803
  const counts = state.tool_counts || {};
2605
2804
  const toolState = state.tool_state || {};
2606
- return {
2805
+ return cleanContextValue({
2607
2806
  last_tool: state.last_tool || null,
2608
2807
  tool_counts: {
2609
2808
  total: counts.total || 0,
@@ -2617,8 +2816,7 @@ const compactOperationalStateForAgent = (state = {}) => {
2617
2816
  last_successful_tool: toolState.last_successful_tool || undefined,
2618
2817
  } : undefined,
2619
2818
  blocked_without_context: state.blocked_without_context || [],
2620
- guidance: (state.guidance || []).slice(0, 3),
2621
- };
2819
+ });
2622
2820
  };
2623
2821
  const compactMemoryCompressionForAgent = (compression = {}) => ({
2624
2822
  turn_summary: (compression.turn_summary || []).slice(-3),
@@ -3138,6 +3336,12 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
3138
3336
  decisionCount: parsed.decisionState?.decision_count,
3139
3337
  latestTool: parsed.decisionState?.latest_tool,
3140
3338
  doNotRepeatTools: parsed.decisionState?.do_not_repeat_tools,
3339
+ avoidRepeatingToolsUnlessNeeded: parsed.decisionState?.avoid_repeating_tools_unless_needed,
3340
+ repeatToolPolicy: parsed.decisionState?.repeat_tool_policy ? {
3341
+ mode: parsed.decisionState.repeat_tool_policy.mode,
3342
+ legacyDoNotRepeatApplies: parsed.decisionState.repeat_tool_policy.legacy_do_not_repeat_applies,
3343
+ sideEffectToolCandidates: parsed.decisionState.repeat_tool_policy.side_effect_tool_candidates,
3344
+ } : undefined,
3141
3345
  });
3142
3346
  summary.quality = parsed.contextHealth?.quality_score || parsed.contextQualityScore || undefined;
3143
3347
  summary.debug = includeDebug ? cleanContextValue({
@@ -3338,7 +3542,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
3338
3542
  value: cleanContextValue({
3339
3543
  current_intent: (decisionState || {}).current_intent || workingMemory.last_user_intent,
3340
3544
  latest_tool: ((decisionState || {}).latest_tool || (operationalState || {}).last_tool || undefined),
3341
- do_not_repeat_tools: ((decisionState || {}).do_not_repeat_tools || []).slice(0, 12),
3545
+ repeat_tool_policy: (decisionState || {}).repeat_tool_policy,
3546
+ avoid_repeating_tools_unless_needed: ((decisionState || {}).avoid_repeating_tools_unless_needed || []).slice(0, 12),
3547
+ do_not_repeat_tools_legacy: ((decisionState || {}).do_not_repeat_tools || []).slice(0, 12),
3342
3548
  instruction: actionDirective || workingMemory.next_expected_action || 'Continue according to the agent prompt.',
3343
3549
  }),
3344
3550
  });
@@ -3382,7 +3588,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
3382
3588
  statusAnswerMaterial: sectionValue('status_answer_material'),
3383
3589
  action_directive: directive ? cleanContextValue({
3384
3590
  required_tool: directive.required_tool,
3385
- next_expected_action: directive.next_expected_action,
3591
+ evidence_from_recent_tools: directive.evidence_from_recent_tools,
3386
3592
  instruction: directive.instruction,
3387
3593
  }) : undefined,
3388
3594
  turnBrief: compactTurnBriefForAgent(sectionValue('turn_brief')),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.1.38",
3
+ "version": "1.1.40",
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",