n8n-nodes-tembory 1.0.31 → 1.0.33
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.
|
@@ -295,7 +295,7 @@ const extractProfileFactsFromText = (text, source = 'message', at = nowIso()) =>
|
|
|
295
295
|
const canExtractStrongProfileFacts = sourceName !== 'assistant_message';
|
|
296
296
|
if (!content.trim())
|
|
297
297
|
return facts;
|
|
298
|
-
if (/\[Used tools:|Calling\s+
|
|
298
|
+
if (/\[Used tools:|Calling\s+[A-Za-z_][A-Za-z0-9_.:-]*|"tool"\s*:|"args"\s*:|confirmation_id|reservation_id/i.test(content))
|
|
299
299
|
return facts;
|
|
300
300
|
const email = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i.exec(content);
|
|
301
301
|
if (email && canExtractStrongProfileFacts)
|
|
@@ -407,74 +407,63 @@ const safeParseToolPayload = (value) => {
|
|
|
407
407
|
};
|
|
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
|
-
const
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
if (
|
|
423
|
-
return
|
|
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
|
-
};
|
|
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
|
-
};
|
|
410
|
+
const isToolName = (value = '') => /^[A-Za-z_][A-Za-z0-9_.:-]{1,127}$/.test(String(value || ''));
|
|
411
|
+
const pickRequiredToolFromAction = (text = '') => {
|
|
412
|
+
const next = String(text || '');
|
|
413
|
+
const protectedCall = /\bdo not call\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b[\s\S]*?\bcall\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b/i.exec(next);
|
|
414
|
+
if (protectedCall && isToolName(protectedCall[2]))
|
|
415
|
+
return protectedCall[2];
|
|
416
|
+
if (/\bdo not call\b/i.test(next))
|
|
417
|
+
return '';
|
|
418
|
+
const mustCall = /\bMUST\s+call\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b/i.exec(next);
|
|
419
|
+
if (mustCall && isToolName(mustCall[1]))
|
|
420
|
+
return mustCall[1];
|
|
421
|
+
const call = /\bcall\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b/i.exec(next);
|
|
422
|
+
if (call && isToolName(call[1]))
|
|
423
|
+
return call[1];
|
|
424
|
+
return '';
|
|
448
425
|
};
|
|
449
426
|
const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessages = [], includeResults = true) => {
|
|
450
427
|
const tools = Array.isArray(toolHistory) ? toolHistory : [];
|
|
451
428
|
const successfulTools = tools.filter((tool) => tool.ok !== false);
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
429
|
+
const failedTools = tools.filter((tool) => tool.ok === false);
|
|
430
|
+
const toolCountsByName = {};
|
|
431
|
+
const latestByName = {};
|
|
432
|
+
const failedByName = {};
|
|
433
|
+
const recentSequence = [];
|
|
434
|
+
for (let index = 0; index < tools.length; index += 1) {
|
|
435
|
+
const tool = tools[index];
|
|
436
|
+
const name = String(tool.name || 'unknown_tool');
|
|
437
|
+
const compactInput = compactToolPayload(safeParseToolPayload(tool.input));
|
|
438
|
+
const compactResult = maybeToolResult(tool, includeResults);
|
|
439
|
+
const reason = truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260);
|
|
440
|
+
toolCountsByName[name] = (toolCountsByName[name] || 0) + 1;
|
|
441
|
+
latestByName[name] = {
|
|
442
|
+
name,
|
|
443
|
+
ok: tool.ok !== false,
|
|
444
|
+
at: tool.at || null,
|
|
445
|
+
input: compactInput,
|
|
446
|
+
result: compactResult,
|
|
447
|
+
reason,
|
|
448
|
+
source: tool.source || 'unknown',
|
|
449
|
+
};
|
|
450
|
+
recentSequence.push(cleanContextValue({
|
|
451
|
+
sequence: tool.sequence || index + 1,
|
|
452
|
+
tool: name,
|
|
453
|
+
status: tool.ok === false ? 'failed' : 'ok',
|
|
454
|
+
at: tool.at || null,
|
|
455
|
+
reason,
|
|
456
|
+
input: compactInput,
|
|
457
|
+
output: compactResult,
|
|
458
|
+
}));
|
|
459
|
+
if (tool.ok === false)
|
|
460
|
+
failedByName[name] = (failedByName[name] || 0) + 1;
|
|
461
|
+
}
|
|
460
462
|
const lastTool = tools[tools.length - 1] || null;
|
|
461
|
-
const lastReservation = reservations[reservations.length - 1] || null;
|
|
462
|
-
const lastConfirmation = confirmations[confirmations.length - 1] || null;
|
|
463
|
-
const hasPendingReservation = Boolean((lastReservation && (!lastConfirmation || String(lastReservation.at || '') > String(lastConfirmation.at || ''))) || (recentPreReservation && !lastConfirmation));
|
|
464
463
|
const blockedWithoutContext = [];
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
blockedWithoutContext.push('agenda_confirmar_agendamento');
|
|
469
|
-
const guidance = [];
|
|
470
|
-
if (!hasAvailability)
|
|
471
|
-
guidance.push('No availability result is known for this session; consult availability before reserving or confirming.');
|
|
472
|
-
else if (hasPendingReservation)
|
|
473
|
-
guidance.push('A pre-reservation exists after the latest confirmation; confirmation can use the latest pre-reservation context.');
|
|
474
|
-
else if (lastConfirmation)
|
|
475
|
-
guidance.push('The latest reservation appears confirmed; do not confirm again unless the user explicitly asks to repeat or change it.');
|
|
476
|
-
else if (hasAvailability)
|
|
477
|
-
guidance.push('Availability is known; if the user chooses one listed slot, reserve without repeating availability.');
|
|
464
|
+
const guidance = [
|
|
465
|
+
'Use tool_state and action_ledger as the source of truth for prior tool calls, inputs, outputs, failures and decisions. Domain-specific tool policy must come from the agent prompt, not from memory.',
|
|
466
|
+
];
|
|
478
467
|
return {
|
|
479
468
|
profile_complete: Boolean(profileFacts && profileFacts.name && profileFacts.company && profileFacts.email && profileFacts.phone),
|
|
480
469
|
last_tool: lastTool ? { name: lastTool.name, ok: lastTool.ok, at: lastTool.at } : null,
|
|
@@ -482,20 +471,22 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
482
471
|
total: tools.length,
|
|
483
472
|
ok: successfulTools.length,
|
|
484
473
|
failed: tools.length - successfulTools.length,
|
|
485
|
-
|
|
486
|
-
agenda_pre_reservar_horario: reservations.length,
|
|
487
|
-
agenda_confirmar_agendamento: confirmations.length,
|
|
474
|
+
by_name: toolCountsByName,
|
|
488
475
|
},
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
476
|
+
tool_state: {
|
|
477
|
+
total: tools.length,
|
|
478
|
+
ok: successfulTools.length,
|
|
479
|
+
failed: failedTools.length,
|
|
480
|
+
names: Object.keys(toolCountsByName),
|
|
481
|
+
counts_by_name: toolCountsByName,
|
|
482
|
+
failed_by_name: failedByName,
|
|
483
|
+
latest_by_name: latestByName,
|
|
484
|
+
recent_sequence: pruneByLimit(recentSequence, 20),
|
|
485
|
+
last_successful_tool: successfulTools.length ? {
|
|
486
|
+
name: successfulTools[successfulTools.length - 1].name,
|
|
487
|
+
at: successfulTools[successfulTools.length - 1].at || null,
|
|
488
|
+
result: maybeToolResult(successfulTools[successfulTools.length - 1], includeResults),
|
|
489
|
+
} : null,
|
|
499
490
|
},
|
|
500
491
|
blocked_without_context: Array.from(new Set(blockedWithoutContext)),
|
|
501
492
|
guidance,
|
|
@@ -510,6 +501,7 @@ const deriveActionLedger = (toolHistory = [], maxItems = 20, includeResults = tr
|
|
|
510
501
|
kind: 'tool_call',
|
|
511
502
|
name: tool.name || 'unknown_tool',
|
|
512
503
|
status: tool.ok === false ? 'failed' : 'ok',
|
|
504
|
+
reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260),
|
|
513
505
|
input: compactToolPayload(safeParseToolPayload(tool.input)),
|
|
514
506
|
result: maybeToolResult(tool, includeResults),
|
|
515
507
|
at: tool.at || null,
|
|
@@ -586,6 +578,7 @@ const deriveEntityTimeline = (profileFacts = {}, graph = [], recentMessages = []
|
|
|
586
578
|
};
|
|
587
579
|
const RECENT_MESSAGE_MARKER = '__tembory_recent_message_v1__';
|
|
588
580
|
const TOOL_HISTORY_MARKER = '__tembory_tool_history_v1__';
|
|
581
|
+
const TOOL_LEDGER_MARKER = '__tembory_tool_ledger_v1__';
|
|
589
582
|
const encodeRecentMessage = (recent, threadId) => `${RECENT_MESSAGE_MARKER}${safeStringify({
|
|
590
583
|
role: recent.role || 'user',
|
|
591
584
|
content: recent.content || '',
|
|
@@ -604,6 +597,21 @@ const encodeToolCall = (tool, threadId) => `${TOOL_HISTORY_MARKER}${safeStringif
|
|
|
604
597
|
source: tool.source || 'n8n',
|
|
605
598
|
thread_id: threadId,
|
|
606
599
|
})}`;
|
|
600
|
+
const encodeToolLedger = (tools = [], threadId) => `${TOOL_LEDGER_MARKER}${safeStringify({
|
|
601
|
+
thread_id: threadId,
|
|
602
|
+
generated_at: nowIso(),
|
|
603
|
+
tools: sortToolHistory(tools || []).map((tool, index) => ({
|
|
604
|
+
id: tool.id || tool.callId || tool.call_id || '',
|
|
605
|
+
turn_id: tool.turnId || tool.turn_id || '',
|
|
606
|
+
sequence: tool.sequence || index + 1,
|
|
607
|
+
name: tool.name || tool.tool || tool.toolName || '',
|
|
608
|
+
input: tool.input || '',
|
|
609
|
+
ok: tool.ok !== false,
|
|
610
|
+
result: tool.result || '',
|
|
611
|
+
at: tool.at || nowIso(),
|
|
612
|
+
source: tool.source || 'n8n',
|
|
613
|
+
})),
|
|
614
|
+
})}`;
|
|
607
615
|
const parseRecentMessageMarker = (text) => {
|
|
608
616
|
if (!text || typeof text !== 'string' || !text.startsWith(RECENT_MESSAGE_MARKER))
|
|
609
617
|
return null;
|
|
@@ -644,6 +652,30 @@ const parseToolHistoryMarker = (text) => {
|
|
|
644
652
|
return null;
|
|
645
653
|
}
|
|
646
654
|
};
|
|
655
|
+
const parseToolLedgerMarker = (text) => {
|
|
656
|
+
if (!text || typeof text !== 'string' || !text.startsWith(TOOL_LEDGER_MARKER))
|
|
657
|
+
return [];
|
|
658
|
+
try {
|
|
659
|
+
const parsed = JSON.parse(text.slice(TOOL_LEDGER_MARKER.length));
|
|
660
|
+
const tools = Array.isArray(parsed?.tools) ? parsed.tools : [];
|
|
661
|
+
return tools
|
|
662
|
+
.filter((tool) => tool && tool.name)
|
|
663
|
+
.map((tool, index) => ({
|
|
664
|
+
id: tool.id || tool.call_id || tool.callId || '',
|
|
665
|
+
turnId: tool.turn_id || tool.turnId || '',
|
|
666
|
+
sequence: tool.sequence || index + 1,
|
|
667
|
+
name: String(tool.name),
|
|
668
|
+
input: tool.input === undefined ? '' : String(tool.input),
|
|
669
|
+
ok: tool.ok !== false,
|
|
670
|
+
result: truncate(tool.result || '', 1000),
|
|
671
|
+
at: tool.at || parsed.generated_at || nowIso(),
|
|
672
|
+
source: tool.source || 'tool_ledger_marker',
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
678
|
+
};
|
|
647
679
|
const recentMessageFromMemory = (item) => {
|
|
648
680
|
const meta = metadataOf(item);
|
|
649
681
|
const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
|
|
@@ -686,7 +718,7 @@ const toolHistoryFromMemory = (item) => {
|
|
|
686
718
|
source: 'semantic_fact',
|
|
687
719
|
};
|
|
688
720
|
}
|
|
689
|
-
const namedTool = meta.kind === 'tool_history' ? /(?:tool|ferramenta)\s+([A-Za-
|
|
721
|
+
const namedTool = meta.kind === 'tool_history' ? /(?:tool|ferramenta)\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})/i.exec(String(content || '')) : null;
|
|
690
722
|
if (namedTool) {
|
|
691
723
|
return {
|
|
692
724
|
id: meta.id || meta.call_id || meta.callId || '',
|
|
@@ -699,26 +731,6 @@ const toolHistoryFromMemory = (item) => {
|
|
|
699
731
|
source: 'semantic_named_tool',
|
|
700
732
|
};
|
|
701
733
|
}
|
|
702
|
-
const text = meta.kind === 'tool_history' ? String(content || '').toLowerCase() : '';
|
|
703
|
-
const inferredAgendaTool = text.includes('confirm') && text.includes('agendamento')
|
|
704
|
-
? 'agenda_confirmar_agendamento'
|
|
705
|
-
: text.includes('pré-reserva') || text.includes('pre-reserva') || text.includes('pre reserva') || text.includes('pré reserva')
|
|
706
|
-
? 'agenda_pre_reservar_horario'
|
|
707
|
-
: (text.includes('horários disponíveis') || text.includes('horarios disponiveis') || text.includes('available') || text.includes('disponibilidade')) && text.includes('agenda')
|
|
708
|
-
? 'agenda_consultar_disponibilidade'
|
|
709
|
-
: '';
|
|
710
|
-
if (inferredAgendaTool) {
|
|
711
|
-
return {
|
|
712
|
-
id: meta.id || meta.call_id || meta.callId || '',
|
|
713
|
-
turnId: meta.turn_id || meta.turnId || '',
|
|
714
|
-
name: inferredAgendaTool,
|
|
715
|
-
input: '',
|
|
716
|
-
ok: true,
|
|
717
|
-
result: truncate(content || '', 1000),
|
|
718
|
-
at: meta.at || item.created_at || item.createdAt || nowIso(),
|
|
719
|
-
source: 'semantic_inference',
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
734
|
if (meta.kind !== 'tool_history')
|
|
723
735
|
return null;
|
|
724
736
|
const name = meta.name || meta.tool || meta.toolName;
|
|
@@ -738,6 +750,9 @@ const toolHistoryFromMemory = (item) => {
|
|
|
738
750
|
const explicitToolHistoryItemsFromMemory = (item) => {
|
|
739
751
|
const meta = metadataOf(item);
|
|
740
752
|
const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
|
|
753
|
+
const ledger = parseToolLedgerMarker(content);
|
|
754
|
+
if (ledger.length)
|
|
755
|
+
return ledger;
|
|
741
756
|
const marked = parseToolHistoryMarker(content);
|
|
742
757
|
if (marked)
|
|
743
758
|
return [marked];
|
|
@@ -1268,17 +1283,18 @@ const normalizeIntentText = (value = '') => String(value || '')
|
|
|
1268
1283
|
.toLowerCase()
|
|
1269
1284
|
.normalize('NFD')
|
|
1270
1285
|
.replace(/[\u0300-\u036f]/g, '');
|
|
1271
|
-
const
|
|
1286
|
+
const hasCommitIntent = (value = '') => /\b(confirm\w*|confim\w*|cnfirm\w*|cofnirm\w*|ocnfi\w*|ocnfia\w*|fechar|aprovar|finalizar|prosseguir|continuar|sim)\b/.test(normalizeIntentText(value));
|
|
1272
1287
|
const isConversationRecallQuery = (value = '') => {
|
|
1273
1288
|
const text = normalizeIntentText(value);
|
|
1274
1289
|
return /\b(minha|qual|quais|o que|oq|voce|vc|lembra|lembrar|sabe|diga|fala)\b.{0,80}\b(ultima|ultimas|msg|msgs|mensagem|mensagens|pergunta|perguntei|falei|disse)\b/.test(text)
|
|
1275
1290
|
|| /\b(o que|oq)\b.{0,30}\b(eu)\b.{0,30}\b(falei|disse|perguntei|mandei)\b/.test(text)
|
|
1276
1291
|
|| /\b(nao|nao)\b.{0,20}\b(lembra|sabe)\b.{0,60}\b(msg|msgs|mensagem|pergunta|falei|disse|perguntei)\b/.test(text);
|
|
1277
1292
|
};
|
|
1278
|
-
const
|
|
1293
|
+
const isOperationalStatusQuery = (value = '') => {
|
|
1279
1294
|
const text = normalizeIntentText(value);
|
|
1280
|
-
return /\b(
|
|
1281
|
-
|| /\b(
|
|
1295
|
+
return /\b(quais?|qual|lista|mostra|resume|status|estado|historico|hist[oó]rico|outputs?|retornos?|resultado|resultados?)\b.{0,80}\b(tools?|ferramentas?|apis?|a[cç][oõ]es?|chamadas?|executadas?|usadas?)\b/.test(text)
|
|
1296
|
+
|| /\b(tools?|ferramentas?|apis?|a[cç][oõ]es?|chamadas?|executadas?|usadas?)\b.{0,80}\b(quais?|qual|lista|mostra|resume|status|estado|historico|hist[oó]rico|outputs?|retornos?|resultado|resultados?)\b/.test(text)
|
|
1297
|
+
|| /\b(ja|ainda|tinha|tenho|foi|esta|ta)\b.{0,60}\b(feito|feita|executad\w*|chamad\w*|concluid\w*|confirm\w*|criad\w*|atualizad\w*|agend\w*|agned\w*|ganed\w*|marc\w*|reserv\w*)\b/.test(text);
|
|
1282
1298
|
};
|
|
1283
1299
|
const recentUserMessages = (recentMessages = []) => [...(recentMessages || [])]
|
|
1284
1300
|
.filter((msg) => /^(user|human)$/i.test(String(msg.role || '')))
|
|
@@ -1336,8 +1352,8 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
|
|
|
1336
1352
|
};
|
|
1337
1353
|
const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalState = {}, workingMemory = {} }) => {
|
|
1338
1354
|
const recall = isConversationRecallQuery(query);
|
|
1339
|
-
const
|
|
1340
|
-
if (!recall && !
|
|
1355
|
+
const operationalStatus = isOperationalStatusQuery(query);
|
|
1356
|
+
if (!recall && !operationalStatus)
|
|
1341
1357
|
return null;
|
|
1342
1358
|
const previousUser = previousUserMessageForQuery(query, recentMessages);
|
|
1343
1359
|
const firstUser = firstUserMessageFromConversation(recentMessages);
|
|
@@ -1345,31 +1361,22 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
|
|
|
1345
1361
|
.filter((msg) => normalizeIntentText(msg.content).trim() !== normalizeIntentText(query).trim())
|
|
1346
1362
|
.slice(0, 3)
|
|
1347
1363
|
.map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at }));
|
|
1348
|
-
const
|
|
1364
|
+
const toolState = (operationalState || {}).tool_state || {};
|
|
1349
1365
|
const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
|
|
1350
1366
|
const focus = cleanContextValue({
|
|
1351
1367
|
current_user_request: truncate(query, 500),
|
|
1352
|
-
intent: recall ? 'conversation_recall' : '
|
|
1368
|
+
intent: recall ? 'conversation_recall' : 'tool_or_status_question',
|
|
1353
1369
|
first_user_message: firstUser ? truncate(firstUser.content, 500) : null,
|
|
1354
1370
|
previous_user_message: previousUserMessage,
|
|
1355
1371
|
recent_user_messages: users,
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
? 'pending_pre_reservation'
|
|
1361
|
-
: agenda.has_pre_reservation
|
|
1362
|
-
? 'pre_reserved'
|
|
1363
|
-
: agenda.has_availability
|
|
1364
|
-
? 'availability_known_not_scheduled'
|
|
1365
|
-
: 'none',
|
|
1366
|
-
has_confirmation: Boolean(agenda.has_confirmation && !agenda.has_pending_pre_reservation),
|
|
1367
|
-
has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
|
|
1368
|
-
has_availability: Boolean(agenda.has_availability),
|
|
1372
|
+
tool_status: operationalStatus ? {
|
|
1373
|
+
executed_tools: toolState.names || [],
|
|
1374
|
+
last_successful_tool: toolState.last_successful_tool || null,
|
|
1375
|
+
failed_by_name: toolState.failed_by_name || {},
|
|
1369
1376
|
} : undefined,
|
|
1370
1377
|
instruction: recall
|
|
1371
|
-
? 'Answer the user meta-question from previous_user_message/recent_user_messages and the chronological transcript. Do not
|
|
1372
|
-
: 'Answer
|
|
1378
|
+
? 'Answer the user meta-question from previous_user_message/recent_user_messages and the chronological transcript. Do not call tools for recall-only questions.'
|
|
1379
|
+
: 'Answer status questions from tool_state, tool_history and action_ledger. Do not call tools unless the agent prompt requires it.',
|
|
1373
1380
|
});
|
|
1374
1381
|
if (!previousUserMessage)
|
|
1375
1382
|
focus.previous_user_message = null;
|
|
@@ -1377,47 +1384,29 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
|
|
|
1377
1384
|
};
|
|
1378
1385
|
const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) => {
|
|
1379
1386
|
const next = String((workingMemory || {}).next_expected_action || '');
|
|
1380
|
-
const
|
|
1381
|
-
if (!
|
|
1387
|
+
const tool = pickRequiredToolFromAction(next);
|
|
1388
|
+
if (!tool || !/\bcall\b|\bMUST\b/i.test(next))
|
|
1382
1389
|
return null;
|
|
1383
|
-
const
|
|
1384
|
-
const
|
|
1390
|
+
const toolState = (operationalState || {}).tool_state || {};
|
|
1391
|
+
const latestForTool = ((toolState.latest_by_name || {})[tool]) || null;
|
|
1385
1392
|
return cleanContextValue({
|
|
1386
1393
|
required_tool: tool,
|
|
1387
1394
|
next_expected_action: next,
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
has_confirmation: Boolean(agenda.has_confirmation),
|
|
1395
|
+
tool_state: {
|
|
1396
|
+
last_tool: (operationalState || {}).last_tool || null,
|
|
1397
|
+
required_tool_last_result: latestForTool,
|
|
1398
|
+
counts_by_name: toolState.counts_by_name || {},
|
|
1399
|
+
failed_by_name: toolState.failed_by_name || {},
|
|
1394
1400
|
},
|
|
1395
|
-
instruction: `
|
|
1401
|
+
instruction: `If the agent prompt requires this action, call ${tool} now using tool_state/action_ledger as evidence. Do not infer domain policy from memory; memory only provides prior conversation, decisions, inputs and outputs.`,
|
|
1396
1402
|
});
|
|
1397
1403
|
};
|
|
1398
1404
|
const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
const hasPreReservationTool = names.has('agenda_pre_reservar_horario');
|
|
1405
|
-
const hasAnyContext = names.size > 0 || hasRecentContext || hasVectorContext;
|
|
1406
|
-
const blocked = [];
|
|
1407
|
-
const reasons = [];
|
|
1408
|
-
if (!hasAnyContext) {
|
|
1409
|
-
blocked.push('agenda_pre_reservar_horario', 'agenda_confirmar_agendamento');
|
|
1410
|
-
reasons.push('no prior availability or pre-reservation context was found for this session');
|
|
1411
|
-
}
|
|
1412
|
-
if (hasConfirmIntent(text) && !hasPreReservationTool) {
|
|
1413
|
-
blocked.push('agenda_confirmar_agendamento');
|
|
1414
|
-
reasons.push('confirmation requested but no structured agenda_pre_reservar_horario tool result was found; assistant text or vector memories are not enough to confirm');
|
|
1415
|
-
}
|
|
1416
|
-
if ((/pre.?reserv|pre.?reserv|reservar/.test(text)) && !hasAvailabilityTool) {
|
|
1417
|
-
blocked.push('agenda_pre_reservar_horario');
|
|
1418
|
-
reasons.push('reservation requested but no structured agenda_consultar_disponibilidade tool result was found');
|
|
1419
|
-
}
|
|
1420
|
-
return { blockedTools: Array.from(new Set(blocked)), reasons };
|
|
1405
|
+
return {
|
|
1406
|
+
blockedTools: [],
|
|
1407
|
+
reasons: [],
|
|
1408
|
+
instruction: 'Memory does not block domain tools by hardcoded rules. The agent prompt decides which tool to call; use tool_state/tool_history/action_ledger to verify prior inputs, outputs and failures.',
|
|
1409
|
+
};
|
|
1421
1410
|
};
|
|
1422
1411
|
const inferUserIntent = (query = '', recentMessages = []) => {
|
|
1423
1412
|
const latestUser = [...(recentMessages || [])].reverse().find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
@@ -1426,20 +1415,14 @@ const inferUserIntent = (query = '', recentMessages = []) => {
|
|
|
1426
1415
|
const normalizedQuery = normalizeIntentText(query).trim();
|
|
1427
1416
|
if (isConversationRecallQuery(query))
|
|
1428
1417
|
return 'conversation_recall';
|
|
1429
|
-
if (
|
|
1430
|
-
return '
|
|
1418
|
+
if (isOperationalStatusQuery(query))
|
|
1419
|
+
return 'operational_status_question';
|
|
1431
1420
|
if (/^(ok|sim|pode|pode sim|isso|isso mesmo|confirmo|confirmar)$/.test(normalizedQuery))
|
|
1432
1421
|
return 'affirm';
|
|
1433
|
-
if (
|
|
1434
|
-
return '
|
|
1435
|
-
if (
|
|
1436
|
-
return '
|
|
1437
|
-
if (/\b(pre.?reserv|pre.?reserv|reservar|segurar|marcar)\b/.test(text))
|
|
1438
|
-
return 'pre_reserve';
|
|
1439
|
-
if (/\b(disponibilidade|horarios?|agenda(?:r|mento)?|quando pode|tem vaga|quero agendar|gostaria de agendar|preciso agendar)\b/.test(text))
|
|
1440
|
-
return 'check_availability';
|
|
1441
|
-
if (/\b(cancel|desmarcar|remarcar|alterar|mudar)\b/.test(text))
|
|
1442
|
-
return 'change_or_cancel';
|
|
1422
|
+
if (hasCommitIntent(text))
|
|
1423
|
+
return 'commit_or_continue';
|
|
1424
|
+
if (/\b(buscar|busca|criar|cria|atualizar|atualiza|consultar|consulta|reservar|reserva|agendar|agenda|abrir|abre|cancelar|cancela|enviar|envia|gerar|gera|validar|valida|processar|processa|executar|executa)\b/.test(text))
|
|
1425
|
+
return 'tool_action_candidate';
|
|
1443
1426
|
if (/\b(meu nome|email|telefone|empresa|prefiro|preferencia)\b/.test(text))
|
|
1444
1427
|
return 'profile_update';
|
|
1445
1428
|
if (text.trim())
|
|
@@ -1447,41 +1430,14 @@ const inferUserIntent = (query = '', recentMessages = []) => {
|
|
|
1447
1430
|
return 'unknown';
|
|
1448
1431
|
};
|
|
1449
1432
|
const deriveNextExpectedAction = (intent, operationalState = {}) => {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
if (agenda.has_pending_pre_reservation)
|
|
1453
|
-
return 'MUST call agenda_confirmar_agendamento now; do not ask the same confirmation question again';
|
|
1454
|
-
if (agenda.has_availability)
|
|
1455
|
-
return 'MUST call agenda_pre_reservar_horario now using the selected or latest discussed slot; do not ask again';
|
|
1456
|
-
return 'call agenda_consultar_disponibilidade before reserving';
|
|
1457
|
-
}
|
|
1458
|
-
if (intent === 'confirm') {
|
|
1459
|
-
if (agenda.has_pending_pre_reservation)
|
|
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';
|
|
1464
|
-
}
|
|
1465
|
-
if (intent === 'select_slot') {
|
|
1466
|
-
if (agenda.has_availability)
|
|
1467
|
-
return 'MUST call agenda_pre_reservar_horario now using the selected slot; do not ask for confirmation before pre-reserving';
|
|
1468
|
-
return 'call agenda_consultar_disponibilidade before pre-reserving';
|
|
1469
|
-
}
|
|
1470
|
-
if (intent === 'pre_reserve') {
|
|
1471
|
-
if (agenda.has_availability)
|
|
1472
|
-
return 'MUST call agenda_pre_reservar_horario now using one of the known available slots';
|
|
1473
|
-
return 'call agenda_consultar_disponibilidade before pre-reserving';
|
|
1474
|
-
}
|
|
1475
|
-
if (intent === 'check_availability')
|
|
1476
|
-
return agenda.has_availability ? 'reuse known availability unless the user asks to refresh' : 'call agenda_consultar_disponibilidade';
|
|
1477
|
-
if (intent === 'change_or_cancel')
|
|
1478
|
-
return 'inspect current reservation state before changing or cancelling';
|
|
1433
|
+
if (['affirm', 'commit_or_continue', 'tool_action_candidate'].includes(intent))
|
|
1434
|
+
return 'continue according to the agent prompt using conversation_frame, tool_state, tool_history and action_ledger; do not apply domain-specific memory rules';
|
|
1479
1435
|
if (intent === 'profile_update')
|
|
1480
1436
|
return 'save stable profile facts and continue the conversation';
|
|
1481
1437
|
if (intent === 'conversation_recall')
|
|
1482
|
-
return 'answer directly using
|
|
1483
|
-
if (intent === '
|
|
1484
|
-
return 'answer
|
|
1438
|
+
return 'answer directly using previous_user_message and conversation_history_chronological; do not call tools for recall-only questions';
|
|
1439
|
+
if (intent === 'operational_status_question')
|
|
1440
|
+
return 'answer status questions from tool_state, tool_history and action_ledger; do not call tools unless the agent prompt requires it';
|
|
1485
1441
|
return 'answer using retrieved context and avoid unnecessary tool calls';
|
|
1486
1442
|
};
|
|
1487
1443
|
const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [], toolHistory = [], operationalState = {}, previous = {} }) => {
|
|
@@ -1506,11 +1462,11 @@ const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [
|
|
|
1506
1462
|
};
|
|
1507
1463
|
};
|
|
1508
1464
|
const deriveDecisionState = ({ query = '', toolHistory = [], operationalState = {}, workingMemory = {} }) => {
|
|
1509
|
-
const agenda = operationalState.agenda_state || {};
|
|
1510
1465
|
const decisions = [];
|
|
1511
1466
|
const doNotRepeatTools = [];
|
|
1512
1467
|
const intent = workingMemory.last_user_intent || inferUserIntent(query, []);
|
|
1513
1468
|
const now = nowIso();
|
|
1469
|
+
const toolState = operationalState.tool_state || {};
|
|
1514
1470
|
const pushDecision = (id, decision, reason, appliesTo, extra = {}) => {
|
|
1515
1471
|
decisions.push({
|
|
1516
1472
|
id,
|
|
@@ -1523,41 +1479,25 @@ const deriveDecisionState = ({ query = '', toolHistory = [], operationalState =
|
|
|
1523
1479
|
created_at: extra.created_at || now,
|
|
1524
1480
|
valid_until: extra.valid_until || null,
|
|
1525
1481
|
superseded_by: extra.superseded_by || null,
|
|
1482
|
+
tool: extra.tool,
|
|
1526
1483
|
});
|
|
1527
1484
|
};
|
|
1528
|
-
|
|
1529
|
-
doNotRepeatTools.push(
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
pushDecision(
|
|
1535
|
-
}
|
|
1536
|
-
if (agenda.has_confirmation && !agenda.has_pending_pre_reservation) {
|
|
1537
|
-
doNotRepeatTools.push('agenda_confirmar_agendamento');
|
|
1538
|
-
pushDecision('agenda_confirmation_done', 'do not confirm again unless the user explicitly asks to repeat or change it', 'latest reservation appears confirmed', 'agenda_flow', { confidence: 0.9 });
|
|
1539
|
-
}
|
|
1540
|
-
const reservationStatus = agenda.has_confirmation && !agenda.has_pending_pre_reservation
|
|
1541
|
-
? 'confirmed'
|
|
1542
|
-
: agenda.has_pending_pre_reservation
|
|
1543
|
-
? 'pending_pre_reservation'
|
|
1544
|
-
: agenda.has_pre_reservation
|
|
1545
|
-
? 'pre_reserved'
|
|
1546
|
-
: agenda.has_availability
|
|
1547
|
-
? 'availability_known'
|
|
1548
|
-
: 'none';
|
|
1485
|
+
for (const name of Object.keys(toolState.counts_by_name || {})) {
|
|
1486
|
+
doNotRepeatTools.push(name);
|
|
1487
|
+
}
|
|
1488
|
+
if (toolState.last_successful_tool)
|
|
1489
|
+
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 });
|
|
1490
|
+
for (const [name, count] of Object.entries(toolState.failed_by_name || {}))
|
|
1491
|
+
pushDecision(`tool_failed_${name}`, `${name} failed ${count} time(s); inspect tool output before retrying`, 'tool_state records failed tool execution', 'tool_orchestration', { confidence: 0.86, tool: name });
|
|
1549
1492
|
return {
|
|
1550
1493
|
active_decisions: decisions,
|
|
1551
1494
|
do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
|
|
1552
1495
|
current_intent: intent,
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
reschedule_requested: intent === 'change_or_cancel' && Boolean(agenda.has_confirmation || agenda.has_pre_reservation),
|
|
1559
|
-
cancel_requested: /\b(cancelar|cancele|desmarcar|remover)\b/i.test(String(query || '')),
|
|
1560
|
-
reservation_status: reservationStatus,
|
|
1496
|
+
tool_decision_state: {
|
|
1497
|
+
executed_tools: Object.keys(toolState.counts_by_name || {}),
|
|
1498
|
+
failed_tools: Object.keys(toolState.failed_by_name || {}),
|
|
1499
|
+
last_successful_tool: toolState.last_successful_tool || null,
|
|
1500
|
+
do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
|
|
1561
1501
|
evaluated_at: now,
|
|
1562
1502
|
},
|
|
1563
1503
|
conflict_policy: 'prefer active decisions with newer timestamps; ignore superseded or expired memories unless auditing',
|
|
@@ -1608,7 +1548,12 @@ const deriveMemoryCompression = ({ recentMessages = [], toolHistory = [], profil
|
|
|
1608
1548
|
session_summary: {
|
|
1609
1549
|
messages: lastMessages.length,
|
|
1610
1550
|
tools: lastTools,
|
|
1611
|
-
|
|
1551
|
+
tool_state: (operationalState || {}).tool_state ? {
|
|
1552
|
+
names: ((operationalState.tool_state || {}).names || []).slice(0, maxItems),
|
|
1553
|
+
counts_by_name: (operationalState.tool_state || {}).counts_by_name || {},
|
|
1554
|
+
failed_by_name: (operationalState.tool_state || {}).failed_by_name || {},
|
|
1555
|
+
last_successful_tool: (operationalState.tool_state || {}).last_successful_tool || null,
|
|
1556
|
+
} : {},
|
|
1612
1557
|
},
|
|
1613
1558
|
entity_summary: profile,
|
|
1614
1559
|
workflow_summary: {
|
|
@@ -1654,7 +1599,6 @@ const deriveContextHealth = ({ userId = '', project = '', vectorMemories = [], r
|
|
|
1654
1599
|
score += weights[key] || 0;
|
|
1655
1600
|
}
|
|
1656
1601
|
const missing = Object.entries(checks).filter(([, ok]) => !ok).map(([key]) => key);
|
|
1657
|
-
const agenda = operationalState.agenda_state || {};
|
|
1658
1602
|
return {
|
|
1659
1603
|
kind: 'tembory.context_health.v1',
|
|
1660
1604
|
namespace: userId || null,
|
|
@@ -1669,11 +1613,10 @@ const deriveContextHealth = ({ userId = '', project = '', vectorMemories = [], r
|
|
|
1669
1613
|
tool_history: Array.isArray(toolHistory) ? toolHistory.length : 0,
|
|
1670
1614
|
active_decisions: Array.isArray(decisionState.active_decisions) ? decisionState.active_decisions.length : 0,
|
|
1671
1615
|
},
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
|
|
1616
|
+
tool_state: {
|
|
1617
|
+
names: (((operationalState || {}).tool_state || {}).names || []).slice(0, 12),
|
|
1618
|
+
last_successful_tool: (((operationalState || {}).tool_state || {}).last_successful_tool) || null,
|
|
1619
|
+
failed_by_name: (((operationalState || {}).tool_state || {}).failed_by_name) || {},
|
|
1677
1620
|
},
|
|
1678
1621
|
generated_at: nowIso(),
|
|
1679
1622
|
};
|
|
@@ -1684,13 +1627,13 @@ const contextMemoryText = (memory, max = 700) => {
|
|
|
1684
1627
|
if (tools.length) {
|
|
1685
1628
|
const names = tools.map((tool) => `${tool.name || 'tool'}:${tool.ok === false ? 'failed' : 'ok'}`).join(', ');
|
|
1686
1629
|
const ids = tools.map((tool) => tool.result || tool.input || '').join(' ');
|
|
1687
|
-
const idMatch = /(
|
|
1630
|
+
const idMatch = /([A-Za-z_][A-Za-z0-9_.:-]*id)["':\s]+([A-Za-z0-9_.:-]+)/i.exec(ids);
|
|
1688
1631
|
return truncate(`[tool_events_extracted] ${names}${idMatch ? ` ${idMatch[1]}=${idMatch[2]}` : ''}. See Tool history for structured details.`, max);
|
|
1689
1632
|
}
|
|
1690
1633
|
return truncate(text, max);
|
|
1691
1634
|
};
|
|
1692
1635
|
const approxTokenCount = (text) => Math.ceil(String(text || '').length / 4);
|
|
1693
|
-
const importantJsonFields = ['
|
|
1636
|
+
const importantJsonFields = ['id', 'status', 'state', 'next_step', 'action', 'intent', 'tool', 'input', 'output', 'result', 'error', 'message', 'reason', 'customer_id', 'user_id', 'lead_id', 'ticket_id', 'charge_id'];
|
|
1694
1637
|
const pickImportantFields = (value) => {
|
|
1695
1638
|
if (value === null || value === undefined)
|
|
1696
1639
|
return {};
|
|
@@ -1720,9 +1663,7 @@ const compactToolResult = (result, max = 360) => {
|
|
|
1720
1663
|
return undefined;
|
|
1721
1664
|
try {
|
|
1722
1665
|
const parsed = JSON.parse(text);
|
|
1723
|
-
|
|
1724
|
-
if (Object.keys(picked).length)
|
|
1725
|
-
return truncate(safeStringify(picked), max);
|
|
1666
|
+
return truncate(safeStringify(parsed), max);
|
|
1726
1667
|
}
|
|
1727
1668
|
catch { }
|
|
1728
1669
|
const picked = {};
|
|
@@ -1741,8 +1682,9 @@ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResul
|
|
|
1741
1682
|
name: tool.name,
|
|
1742
1683
|
status: tool.ok === false ? 'failed' : 'ok',
|
|
1743
1684
|
at: tool.at,
|
|
1744
|
-
|
|
1745
|
-
|
|
1685
|
+
reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 140),
|
|
1686
|
+
input: truncate(String(tool.input || ''), 140) || undefined,
|
|
1687
|
+
result: includeResults ? compactToolResult(tool.result, 240) : undefined,
|
|
1746
1688
|
}));
|
|
1747
1689
|
const cleanContextValue = (value) => {
|
|
1748
1690
|
if (Array.isArray(value)) {
|
|
@@ -1786,7 +1728,7 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
|
|
|
1786
1728
|
at: decision.at || decision.updated_at,
|
|
1787
1729
|
})),
|
|
1788
1730
|
do_not_repeat_tools: state.do_not_repeat_tools,
|
|
1789
|
-
|
|
1731
|
+
tool_decision_state: state.tool_decision_state,
|
|
1790
1732
|
latest_tool: state.latest_tool ? cleanContextValue({
|
|
1791
1733
|
name: state.latest_tool.name,
|
|
1792
1734
|
status: state.latest_tool.status || (state.latest_tool.ok === false ? 'failed' : 'ok'),
|
|
@@ -1795,19 +1737,21 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
|
|
|
1795
1737
|
conflict_policy: state.conflict_policy,
|
|
1796
1738
|
});
|
|
1797
1739
|
const compactOperationalStateForAgent = (state = {}) => {
|
|
1798
|
-
const
|
|
1740
|
+
const counts = state.tool_counts || {};
|
|
1741
|
+
const toolState = state.tool_state || {};
|
|
1799
1742
|
return {
|
|
1800
1743
|
last_tool: state.last_tool || null,
|
|
1801
|
-
tool_counts:
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
has_confirmation: Boolean(agenda.has_confirmation),
|
|
1806
|
-
has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
|
|
1807
|
-
latest_availability: compactToolResult(agenda.latest_availability_result),
|
|
1808
|
-
latest_pre_reservation: compactToolResult(agenda.latest_pre_reservation_result),
|
|
1809
|
-
latest_confirmation: compactToolResult(agenda.latest_confirmation_result),
|
|
1744
|
+
tool_counts: {
|
|
1745
|
+
total: counts.total || 0,
|
|
1746
|
+
ok: counts.ok || 0,
|
|
1747
|
+
failed: counts.failed || 0,
|
|
1810
1748
|
},
|
|
1749
|
+
tool_state: (toolState.names || []).length ? {
|
|
1750
|
+
names: toolState.names,
|
|
1751
|
+
counts_by_name: toolState.counts_by_name || {},
|
|
1752
|
+
failed_by_name: toolState.failed_by_name || {},
|
|
1753
|
+
last_successful_tool: toolState.last_successful_tool || undefined,
|
|
1754
|
+
} : undefined,
|
|
1811
1755
|
blocked_without_context: state.blocked_without_context || [],
|
|
1812
1756
|
guidance: (state.guidance || []).slice(0, 3),
|
|
1813
1757
|
};
|
|
@@ -1815,12 +1759,6 @@ const compactOperationalStateForAgent = (state = {}) => {
|
|
|
1815
1759
|
const compactMemoryCompressionForAgent = (compression = {}) => ({
|
|
1816
1760
|
turn_summary: (compression.turn_summary || []).slice(-3),
|
|
1817
1761
|
session_tools: (((compression.session_summary || {}).tools) || []).slice(-6),
|
|
1818
|
-
agenda_state: ((compression.session_summary || {}).agenda_state) ? {
|
|
1819
|
-
has_availability: Boolean(compression.session_summary.agenda_state.has_availability),
|
|
1820
|
-
has_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pre_reservation),
|
|
1821
|
-
has_confirmation: Boolean(compression.session_summary.agenda_state.has_confirmation),
|
|
1822
|
-
has_pending_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pending_pre_reservation),
|
|
1823
|
-
} : undefined,
|
|
1824
1762
|
active_memory_count: ((compression.workflow_summary || {}).active_memory_count) || 0,
|
|
1825
1763
|
});
|
|
1826
1764
|
const compactActionLedgerForAgent = (ledger = [], maxItems = 6, includeResults = true) => pruneByLimit(ledger || [], maxItems).map((item) => cleanContextValue({
|
|
@@ -1829,7 +1767,9 @@ const compactActionLedgerForAgent = (ledger = [], maxItems = 6, includeResults =
|
|
|
1829
1767
|
tool: item.tool || item.name,
|
|
1830
1768
|
status: item.status,
|
|
1831
1769
|
at: item.at,
|
|
1832
|
-
|
|
1770
|
+
reason: item.reason,
|
|
1771
|
+
input: item.input,
|
|
1772
|
+
result: includeResults ? compactToolResult(item.result, 180) : undefined,
|
|
1833
1773
|
}));
|
|
1834
1774
|
const compactEntityTimelineForAgent = (timeline = [], maxItems = 8) => pruneByLimit(timeline || [], maxItems).map((item) => cleanContextValue({
|
|
1835
1775
|
entity: item.entity || item.source || item.name,
|
|
@@ -2047,8 +1987,8 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
2047
1987
|
section: 'context_header',
|
|
2048
1988
|
title: 'Tembory context',
|
|
2049
1989
|
value: compactForAgent || compactStateSections
|
|
2050
|
-
? 'Read-only memory. Conversation frame is authoritative for
|
|
2051
|
-
: 'Use this context as read-only memory.
|
|
1990
|
+
? 'Read-only operational memory. Conversation frame is authoritative for user/assistant transcript. Tool history/action ledger are authoritative for tool calls, reasons, inputs, outputs, status and timestamps. The agent prompt owns domain policy and decides which tool to call.'
|
|
1991
|
+
: 'Use this context as read-only operational memory. Do not mention internal section names to the user. Conversation frame is authoritative for the chronological user/assistant transcript. Tool history and action ledger are authoritative for what tools were called, why they were called, what input was sent, what output returned, whether they failed, and when they ran. Memory must not invent domain-specific tool rules; the agent prompt owns tool orchestration policy.',
|
|
2052
1992
|
});
|
|
2053
1993
|
}
|
|
2054
1994
|
sections.push({
|
|
@@ -2184,9 +2124,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
2184
2124
|
value: {
|
|
2185
2125
|
blocked_tools: guard.blockedTools,
|
|
2186
2126
|
reasons: guard.reasons,
|
|
2187
|
-
instruction: guard.blockedTools.length
|
|
2127
|
+
instruction: guard.instruction || (guard.blockedTools.length
|
|
2188
2128
|
? `Do not call these tools now: ${guard.blockedTools.join(', ')}. Ask for the missing context or execute the prerequisite step first.`
|
|
2189
|
-
: 'No downstream tool is blocked by missing memory prerequisites.',
|
|
2129
|
+
: 'No downstream tool is blocked by missing memory prerequisites.'),
|
|
2190
2130
|
},
|
|
2191
2131
|
});
|
|
2192
2132
|
sections.push({
|
|
@@ -2915,6 +2855,13 @@ class Mem0Memory {
|
|
|
2915
2855
|
thread_id: threadId,
|
|
2916
2856
|
}, ids));
|
|
2917
2857
|
}
|
|
2858
|
+
clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolLedger(toolHistoryForTurn, threadId), {
|
|
2859
|
+
kind: 'tool_ledger',
|
|
2860
|
+
thread_id: threadId,
|
|
2861
|
+
project: project || undefined,
|
|
2862
|
+
source: 'n8n_connected_embedding',
|
|
2863
|
+
generated_at: nowIso(),
|
|
2864
|
+
}, ids));
|
|
2918
2865
|
}
|
|
2919
2866
|
await saveClientVectorMemories(this, clientMemories, ids);
|
|
2920
2867
|
if (adv.includeRecentMessages !== false && recentForMem0.length) {
|
|
@@ -2968,6 +2915,23 @@ class Mem0Memory {
|
|
|
2968
2915
|
tool: tool.name,
|
|
2969
2916
|
});
|
|
2970
2917
|
}
|
|
2918
|
+
await safePersistLegacyMemory(this, {
|
|
2919
|
+
messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
|
|
2920
|
+
infer: false,
|
|
2921
|
+
user_id: body.user_id,
|
|
2922
|
+
agent_id: body.agent_id,
|
|
2923
|
+
run_id: body.run_id,
|
|
2924
|
+
metadata: {
|
|
2925
|
+
kind: 'tool_ledger',
|
|
2926
|
+
thread_id: threadId,
|
|
2927
|
+
project: project || undefined,
|
|
2928
|
+
source: 'tembory_transcript',
|
|
2929
|
+
generated_at: nowIso(),
|
|
2930
|
+
},
|
|
2931
|
+
}, {
|
|
2932
|
+
kind: 'tool_ledger',
|
|
2933
|
+
user_id: body.user_id,
|
|
2934
|
+
});
|
|
2971
2935
|
}
|
|
2972
2936
|
return;
|
|
2973
2937
|
}
|
|
@@ -3015,6 +2979,20 @@ class Mem0Memory {
|
|
|
3015
2979
|
},
|
|
3016
2980
|
});
|
|
3017
2981
|
}
|
|
2982
|
+
await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', {
|
|
2983
|
+
messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
|
|
2984
|
+
infer: false,
|
|
2985
|
+
user_id: body.user_id,
|
|
2986
|
+
agent_id: body.agent_id,
|
|
2987
|
+
run_id: body.run_id,
|
|
2988
|
+
metadata: {
|
|
2989
|
+
kind: 'tool_ledger',
|
|
2990
|
+
thread_id: threadId,
|
|
2991
|
+
project: project || undefined,
|
|
2992
|
+
source: 'tembory_transcript',
|
|
2993
|
+
generated_at: nowIso(),
|
|
2994
|
+
},
|
|
2995
|
+
});
|
|
3018
2996
|
}
|
|
3019
2997
|
if (adv.persistToolFactsToMem0 && toolCalls.length) {
|
|
3020
2998
|
const facts = toolCalls.map((tool) => `Tool ${tool.name} input=${tool.input}${tool.result ? ` result=${tool.result}` : ''}`).join('\n');
|
|
@@ -3712,8 +3690,6 @@ exports.__private = {
|
|
|
3712
3690
|
explicitToolHistoryItemsFromMemory,
|
|
3713
3691
|
toolHistoryFromMemory,
|
|
3714
3692
|
cleanAssistantTranscriptText,
|
|
3715
|
-
availabilityFromRecentMessages,
|
|
3716
|
-
preReservationFromRecentMessages,
|
|
3717
3693
|
buildActionDirective,
|
|
3718
3694
|
recentMessageFromMemory,
|
|
3719
3695
|
previousUserFallbackFromWorkingMemory,
|
|
@@ -3724,8 +3700,10 @@ exports.__private = {
|
|
|
3724
3700
|
applyToolHistoryWindow,
|
|
3725
3701
|
dedupeRecentMessages,
|
|
3726
3702
|
parseToolHistoryMarker,
|
|
3703
|
+
parseToolLedgerMarker,
|
|
3727
3704
|
parseRecentMessageMarker,
|
|
3728
3705
|
encodeToolCall,
|
|
3706
|
+
encodeToolLedger,
|
|
3729
3707
|
encodeRecentMessage,
|
|
3730
3708
|
userKeyFrom,
|
|
3731
3709
|
applyOperationalPreset,
|