n8n-nodes-tembory 1.0.28 → 1.0.30

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.28`.
5
+ Versao atual: `1.0.30`.
6
+
7
+ ## 1.0.30
8
+
9
+ - Adiciona `Action directive` quando a memoria conclui que uma tool deve ser chamada no turno atual.
10
+ - Torna `next_expected_action` mais coercitivo para agenda: usa `MUST call ...` e proibe respostas como "se quiser/posso" no lugar da tool obrigatoria.
11
+ - Reduz o peso instrucional de `first_user_message`; o foco passa a ser mensagem atual, mensagem anterior e transcript cronologico recente.
12
+
13
+ ## 1.0.29
14
+
15
+ - Considera disponibilidade recente no transcript como contexto operacional valido quando `tool_history` ainda nao foi recuperado no turno.
16
+ - Evita repetir `agenda_consultar_disponibilidade` apos o agente ja ter respondido vagas no historico recente.
17
+ - Direciona selecao de horario como "quero dia 13" para `agenda_pre_reservar_horario` quando ha vagas recentes, mesmo sem marker estruturado no contexto atual.
6
18
 
7
19
  ## 1.0.28
8
20
 
@@ -408,11 +408,32 @@ const safeParseToolPayload = (value) => {
408
408
  const compactToolPayload = (value) => truncate(typeof value === 'string' ? value : safeStringify(value), 900);
409
409
  const maybeToolResult = (tool, includeResults = true) => includeResults === false ? undefined : compactToolPayload(safeParseToolPayload(tool === null || tool === void 0 ? void 0 : tool.result));
410
410
  const containsSelectedSlot = (text = '') => /\b(?:às|as|das|para)?\s*\d{1,2}(?::\d{2}|h\d{0,2})?\b/i.test(String(text || '')) || /\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(String(text || ''));
411
+ const availabilityFromRecentMessages = (recentMessages = []) => {
412
+ const messages = Array.isArray(recentMessages) ? recentMessages : [];
413
+ const candidates = [...messages].reverse().filter((message) => {
414
+ const role = String(message.role || '').toLowerCase();
415
+ const text = String(message.content || message.text || message.memory || '');
416
+ if (role && role !== 'assistant' && role !== 'ai' && role !== 'system')
417
+ return false;
418
+ return /\b(?:tem|tenho|há|ha)\s+vagas?\b|\bop[cç][oõ]es?\s+dispon[ií]veis\b|\bavailable_slots\b/i.test(text)
419
+ && (/\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(text) || /\b\d{1,2}:\d{2}\b/.test(text));
420
+ });
421
+ const latest = candidates[0];
422
+ if (!latest)
423
+ return null;
424
+ return {
425
+ text: truncate(String(latest.content || latest.text || latest.memory || ''), 900),
426
+ at: latest.at || latest.created_at || latest.createdAt || nowIso(),
427
+ source: latest.source || 'recent_message',
428
+ };
429
+ };
411
430
  const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessages = [], includeResults = true) => {
412
431
  const tools = Array.isArray(toolHistory) ? toolHistory : [];
413
432
  const successfulTools = tools.filter((tool) => tool.ok !== false);
414
433
  const byName = (name) => successfulTools.filter((tool) => tool.name === name);
415
434
  const availability = byName('agenda_consultar_disponibilidade');
435
+ const recentAvailability = availability.length ? null : availabilityFromRecentMessages(recentMessages);
436
+ const hasAvailability = availability.length > 0 || Boolean(recentAvailability);
416
437
  const reservations = byName('agenda_pre_reservar_horario');
417
438
  const confirmations = byName('agenda_confirmar_agendamento');
418
439
  const lastTool = tools[tools.length - 1] || null;
@@ -420,18 +441,18 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
420
441
  const lastConfirmation = confirmations[confirmations.length - 1] || null;
421
442
  const hasPendingReservation = Boolean(lastReservation && (!lastConfirmation || String(lastReservation.at || '') > String(lastConfirmation.at || '')));
422
443
  const blockedWithoutContext = [];
423
- if (!availability.length)
444
+ if (!hasAvailability)
424
445
  blockedWithoutContext.push('agenda_pre_reservar_horario');
425
446
  if (!reservations.length)
426
447
  blockedWithoutContext.push('agenda_confirmar_agendamento');
427
448
  const guidance = [];
428
- if (!availability.length)
449
+ if (!hasAvailability)
429
450
  guidance.push('No availability result is known for this session; consult availability before reserving or confirming.');
430
451
  else if (hasPendingReservation)
431
452
  guidance.push('A pre-reservation exists after the latest confirmation; confirmation can use the latest pre-reservation context.');
432
453
  else if (lastConfirmation)
433
454
  guidance.push('The latest reservation appears confirmed; do not confirm again unless the user explicitly asks to repeat or change it.');
434
- else if (availability.length)
455
+ else if (hasAvailability)
435
456
  guidance.push('Availability is known; if the user chooses one listed slot, reserve without repeating availability.');
436
457
  return {
437
458
  profile_complete: Boolean(profileFacts && profileFacts.name && profileFacts.company && profileFacts.email && profileFacts.phone),
@@ -445,11 +466,12 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
445
466
  agenda_confirmar_agendamento: confirmations.length,
446
467
  },
447
468
  agenda_state: {
448
- has_availability: availability.length > 0,
469
+ has_availability: hasAvailability,
449
470
  has_pre_reservation: reservations.length > 0,
450
471
  has_confirmation: confirmations.length > 0,
451
472
  has_pending_pre_reservation: hasPendingReservation,
452
- latest_availability_result: availability.length ? maybeToolResult(availability[availability.length - 1], includeResults) : null,
473
+ latest_availability_result: availability.length ? maybeToolResult(availability[availability.length - 1], includeResults) : (recentAvailability && includeResults !== false ? recentAvailability.text : null),
474
+ latest_availability_source: availability.length ? 'tool_history' : (recentAvailability ? recentAvailability.source : null),
453
475
  latest_pre_reservation_result: lastReservation ? maybeToolResult(lastReservation, includeResults) : null,
454
476
  latest_confirmation_result: lastConfirmation ? maybeToolResult(lastConfirmation, includeResults) : null,
455
477
  },
@@ -1284,7 +1306,7 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
1284
1306
  .filter((msg) => normalizeIntentText(msg.content).trim() !== normalizedQuery)
1285
1307
  .slice(0, 5)
1286
1308
  .map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at })),
1287
- 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.',
1309
+ 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.',
1288
1310
  });
1289
1311
  if (!previousUserMessage)
1290
1312
  frame.previous_user_message = null;
@@ -1324,13 +1346,33 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
1324
1346
  has_availability: Boolean(agenda.has_availability),
1325
1347
  } : undefined,
1326
1348
  instruction: recall
1327
- ? '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.'
1349
+ ? '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.'
1328
1350
  : '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.',
1329
1351
  });
1330
1352
  if (!previousUserMessage)
1331
1353
  focus.previous_user_message = null;
1332
1354
  return focus;
1333
1355
  };
1356
+ const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) => {
1357
+ const next = String((workingMemory || {}).next_expected_action || '');
1358
+ const match = /\bagenda_(consultar_disponibilidade|pre_reservar_horario|confirmar_agendamento)\b/.exec(next);
1359
+ if (!match || !/\bcall\b|\bMUST\b/i.test(next))
1360
+ return null;
1361
+ const tool = match[0];
1362
+ const agenda = (operationalState || {}).agenda_state || {};
1363
+ return cleanContextValue({
1364
+ required_tool: tool,
1365
+ next_expected_action: next,
1366
+ agenda_state: {
1367
+ has_availability: Boolean(agenda.has_availability),
1368
+ latest_availability_source: agenda.latest_availability_source,
1369
+ has_pre_reservation: Boolean(agenda.has_pre_reservation),
1370
+ has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1371
+ has_confirmation: Boolean(agenda.has_confirmation),
1372
+ },
1373
+ 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.`,
1374
+ });
1375
+ };
1334
1376
  const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
