n8n-nodes-tembory 1.1.37 → 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,19 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.1.37`.
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.
12
+
13
+ ## 1.1.38
14
+
15
+ - Torna o resumo visual independente do tipo da mensagem LangChain, procurando o contexto Tembory em qualquer mensagem carregada.
16
+ - Enriquece eventos `chatHistory.addMessage(s)` com `savedMessages`, input/output resumidos e tool events quando esse for o caminho usado pelo Agent.
17
+ - Mantem a separacao entre contexto interno do Agent e output compacto para humano.
6
18
 
7
19
  ## 1.1.37
8
20
 
@@ -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 || {};
@@ -3027,10 +3065,11 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
3027
3065
  const type = messageTypeOf(message);
3028
3066
  return type === 'system' || /system/i.test(String(message?.role || ''));
3029
3067
  });
3030
- if (!firstSystem)
3068
+ const contextMessage = firstSystem || list.find((message) => messageContentOf(message).includes('tembory.agent_context.v1')) || list[0];
3069
+ if (!contextMessage)
3031
3070
  return summary;
3032
3071
  try {
3033
- const rawContent = messageContentOf(firstSystem);
3072
+ const rawContent = messageContentOf(contextMessage);
3034
3073
  let parsed;
3035
3074
  try {
3036
3075
  parsed = JSON.parse(rawContent);
@@ -3137,6 +3176,12 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
3137
3176
  decisionCount: parsed.decisionState?.decision_count,
3138
3177
  latestTool: parsed.decisionState?.latest_tool,
3139
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,
3140
3185
  });
3141
3186
  summary.quality = parsed.contextHealth?.quality_score || parsed.contextQualityScore || undefined;
3142
3187
  summary.debug = includeDebug ? cleanContextValue({
@@ -3150,7 +3195,7 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
3150
3195
  return Object.fromEntries(Object.entries(summary).filter(([, value]) => value !== undefined));
3151
3196
  }
3152
3197
  catch {
3153
- const content = messageContentOf(firstSystem);
3198
+ const content = messageContentOf(contextMessage);
3154
3199
  return cleanContextValue({
3155
3200
  ...summary,
3156
3201
  contextChars: content.length || undefined,
@@ -3337,7 +3382,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
3337
3382
  value: cleanContextValue({
3338
3383
  current_intent: (decisionState || {}).current_intent || workingMemory.last_user_intent,
3339
3384
  latest_tool: ((decisionState || {}).latest_tool || (operationalState || {}).last_tool || undefined),
3340
- 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),
3341
3388
  instruction: actionDirective || workingMemory.next_expected_action || 'Continue according to the agent prompt.',
3342
3389
  }),
3343
3390
  });
@@ -3852,24 +3899,40 @@ class TemboryMemory {
3852
3899
  addMessage: async (message) => {
3853
3900
  currentMessages.push(message);
3854
3901
  const saved = await TemboryMemory.prototype.saveMessagesForItem.call(this, itemIndex, [message]);
3855
- recordMemoryEvent('chatHistory.addMessage', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: 1 });
3902
+ recordMemoryEvent('chatHistory.addMessage', {
3903
+ saved: Boolean(saved && saved.saved),
3904
+ messages: 1,
3905
+ ...summarizeSaveContextForSideChannel((saved && saved.input) || {}, (saved && saved.output) || {}, currentMessages),
3906
+ });
3856
3907
  },
3857
3908
  addUserMessage: async (message) => {
3858
3909
  const baseMessage = toBaseMessage({ role: 'user', content: message });
3859
3910
  currentMessages.push(baseMessage);
3860
3911
  const saved = await TemboryMemory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
3861
- recordMemoryEvent('chatHistory.addUserMessage', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: 1 });
3912
+ recordMemoryEvent('chatHistory.addUserMessage', {
3913
+ saved: Boolean(saved && saved.saved),
3914
+ messages: 1,
3915
+ ...summarizeSaveContextForSideChannel((saved && saved.input) || {}, (saved && saved.output) || {}, currentMessages),
3916
+ });
3862
3917
  },
3863
3918
  addAIChatMessage: async (message) => {
3864
3919
  const baseMessage = toBaseMessage({ role: 'assistant', content: message });
3865
3920
  currentMessages.push(baseMessage);
3866
3921
  const saved = await TemboryMemory.prototype.saveMessagesForItem.call(this, itemIndex, [baseMessage]);
3867
- recordMemoryEvent('chatHistory.addAIChatMessage', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: 1 });
3922
+ recordMemoryEvent('chatHistory.addAIChatMessage', {
3923
+ saved: Boolean(saved && saved.saved),
3924
+ messages: 1,
3925
+ ...summarizeSaveContextForSideChannel((saved && saved.input) || {}, (saved && saved.output) || {}, currentMessages),
3926
+ });
3868
3927
  },
3869
3928
  addMessages: async (messages) => {
3870
3929
  currentMessages.push(...messages);
3871
3930
  const saved = await TemboryMemory.prototype.saveMessagesForItem.call(this, itemIndex, messages);
3872
- recordMemoryEvent('chatHistory.addMessages', { saved: Boolean(saved && saved.saved), toolCalls: (saved && saved.toolCalls) || [], messages: Array.isArray(messages) ? messages.length : 0 });
3931
+ recordMemoryEvent('chatHistory.addMessages', {
3932
+ saved: Boolean(saved && saved.saved),
3933
+ messages: Array.isArray(messages) ? messages.length : 0,
3934
+ ...summarizeSaveContextForSideChannel((saved && saved.input) || {}, (saved && saved.output) || {}, currentMessages),
3935
+ });
3873
3936
  },
3874
3937
  clear: async () => {
3875
3938
  currentMessages = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.1.37",
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",