n8n-nodes-tembory 1.0.29 → 1.0.31

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.0.29`.
5
+ Versao atual: `1.0.31`.
6
+
7
+ ## 1.0.31
8
+
9
+ - Considera pre-reserva recente no transcript como estado operacional valido quando `tool_history` ainda nao foi recuperado no turno.
10
+ - Evita chamar `agenda_pre_reservar_horario` novamente antes de confirmar quando a mensagem recente ja diz que houve pre-reserva.
11
+ - Direciona confirmacoes como "pode confirmar" direto para `agenda_confirmar_agendamento` quando existe pre-reserva recente pendente.
12
+
13
+ ## 1.0.30
14
+
15
+ - Adiciona `Action directive` quando a memoria conclui que uma tool deve ser chamada no turno atual.
16
+ - Torna `next_expected_action` mais coercitivo para agenda: usa `MUST call ...` e proibe respostas como "se quiser/posso" no lugar da tool obrigatoria.
17
+ - Reduz o peso instrucional de `first_user_message`; o foco passa a ser mensagem atual, mensagem anterior e transcript cronologico recente.
6
18
 
7
19
  ## 1.0.29
8
20
 
@@ -427,6 +427,25 @@ const availabilityFromRecentMessages = (recentMessages = []) => {
427
427
  source: latest.source || 'recent_message',
428
428
  };
429
429
  };
430
+ const preReservationFromRecentMessages = (recentMessages = []) => {
431
+ const messages = Array.isArray(recentMessages) ? recentMessages : [];
432
+ const candidates = [...messages].reverse().filter((message) => {
433
+ const role = String(message.role || '').toLowerCase();
434
+ const text = String(message.content || message.text || message.memory || '');
435
+ if (role && role !== 'assistant' && role !== 'ai' && role !== 'system')
436
+ return false;
437
+ return /\b(pr[eé][-\s]?reservad[oa]|pre[-\s]?reservad[oa]|reservation_id|RES-[A-Z0-9-]+)\b/i.test(text)
438
+ && !/\b(confirmad[oa]|confirmation_id|CONF-[A-Z0-9-]+)\b/i.test(text);
439
+ });
440
+ const latest = candidates[0];
441
+ if (!latest)
442
+ return null;
443
+ return {
444
+ text: truncate(String(latest.content || latest.text || latest.memory || ''), 900),
445
+ at: latest.at || latest.created_at || latest.createdAt || nowIso(),
446
+ source: latest.source || 'recent_message',
447
+ };
448
+ };
430
449
  const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessages = [], includeResults = true) => {
431
450
  const tools = Array.isArray(toolHistory) ? toolHistory : [];
432
451
  const successfulTools = tools.filter((tool) => tool.ok !== false);
@@ -435,15 +454,17 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
435
454
  const recentAvailability = availability.length ? null : availabilityFromRecentMessages(recentMessages);
436
455
  const hasAvailability = availability.length > 0 || Boolean(recentAvailability);
437
456
  const reservations = byName('agenda_pre_reservar_horario');
457
+ const recentPreReservation = reservations.length ? null : preReservationFromRecentMessages(recentMessages);
458
+ const hasPreReservation = reservations.length > 0 || Boolean(recentPreReservation);
438
459
  const confirmations = byName('agenda_confirmar_agendamento');
439
460
  const lastTool = tools[tools.length - 1] || null;
440
461
  const lastReservation = reservations[reservations.length - 1] || null;
441
462
  const lastConfirmation = confirmations[confirmations.length - 1] || null;
442
- const hasPendingReservation = Boolean(lastReservation && (!lastConfirmation || String(lastReservation.at || '') > String(lastConfirmation.at || '')));
463
+ const hasPendingReservation = Boolean((lastReservation && (!lastConfirmation || String(lastReservation.at || '') > String(lastConfirmation.at || ''))) || (recentPreReservation && !lastConfirmation));
443
464
  const blockedWithoutContext = [];
444
465
  if (!hasAvailability)
445
466
  blockedWithoutContext.push('agenda_pre_reservar_horario');
446
- if (!reservations.length)
467
+ if (!hasPreReservation)
447
468
  blockedWithoutContext.push('agenda_confirmar_agendamento');
448
469
  const guidance = [];
449
470
  if (!hasAvailability)
@@ -467,12 +488,13 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
467
488
  },
468
489
  agenda_state: {
469
490
  has_availability: hasAvailability,
470
- has_pre_reservation: reservations.length > 0,
491
+ has_pre_reservation: hasPreReservation,
471
492
  has_confirmation: confirmations.length > 0,
472
493
  has_pending_pre_reservation: hasPendingReservation,
473
494
  latest_availability_result: availability.length ? maybeToolResult(availability[availability.length - 1], includeResults) : (recentAvailability && includeResults !== false ? recentAvailability.text : null),
474
495
  latest_availability_source: availability.length ? 'tool_history' : (recentAvailability ? recentAvailability.source : null),
475
- latest_pre_reservation_result: lastReservation ? maybeToolResult(lastReservation, includeResults) : null,
496
+ latest_pre_reservation_result: lastReservation ? maybeToolResult(lastReservation, includeResults) : (recentPreReservation && includeResults !== false ? recentPreReservation.text : null),
497
+ latest_pre_reservation_source: lastReservation ? 'tool_history' : (recentPreReservation ? recentPreReservation.source : null),
476
498
  latest_confirmation_result: lastConfirmation ? maybeToolResult(lastConfirmation, includeResults) : null,
477
499
  },
478
500
  blocked_without_context: Array.from(new Set(blockedWithoutContext)),
@@ -1306,7 +1328,7 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
1306
1328
  .filter((msg) => normalizeIntentText(msg.content).trim() !== normalizedQuery)
1307
1329
  .slice(0, 5)
1308
1330
  .map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at })),
1309
- instruction: 'This is the authoritative short-term conversation frame. If the user asks about first/current/previous/last client messages or what they already said, answer from first_user_message/current_user_message/previous_user_message/conversation_history_chronological/all_user_messages_chronological before using vector memories, summaries, operational state, or tool history.',
1331
+ instruction: 'This is the authoritative short-term conversation frame. If the user asks about current/previous/last client messages or what they already said, answer from current_user_message/previous_user_message/conversation_history_chronological/all_user_messages_chronological before using vector memories, summaries, operational state, or tool history.',
1310
1332
  });
1311
1333
  if (!previousUserMessage)
1312
1334
  frame.previous_user_message = null;
@@ -1346,13 +1368,33 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
1346
1368
  has_availability: Boolean(agenda.has_availability),
1347
1369
  } : undefined,
1348
1370
  instruction: recall
1349
- ? 'Answer the user meta-question from first_user_message/previous_user_message/recent_user_messages. Do not answer with agenda availability unless the user asks for availability.'
1371
+ ? 'Answer the user meta-question from previous_user_message/recent_user_messages and the chronological transcript. Do not answer with agenda availability unless the user asks for availability.'
1350
1372
  : 'Answer whether the appointment/reservation is already scheduled using agenda_status. Do not offer availability as the main answer unless there is no schedule/reservation state.',
1351
1373
  });
1352
1374
  if (!previousUserMessage)
1353
1375
  focus.previous_user_message = null;
1354
1376
  return focus;
1355
1377
  };
1378
+ const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) => {
1379
+ const next = String((workingMemory || {}).next_expected_action || '');
1380
+ const match = /\bagenda_(consultar_disponibilidade|pre_reservar_horario|confirmar_agendamento)\b/.exec(next);
1381
+ if (!match || !/\bcall\b|\bMUST\b/i.test(next))
1382
+ return null;
1383
+ const tool = match[0];
1384
+ const agenda = (operationalState || {}).agenda_state || {};
1385
+ return cleanContextValue({
1386
+ required_tool: tool,
1387
+ next_expected_action: next,
1388
+ agenda_state: {
1389
+ has_availability: Boolean(agenda.has_availability),
1390
+ latest_availability_source: agenda.latest_availability_source,
1391
+ has_pre_reservation: Boolean(agenda.has_pre_reservation),
1392
+ has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1393
+ has_confirmation: Boolean(agenda.has_confirmation),
1394
+ },
1395
+ instruction: `You MUST call ${tool} now. Do not answer with "posso", "se quiser", "já posso" or ask for the same confirmation instead of calling the required tool. Only skip this tool if it is explicitly blocked in Tool guard.`,
1396
+ });
1397
+ };
1356
1398
  const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
1357
1399
  const text = normalizeIntentText(query);
1358
1400
  const names = new Set((toolHistory || []).map((tool) => String(tool.name || '')));
@@ -1408,24 +1450,26 @@ const deriveNextExpectedAction = (intent, operationalState = {}) => {
1408
1450
  const agenda = operationalState.agenda_state || {};
1409
1451
  if (intent === 'affirm') {
1410
1452
  if (agenda.has_pending_pre_reservation)
1411
- return 'call agenda_confirmar_agendamento now; do not ask the same confirmation question again';
1453
+ return 'MUST call agenda_confirmar_agendamento now; do not ask the same confirmation question again';
1412
1454
  if (agenda.has_availability)
1413
- return 'call agenda_pre_reservar_horario now using the selected or latest discussed slot; do not ask again';
1455
+ return 'MUST call agenda_pre_reservar_horario now using the selected or latest discussed slot; do not ask again';
1414
1456
  return 'call agenda_consultar_disponibilidade before reserving';
1415
1457
  }
1416
1458
  if (intent === 'confirm') {
1417
1459
  if (agenda.has_pending_pre_reservation)
1418
- return 'call agenda_confirmar_agendamento now using the latest pre-reservation context; do not ask again';
1419
- return 'do not call agenda_confirmar_agendamento; call agenda_pre_reservar_horario first if a selected slot and availability are known, otherwise call agenda_consultar_disponibilidade';
1460
+ return 'MUST call agenda_confirmar_agendamento now using the latest pre-reservation context; do not ask again';
1461
+ if (agenda.has_availability)
1462
+ return 'MUST call agenda_pre_reservar_horario first using the selected or latest discussed slot; do not ask for permission again';
1463
+ return 'do not call agenda_confirmar_agendamento; call agenda_consultar_disponibilidade first because no availability is known';
1420
1464
  }
1421
1465
  if (intent === 'select_slot') {
1422
1466
  if (agenda.has_availability)
1423
- return 'call agenda_pre_reservar_horario now using the selected slot; do not ask for confirmation before pre-reserving';
1467
+ return 'MUST call agenda_pre_reservar_horario now using the selected slot; do not ask for confirmation before pre-reserving';
1424
1468
  return 'call agenda_consultar_disponibilidade before pre-reserving';
1425
1469
  }
1426
1470
  if (intent === 'pre_reserve') {
1427
1471
  if (agenda.has_availability)
1428
- return 'call agenda_pre_reservar_horario now using one of the known available slots';
1472
+ return 'MUST call agenda_pre_reservar_horario now using one of the known available slots';
1429
1473
  return 'call agenda_consultar_disponibilidade before pre-reserving';
1430
1474
  }
1431
1475
  if (intent === 'check_availability')
@@ -2010,8 +2054,8 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
2010
2054
  sections.push({
2011
2055
  section: 'conversation_frame',
2012
2056
  title: 'Conversation frame',
2013
- value: buildConversationFrame({ query, recentMessages, workingMemory }),
2014
- });
2057
+ value: buildConversationFrame({ query, recentMessages, workingMemory }),
2058
+ });
2015
2059
  const currentTurnFocus = buildCurrentTurnFocus({ query, recentMessages, operationalState, workingMemory });
2016
2060
  if (currentTurnFocus) {
2017
2061
  sections.push({
@@ -2020,6 +2064,14 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
2020
2064
  value: currentTurnFocus,
2021
2065
  });
2022
2066
  }
2067
+ const actionDirective = buildActionDirective({ workingMemory, operationalState });
2068
+ if (actionDirective) {
2069
+ sections.push({
2070
+ section: 'action_directive',
2071
+ title: 'Action directive',
2072
+ value: actionDirective,
2073
+ });
2074
+ }
2023
2075
  if (compactForAgent) {
2024
2076
  if (includeSummary) {
2025
2077
  const summary = compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.summaryMaxFacts || 3));
@@ -3661,6 +3713,8 @@ exports.__private = {
3661
3713
  toolHistoryFromMemory,
3662
3714
  cleanAssistantTranscriptText,
3663
3715
  availabilityFromRecentMessages,
3716
+ preReservationFromRecentMessages,
3717
+ buildActionDirective,
3664
3718
  recentMessageFromMemory,
3665
3719
  previousUserFallbackFromWorkingMemory,
3666
3720
  firstUserMessageFromConversation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "Tembory node for n8n AI Agents with profile, tools, timeline, graph and semantic memory",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tembory.com",