1335
1377
  const text = normalizeIntentText(query);
1336
1378
  const names = new Set((toolHistory || []).map((tool) => String(tool.name || '')));
@@ -1386,24 +1428,26 @@ const deriveNextExpectedAction = (intent, operationalState = {}) => {
1386
1428
  const agenda = operationalState.agenda_state || {};
1387
1429
  if (intent === 'affirm') {
1388
1430
  if (agenda.has_pending_pre_reservation)
1389
- return 'call agenda_confirmar_agendamento now; do not ask the same confirmation question again';
1431
+ return 'MUST call agenda_confirmar_agendamento now; do not ask the same confirmation question again';
1390
1432
  if (agenda.has_availability)
1391
- return 'call agenda_pre_reservar_horario now using the selected or latest discussed slot; do not ask again';
1433
+ return 'MUST call agenda_pre_reservar_horario now using the selected or latest discussed slot; do not ask again';
1392
1434
  return 'call agenda_consultar_disponibilidade before reserving';
1393
1435
  }
1394
1436
  if (intent === 'confirm') {
1395
1437
  if (agenda.has_pending_pre_reservation)
1396
- return 'call agenda_confirmar_agendamento now using the latest pre-reservation context; do not ask again';
1397
- 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';
1438
+ return 'MUST call agenda_confirmar_agendamento now using the latest pre-reservation context; do not ask again';
1439
+ if (agenda.has_availability)
1440
+ return 'MUST call agenda_pre_reservar_horario first using the selected or latest discussed slot; do not ask for permission again';
1441
+ return 'do not call agenda_confirmar_agendamento; call agenda_consultar_disponibilidade first because no availability is known';
1398
1442
  }
1399
1443
  if (intent === 'select_slot') {
1400
1444
  if (agenda.has_availability)
1401
- return 'call agenda_pre_reservar_horario now using the selected slot; do not ask for confirmation before pre-reserving';
1445
+ return 'MUST call agenda_pre_reservar_horario now using the selected slot; do not ask for confirmation before pre-reserving';
1402
1446
  return 'call agenda_consultar_disponibilidade before pre-reserving';
1403
1447
  }
1404
1448
  if (intent === 'pre_reserve') {
1405
1449
  if (agenda.has_availability)
1406
- return 'call agenda_pre_reservar_horario now using one of the known available slots';
1450
+ return 'MUST call agenda_pre_reservar_horario now using one of the known available slots';
1407
1451
  return 'call agenda_consultar_disponibilidade before pre-reserving';
1408
1452
  }
1409
1453
  if (intent === 'check_availability')
@@ -1988,8 +2032,8 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1988
2032
  sections.push({
1989
2033
  section: 'conversation_frame',
1990
2034
  title: 'Conversation frame',
1991
- value: buildConversationFrame({ query, recentMessages, workingMemory }),
1992
- });
2035
+ value: buildConversationFrame({ query, recentMessages, workingMemory }),
2036
+ });
1993
2037
  const currentTurnFocus = buildCurrentTurnFocus({ query, recentMessages, operationalState, workingMemory });
1994
2038
  if (currentTurnFocus) {
1995
2039
  sections.push({
@@ -1998,6 +2042,14 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1998
2042
  value: currentTurnFocus,
1999
2043
  });
2000
2044
  }
2045
+ const actionDirective = buildActionDirective({ workingMemory, operationalState });
2046
+ if (actionDirective) {
2047
+ sections.push({
2048
+ section: 'action_directive',
2049
+ title: 'Action directive',
2050
+ value: actionDirective,
2051
+ });
2052
+ }
2001
2053
  if (compactForAgent) {
2002
2054
  if (includeSummary) {
2003
2055
  const summary = compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.summaryMaxFacts || 3));
@@ -3638,6 +3690,8 @@ exports.__private = {
3638
3690
  explicitToolHistoryItemsFromMemory,
3639
3691
  toolHistoryFromMemory,
3640
3692
  cleanAssistantTranscriptText,
3693
+ availabilityFromRecentMessages,
3694
+ buildActionDirective,
3641
3695
  recentMessageFromMemory,
3642
3696
  previousUserFallbackFromWorkingMemory,
3643
3697
  firstUserMessageFromConversation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
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",