n8n-nodes-tembory 1.1.43 → 1.1.44

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,14 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.1.43`.
5
+ Versao atual: `1.1.44`.
6
+
7
+ ## 1.1.44
8
+
9
+ - Adiciona `toolReuseGuard` no contexto compacto do agente para reduzir repeticao indevida de tools entre turnos.
10
+ - Registra chamadas bem sucedidas recentes com `input_hash` e classifica tools de leitura versus side effect de forma generica.
11
+ - Instrui o agente a consultar `tool_ledger` antes de nova tool e a nao repetir side effects sem pedido explicito do usuario.
12
+ - Remove duplicacoes do payload compacto para manter o contexto balanceado abaixo do limite de regressao.
6
13
 
7
14
  ## 1.1.43
8
15
 
@@ -2172,17 +2172,56 @@ const shouldCarryPreviousGoal = (intent = '', previousGoal = '') => {
2172
2172
  return false;
2173
2173
  return ['general_message', 'profile_update', 'unknown'].includes(intent);
2174
2174
  };
2175
- 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 || ''));
2176
- const deriveToolRepeatPolicy = ({ intent = '', query = '', executedTools = [], toolState = {} }) => {
2175
+ const toolNameSuggestsSideEffect = (name = '') => {
2176
+ const text = String(name || '');
2177
+ if (/(?:^|[_\-. ])(check|consult\w*|search|lookup|find|list|get|read|info|inform\w*|disponibilidade|availability)(?:$|[_\-. ])/i.test(text))
2178
+ return false;
2179
+ return /(?:^|[_\-. ])(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(text);
2180
+ };
2181
+ const compactToolPolicyPayload = (value, max = 420) => {
2182
+ const parsed = safeParseToolPayload(value);
2183
+ const cleaned = stripNoisyToolFields(parsed);
2184
+ return truncate(typeof cleaned === 'string' ? cleaned : safeStringify(cleaned), max) || undefined;
2185
+ };
2186
+ const compactSuccessfulToolCallsForPolicy = (toolHistory = [], toolState = {}, maxItems = 10) => {
2187
+ const fromHistory = (Array.isArray(toolHistory) ? toolHistory : [])
2188
+ .filter((tool) => tool && tool.ok !== false && (tool.name || tool.tool_name || tool.tool))
2189
+ .map((tool) => {
2190
+ const name = String(tool.name || tool.tool_name || tool.tool || 'unknown_tool');
2191
+ return cleanContextValue({
2192
+ name,
2193
+ at: tool.at || tool.timestamp,
2194
+ input_hash: stableHash({ name, input: safeParseToolPayload(tool.input) }),
2195
+ side_effect: toolNameSuggestsSideEffect(name),
2196
+ });
2197
+ });
2198
+ const fromToolState = Object.values((toolState || {}).latest_by_name || {}).map((tool) => {
2199
+ const name = String(tool.name || tool.tool || 'unknown_tool');
2200
+ const input = compactToolPolicyPayload(tool.input, 220);
2201
+ return cleanContextValue({
2202
+ name,
2203
+ at: tool.at || tool.timestamp,
2204
+ input_hash: stableHash({ name, input }),
2205
+ side_effect: toolNameSuggestsSideEffect(name),
2206
+ });
2207
+ });
2208
+ return pruneByLimit(fromHistory.length ? fromHistory : fromToolState, maxItems);
2209
+ };
2210
+ const deriveToolRepeatPolicy = ({ intent = '', query = '', executedTools = [], toolState = {}, toolHistory = [] }) => {
2177
2211
  const tools = Array.from(new Set(executedTools.filter(Boolean)));
2178
2212
  const strictMemoryOnlyTurn = intent === 'conversation_recall' || intent === 'operational_status_question' || hasNoToolRequested(query);
2179
2213
  const sideEffectToolCandidates = tools.filter(toolNameSuggestsSideEffect);
2214
+ const successfulToolCalls = compactSuccessfulToolCallsForPolicy(toolHistory, toolState, 10);
2215
+ const referenceToolCandidates = tools.filter((tool) => !toolNameSuggestsSideEffect(tool));
2180
2216
  return cleanContextValue({
2181
2217
  mode: strictMemoryOnlyTurn ? 'answer_from_memory_when_possible' : 'conditional_reuse',
2182
2218
  avoid_repeating_tools_unless_needed: tools,
2183
2219
  legacy_do_not_repeat_applies: strictMemoryOnlyTurn,
2184
2220
  side_effect_tool_candidates: sideEffectToolCandidates,
2185
- instruction: 'Prior tools are evidence, not forbidden tools. Reuse outputs when sufficient; call tools for fresh data, new scope, or current side effects.',
2221
+ do_not_repeat_side_effect_tools_without_explicit_new_request: sideEffectToolCandidates,
2222
+ answer_from_memory_candidates: referenceToolCandidates,
2223
+ successful_tool_calls: successfulToolCalls,
2224
+ instruction: 'Check tool_ledger before any tool. If same tool/input already succeeded and user did not request new or fresh data, answer from prior result. Never repeat side-effect tools without an explicit new user request.',
2186
2225
  });
2187
2226
  };
2188
2227
  const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [], toolHistory = [], operationalState = {}, previous = {} }) => {
@@ -2278,7 +2317,7 @@ const deriveDecisionState = ({ query = '', toolHistory = [], operationalState =
2278
2317
  });
2279
2318
  };
2280
2319
  const executedTools = Array.from(new Set(Object.keys(toolState.counts_by_name || {})));
2281
- const toolRepeatPolicy = deriveToolRepeatPolicy({ intent, query, executedTools, toolState });
2320
+ const toolRepeatPolicy = deriveToolRepeatPolicy({ intent, query, executedTools, toolState, toolHistory });
2282
2321
  const doNotRepeatTools = toolRepeatPolicy.legacy_do_not_repeat_applies ? executedTools : [];
2283
2322
  if (toolState.last_successful_tool)
2284
2323
  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 });
@@ -2814,8 +2853,9 @@ const compactToolDecisionStateForAgent = (toolDecisionState = {}) => {
2814
2853
  };
2815
2854
  const compactRepeatToolPolicyForAgent = (policy = {}) => cleanContextValue({
2816
2855
  mode: policy.mode,
2817
- legacy_do_not_repeat_applies: policy.legacy_do_not_repeat_applies,
2818
- side_effect_tool_candidates: (policy.side_effect_tool_candidates || []).slice(0, 12),
2856
+ do_not_repeat_side_effect_tools: (policy.do_not_repeat_side_effect_tools_without_explicit_new_request || []).slice(0, 12),
2857
+ answer_from_memory_candidates: (policy.answer_from_memory_candidates || []).slice(0, 12),
2858
+ successful_tool_calls: (policy.successful_tool_calls || []).slice(-8),
2819
2859
  instruction: policy.instruction,
2820
2860
  });
2821
2861
  const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
@@ -2830,9 +2870,6 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
2830
2870
  at: decision.at || decision.updated_at,
2831
2871
  })),
2832
2872
  do_not_repeat_tools: state.do_not_repeat_tools,
2833
- repeat_tool_policy: state.repeat_tool_policy ? compactRepeatToolPolicyForAgent(state.repeat_tool_policy) : undefined,
2834
- avoid_repeating_tools_unless_needed: state.avoid_repeating_tools_unless_needed,
2835
- tool_decision_state: state.tool_decision_state ? compactToolDecisionStateForAgent(state.tool_decision_state) : undefined,
2836
2873
  latest_tool: state.latest_tool ? cleanContextValue({
2837
2874
  name: state.latest_tool.name,
2838
2875
  status: state.latest_tool.status || (state.latest_tool.ok === false ? 'failed' : 'ok'),
@@ -3700,7 +3737,6 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
3700
3737
  value: cleanContextValue({
3701
3738
  current_intent: (decisionState || {}).current_intent || workingMemory.last_user_intent,
3702
3739
  latest_tool: ((decisionState || {}).latest_tool || (operationalState || {}).last_tool || undefined),
3703
- repeat_tool_policy: (decisionState || {}).repeat_tool_policy,
3704
3740
  avoid_repeating_tools_unless_needed: ((decisionState || {}).avoid_repeating_tools_unless_needed || []).slice(0, 12),
3705
3741
  do_not_repeat_tools_legacy: ((decisionState || {}).do_not_repeat_tools || []).slice(0, 12),
3706
3742
  instruction: actionDirective || workingMemory.next_expected_action || 'Continue according to the agent prompt.',
@@ -3757,6 +3793,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
3757
3793
  state: minimalState,
3758
3794
  workingMemory: sectionValue('working_memory'),
3759
3795
  decisionState: sectionValue('decision_state'),
3796
+ toolReuseGuard: compactRepeatToolPolicyForAgent((decisionState || {}).repeat_tool_policy || {}),
3760
3797
  operationalState: sectionValue('operational_state'),
3761
3798
  actionLedger: sectionValue('action_ledger'),
3762
3799
  memoryCompression: sectionValue('memory_compression'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.1.43",
3
+ "version": "1.1.44",
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",