n8n-nodes-tembory 1.1.38 → 1.1.39

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,13 @@
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.39`.
6
+
7
+ ## 1.1.39
8
+
9
+ - 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.
10
+ - 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.
11
+ - Compacta `decisionState.tool_decision_state` para evitar duplicar outputs longos de tools no contexto balanceado.
6
12
 
7
13
  ## 1.1.38
8
14
 
@@ -2046,6 +2046,8 @@ const buildTurnBriefForAgent = ({ query = '', recentMessages = [], toolHistory =
2046
2046
  status: lastTool.status || (lastTool.ok === false ? 'failed' : 'ok'),
2047
2047
  at: lastTool.at || lastTool.timestamp,
2048
2048
  } : undefined,
2049
+ repeat_tool_policy: (decisionState || {}).repeat_tool_policy,
2050
+ avoid_repeating_tools_unless_needed: ((decisionState || {}).avoid_repeating_tools_unless_needed || []).slice(0, 12),
2049
2051
  do_not_repeat_tools: ((decisionState || {}).do_not_repeat_tools || []).slice(0, 12),
2050
2052
  guidance: turnBriefGuidanceForIntent(currentIntent),
2051
2053
  });
@@ -2151,6 +2153,19 @@ const shouldCarryPreviousGoal = (intent = '', previousGoal = '') => {
2151
2153
  return false;
2152
2154
  return ['general_message', 'profile_update', 'unknown'].includes(intent);
2153
2155
  };
2156
+ 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 || ''));
2157
+ const deriveToolRepeatPolicy = ({ intent = '', query = '', executedTools = [], toolState = {} }) => {
2158
+ const tools = Array.from(new Set(executedTools.filter(Boolean)));
2159
+ const strictMemoryOnlyTurn = intent === 'conversation_recall' || intent === 'operational_status_question' || hasNoToolRequested(query);
2160
+ const sideEffectToolCandidates = tools.filter(toolNameSuggestsSideEffect);
2161
+ return cleanContextValue({
2162
+ mode: strictMemoryOnlyTurn ? 'answer_from_memory_when_possible' : 'conditional_reuse',
2163
+ avoid_repeating_tools_unless_needed: tools,
2164
+ legacy_do_not_repeat_applies: strictMemoryOnlyTurn,
2165
+ side_effect_tool_candidates: sideEffectToolCandidates,
2166
+ instruction: 'Prior tools are evidence, not forbidden tools. Reuse outputs when sufficient; call tools for fresh data, new scope, or current side effects.',
2167
+ });
2168
+ };
2154
2169
  const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [], toolHistory = [], operationalState = {}, previous = {} }) => {
2155
2170
  const intent = inferUserIntent(query, recentMessages);
2156
2171
  const chronological = sortConversationChronological(recentMessages || []);
@@ -2225,7 +2240,6 @@ const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [
2225
2240
  };
2226
2241
  const deriveDecisionState = ({ query = '', toolHistory = [], operationalState = {}, workingMemory = {} }) => {
2227
2242
  const decisions = [];
2228
- const doNotRepeatTools = [];
2229
2243
  const intent = workingMemory.last_user_intent || inferUserIntent(query, []);
2230
2244
  const now = nowIso();
2231
2245
  const toolState = operationalState.tool_state || {};
@@ -2244,9 +2258,9 @@ const deriveDecisionState = ({ query = '', toolHistory = [], operationalState =
2244
2258
  tool: extra.tool,
2245
2259
  });
2246
2260
  };
2247
- for (const name of Object.keys(toolState.counts_by_name || {})) {
2248
- doNotRepeatTools.push(name);
2249
- }
2261
+ const executedTools = Array.from(new Set(Object.keys(toolState.counts_by_name || {})));
2262
+ const toolRepeatPolicy = deriveToolRepeatPolicy({ intent, query, executedTools, toolState });
2263
+ const doNotRepeatTools = toolRepeatPolicy.legacy_do_not_repeat_applies ? executedTools : [];
2250
2264
  if (toolState.last_successful_tool)
2251
2265
  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
2266
  for (const [name, count] of Object.entries(toolState.failed_by_name || {}))
@@ -2254,15 +2268,18 @@ const deriveDecisionState = ({ query = '', toolHistory = [], operationalState =
2254
2268
  return {
2255
2269
  active_decisions: decisions,
2256
2270
  do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
2271
+ repeat_tool_policy: toolRepeatPolicy,
2272
+ avoid_repeating_tools_unless_needed: toolRepeatPolicy.avoid_repeating_tools_unless_needed,
2257
2273
  current_intent: intent,
2258
2274
  tool_decision_state: {
2259
- executed_tools: Object.keys(toolState.counts_by_name || {}),
2275
+ executed_tools: executedTools,
2260
2276
  failed_tools: Object.keys(toolState.failed_by_name || {}),
2261
2277
  last_successful_tool: toolState.last_successful_tool || null,
2262
2278
  do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
2279
+ repeat_tool_policy: toolRepeatPolicy,
2263
2280
  evaluated_at: now,
2264
2281
  },
2265
- conflict_policy: 'prefer active decisions with newer timestamps; ignore superseded or expired memories unless auditing',
2282
+ conflict_policy: 'prefer newer active decisions; ignore superseded or expired memories unless auditing',
2266
2283
  decision_count: decisions.length,
2267
2284
  latest_tool: toolHistory && toolHistory.length ? {
2268
2285
  name: toolHistory[toolHistory.length - 1].name,
@@ -2580,6 +2597,26 @@ const compactWorkingMemoryForAgent = (memory = {}) => cleanContextValue({
2580
2597
  next_expected_action: memory.next_expected_action,
2581
2598
  updated_at: memory.updated_at,
2582
2599
  });
2600
+ const compactToolDecisionStateForAgent = (toolDecisionState = {}) => {
2601
+ const lastTool = toolDecisionState.last_successful_tool || null;
2602
+ return cleanContextValue({
2603
+ executed_tools: (toolDecisionState.executed_tools || []).slice(0, 12),
2604
+ failed_tools: (toolDecisionState.failed_tools || []).slice(0, 12),
2605
+ last_successful_tool: lastTool ? cleanContextValue({
2606
+ name: lastTool.name || lastTool.tool_name || lastTool.tool,
2607
+ status: lastTool.status || (lastTool.ok === false ? 'failed' : 'ok'),
2608
+ at: lastTool.at || lastTool.timestamp,
2609
+ }) : undefined,
2610
+ do_not_repeat_tools: (toolDecisionState.do_not_repeat_tools || []).slice(0, 12),
2611
+ evaluated_at: toolDecisionState.evaluated_at,
2612
+ });
2613
+ };
2614
+ const compactRepeatToolPolicyForAgent = (policy = {}) => cleanContextValue({
2615
+ mode: policy.mode,
2616
+ legacy_do_not_repeat_applies: policy.legacy_do_not_repeat_applies,
2617
+ side_effect_tool_candidates: (policy.side_effect_tool_candidates || []).slice(0, 12),
2618
+ instruction: policy.instruction,
2619
+ });
2583
2620
  const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
2584
2621
  current_intent: state.current_intent,
2585
2622
  active_decisions: (state.active_decisions || []).slice(-4).map((decision) => cleanContextValue({
@@ -2592,13 +2629,14 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
2592
2629
  at: decision.at || decision.updated_at,
2593
2630
  })),
2594
2631
  do_not_repeat_tools: state.do_not_repeat_tools,
2595
- tool_decision_state: state.tool_decision_state,
2632
+ repeat_tool_policy: state.repeat_tool_policy ? compactRepeatToolPolicyForAgent(state.repeat_tool_policy) : undefined,
2633
+ avoid_repeating_tools_unless_needed: state.avoid_repeating_tools_unless_needed,
2634
+ tool_decision_state: state.tool_decision_state ? compactToolDecisionStateForAgent(state.tool_decision_state) : undefined,
2596
2635
  latest_tool: state.latest_tool ? cleanContextValue({
2597
2636
  name: state.latest_tool.name,
2598
2637
  status: state.latest_tool.status || (state.latest_tool.ok === false ? 'failed' : 'ok'),
2599
2638
  at: state.latest_tool.at,
2600
2639
  }) : undefined,
2601
- conflict_policy: state.conflict_policy,
2602
2640
  });
2603
2641
  const compactOperationalStateForAgent = (state = {}) => {
2604
2642
  const counts = state.tool_counts || {};
@@ -3138,6 +3176,12 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
3138
3176
  decisionCount: parsed.decisionState?.decision_count,
3139
3177
  latestTool: parsed.decisionState?.latest_tool,
3140
3178
  doNotRepeatTools: parsed.decisionState?.do_not_repeat_tools,
3179
+ avoidRepeatingToolsUnlessNeeded: parsed.decisionState?.avoid_repeating_tools_unless_needed,
3180
+ repeatToolPolicy: parsed.decisionState?.repeat_tool_policy ? {
3181
+ mode: parsed.decisionState.repeat_tool_policy.mode,
3182
+ legacyDoNotRepeatApplies: parsed.decisionState.repeat_tool_policy.legacy_do_not_repeat_applies,
3183
+ sideEffectToolCandidates: parsed.decisionState.repeat_tool_policy.side_effect_tool_candidates,
3184
+ } : undefined,
3141
3185
  });
3142
3186
  summary.quality = parsed.contextHealth?.quality_score || parsed.contextQualityScore || undefined;
3143
3187
  summary.debug = includeDebug ? cleanContextValue({
@@ -3338,7 +3382,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
3338
3382
  value: cleanContextValue({
3339
3383
  current_intent: (decisionState || {}).current_intent || workingMemory.last_user_intent,
3340
3384
  latest_tool: ((decisionState || {}).latest_tool || (operationalState || {}).last_tool || undefined),
3341
- do_not_repeat_tools: ((decisionState || {}).do_not_repeat_tools || []).slice(0, 12),
3385
+ repeat_tool_policy: (decisionState || {}).repeat_tool_policy,
3386
+ avoid_repeating_tools_unless_needed: ((decisionState || {}).avoid_repeating_tools_unless_needed || []).slice(0, 12),
3387
+ do_not_repeat_tools_legacy: ((decisionState || {}).do_not_repeat_tools || []).slice(0, 12),
3342
3388
  instruction: actionDirective || workingMemory.next_expected_action || 'Continue according to the agent prompt.',
3343
3389
  }),
3344
3390
  });
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.39",
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",