n8n-nodes-tembory 1.1.26 → 1.1.27
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.
|
@@ -1205,16 +1205,18 @@ const getMemoryStore = (ctx) => {
|
|
|
1205
1205
|
data.tembory.activeSummary = data.tembory.activeSummary || {};
|
|
1206
1206
|
data.tembory.connectedModelSummaryCache = data.tembory.connectedModelSummaryCache || {};
|
|
1207
1207
|
data.tembory.captureState = data.tembory.captureState || {};
|
|
1208
|
+
data.tembory.memoryDedupe = data.tembory.memoryDedupe || {};
|
|
1208
1209
|
return data.tembory;
|
|
1209
1210
|
}
|
|
1210
1211
|
catch {
|
|
1211
|
-
global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {}, activeSummary: {}, connectedModelSummaryCache: {}, captureState: {} };
|
|
1212
|
+
global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {}, activeSummary: {}, connectedModelSummaryCache: {}, captureState: {}, memoryDedupe: {} };
|
|
1212
1213
|
global.__temboryMemory.workingMemory = global.__temboryMemory.workingMemory || {};
|
|
1213
1214
|
global.__temboryMemory.decisionState = global.__temboryMemory.decisionState || {};
|
|
1214
1215
|
global.__temboryMemory.memoryCompression = global.__temboryMemory.memoryCompression || {};
|
|
1215
1216
|
global.__temboryMemory.activeSummary = global.__temboryMemory.activeSummary || {};
|
|
1216
1217
|
global.__temboryMemory.connectedModelSummaryCache = global.__temboryMemory.connectedModelSummaryCache || {};
|
|
1217
1218
|
global.__temboryMemory.captureState = global.__temboryMemory.captureState || {};
|
|
1219
|
+
global.__temboryMemory.memoryDedupe = global.__temboryMemory.memoryDedupe || {};
|
|
1218
1220
|
return global.__temboryMemory;
|
|
1219
1221
|
}
|
|
1220
1222
|
};
|
|
@@ -1268,6 +1270,8 @@ const mergeRemoteThreadState = (store, key, state) => {
|
|
|
1268
1270
|
store.memoryCompression[key] = state.memoryCompression;
|
|
1269
1271
|
if (state.captureState && typeof state.captureState === 'object')
|
|
1270
1272
|
store.captureState[key] = { ...(store.captureState[key] || {}), ...state.captureState };
|
|
1273
|
+
if (state.memoryDedupe && typeof state.memoryDedupe === 'object')
|
|
1274
|
+
store.memoryDedupe[key] = mergeMemoryDedupeState(store.memoryDedupe[key], state.memoryDedupe);
|
|
1271
1275
|
if (typeof state.activeSummary === 'string' && state.activeSummary)
|
|
1272
1276
|
store.activeSummary[key] = state.activeSummary;
|
|
1273
1277
|
};
|
|
@@ -1632,6 +1636,195 @@ const normalizeIntentText = (value = '') => String(value || '')
|
|
|
1632
1636
|
.toLowerCase()
|
|
1633
1637
|
.normalize('NFD')
|
|
1634
1638
|
.replace(/[\u0300-\u036f]/g, '');
|
|
1639
|
+
const MEMORY_DEDUPE_MAX_KEYS = 600;
|
|
1640
|
+
const LEDGER_MEMORY_KINDS = new Set(['conversation_ledger', 'tool_ledger', 'turn_archive']);
|
|
1641
|
+
const normalizeDedupeText = (value = '') => normalizeIntentText(value).replace(/\s+/g, ' ').trim();
|
|
1642
|
+
const memoryBodyText = (body = {}) => {
|
|
1643
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
1644
|
+
if (messages.length)
|
|
1645
|
+
return messages.map((message) => message.content || message.text || message.message || '').filter(Boolean).join('\n');
|
|
1646
|
+
return body.memory || body.text || body.value || body.content || '';
|
|
1647
|
+
};
|
|
1648
|
+
const parseTurnArchiveText = (text = '') => {
|
|
1649
|
+
const raw = String(text || '');
|
|
1650
|
+
const start = raw.indexOf('{');
|
|
1651
|
+
if (start < 0)
|
|
1652
|
+
return null;
|
|
1653
|
+
try {
|
|
1654
|
+
const parsed = JSON.parse(raw.slice(start));
|
|
1655
|
+
return parsed && parsed.marker === 'tembory_turn_archive_v1' ? parsed : null;
|
|
1656
|
+
}
|
|
1657
|
+
catch {
|
|
1658
|
+
return null;
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
const compactToolForDedupe = (tool = {}, index = 0) => ({
|
|
1662
|
+
id: tool.id || tool.callId || tool.call_id || undefined,
|
|
1663
|
+
sequence: tool.sequence || index + 1,
|
|
1664
|
+
name: tool.name || tool.tool || tool.toolName || '',
|
|
1665
|
+
input: canonicalToolInput(tool.input || tool.toolInput || tool.args || ''),
|
|
1666
|
+
ok: tool.ok !== false && tool.status !== 'failed',
|
|
1667
|
+
result: String(tool.result || tool.output || ''),
|
|
1668
|
+
});
|
|
1669
|
+
const canonicalMemoryPayloadForDedupe = (kind = 'memory', body = {}, diagnostics = {}) => {
|
|
1670
|
+
const meta = body.metadata || {};
|
|
1671
|
+
const text = memoryBodyText(body);
|
|
1672
|
+
if (kind === 'recent_message') {
|
|
1673
|
+
const marker = parseRecentMessageMarker(text);
|
|
1674
|
+
return stableStringify({
|
|
1675
|
+
role: meta.role || marker?.role || 'user',
|
|
1676
|
+
content: normalizeDedupeText(meta.content || marker?.content || text),
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
if (kind === 'tool_history') {
|
|
1680
|
+
const marker = parseToolHistoryMarker(text);
|
|
1681
|
+
const tool = marker || {
|
|
1682
|
+
id: meta.id,
|
|
1683
|
+
turnId: meta.turn_id || meta.turnId,
|
|
1684
|
+
sequence: meta.sequence,
|
|
1685
|
+
name: meta.name || diagnostics.tool,
|
|
1686
|
+
input: meta.input,
|
|
1687
|
+
ok: meta.ok,
|
|
1688
|
+
result: meta.result,
|
|
1689
|
+
};
|
|
1690
|
+
return stableStringify(compactToolForDedupe(tool));
|
|
1691
|
+
}
|
|
1692
|
+
if (kind === 'tool_ledger') {
|
|
1693
|
+
return stableStringify(dedupeToolHistory(parseToolLedgerMarker(text)).map(compactToolForDedupe));
|
|
1694
|
+
}
|
|
1695
|
+
if (kind === 'conversation_ledger') {
|
|
1696
|
+
const messages = parseConversationLedgerMarker(text);
|
|
1697
|
+
return stableStringify((messages || []).map((message) => ({
|
|
1698
|
+
role: message.role || 'user',
|
|
1699
|
+
content: normalizeDedupeText(message.content || ''),
|
|
1700
|
+
})));
|
|
1701
|
+
}
|
|
1702
|
+
if (kind === 'turn_archive') {
|
|
1703
|
+
const archive = parseTurnArchiveText(text);
|
|
1704
|
+
if (archive) {
|
|
1705
|
+
return stableStringify({
|
|
1706
|
+
conversation: (archive.conversation || []).map((message) => ({
|
|
1707
|
+
role: message.role || 'user',
|
|
1708
|
+
content: normalizeDedupeText(message.content || ''),
|
|
1709
|
+
})),
|
|
1710
|
+
tools: (archive.tools || []).map(compactToolForDedupe),
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return normalizeDedupeText(String(text || '')
|
|
1715
|
+
.replace(/"generated_at"\s*:\s*"[^"]+"/g, '"generated_at":"<ts>"')
|
|
1716
|
+
.replace(/"timestamp"\s*:\s*"[^"]+"/g, '"timestamp":"<ts>"'));
|
|
1717
|
+
};
|
|
1718
|
+
const inferMemoryKindFromText = (text = '') => {
|
|
1719
|
+
const raw = String(text || '');
|
|
1720
|
+
if (parseRecentMessageMarker(raw))
|
|
1721
|
+
return 'recent_message';
|
|
1722
|
+
if (parseToolHistoryMarker(raw))
|
|
1723
|
+
return 'tool_history';
|
|
1724
|
+
if (parseToolLedgerMarker(raw).length)
|
|
1725
|
+
return 'tool_ledger';
|
|
1726
|
+
if (parseConversationLedgerMarker(raw).length)
|
|
1727
|
+
return 'conversation_ledger';
|
|
1728
|
+
if (parseTurnArchiveText(raw))
|
|
1729
|
+
return 'turn_archive';
|
|
1730
|
+
return '';
|
|
1731
|
+
};
|
|
1732
|
+
const buildMemoryDedupeCandidate = (body = {}, diagnostics = {}) => {
|
|
1733
|
+
const meta = body.metadata || {};
|
|
1734
|
+
const text = memoryBodyText(body);
|
|
1735
|
+
const kind = String(meta.kind || diagnostics.kind || inferMemoryKindFromText(text) || 'memory');
|
|
1736
|
+
const thread = String(meta.thread_id || diagnostics.thread_id || body.thread_id || '');
|
|
1737
|
+
const project = String(meta.project || diagnostics.project || body.project || '');
|
|
1738
|
+
const slot = [kind, thread, project].filter(Boolean).join(':') || kind;
|
|
1739
|
+
const canonical = canonicalMemoryPayloadForDedupe(kind, body, diagnostics);
|
|
1740
|
+
const key = `${slot}:${stableHash(canonical)}`;
|
|
1741
|
+
return cleanContextValue({
|
|
1742
|
+
key,
|
|
1743
|
+
slot,
|
|
1744
|
+
kind,
|
|
1745
|
+
content_hash: stableHash(canonical),
|
|
1746
|
+
strategy: LEDGER_MEMORY_KINDS.has(kind) ? 'latest-ledger-per-content' : 'exact-canonical-content',
|
|
1747
|
+
});
|
|
1748
|
+
};
|
|
1749
|
+
const mergeMemoryDedupeState = (current = {}, incoming = {}) => {
|
|
1750
|
+
const merged = {
|
|
1751
|
+
seen: { ...((current || {}).seen || {}), ...((incoming || {}).seen || {}) },
|
|
1752
|
+
latestBySlot: { ...((current || {}).latestBySlot || {}), ...((incoming || {}).latestBySlot || {}) },
|
|
1753
|
+
lastSummary: (incoming || {}).lastSummary || (current || {}).lastSummary || undefined,
|
|
1754
|
+
updatedAt: (incoming || {}).updatedAt || (current || {}).updatedAt || undefined,
|
|
1755
|
+
};
|
|
1756
|
+
const entries = Object.entries(merged.seen);
|
|
1757
|
+
if (entries.length > MEMORY_DEDUPE_MAX_KEYS) {
|
|
1758
|
+
entries
|
|
1759
|
+
.sort((a, b) => String(a[1] || '').localeCompare(String(b[1] || '')))
|
|
1760
|
+
.slice(0, entries.length - MEMORY_DEDUPE_MAX_KEYS)
|
|
1761
|
+
.forEach(([key]) => delete merged.seen[key]);
|
|
1762
|
+
}
|
|
1763
|
+
return cleanContextValue(merged);
|
|
1764
|
+
};
|
|
1765
|
+
const ensureMemoryDedupeState = (store, key) => {
|
|
1766
|
+
store.memoryDedupe = store.memoryDedupe || {};
|
|
1767
|
+
store.memoryDedupe[key] = mergeMemoryDedupeState(store.memoryDedupe[key] || {}, {});
|
|
1768
|
+
store.memoryDedupe[key].seen = store.memoryDedupe[key].seen || {};
|
|
1769
|
+
store.memoryDedupe[key].latestBySlot = store.memoryDedupe[key].latestBySlot || {};
|
|
1770
|
+
return store.memoryDedupe[key];
|
|
1771
|
+
};
|
|
1772
|
+
const createDedupeSummary = () => ({
|
|
1773
|
+
enabled: true,
|
|
1774
|
+
write: { candidates: 0, persisted: 0, skipped: 0, byKind: {} },
|
|
1775
|
+
load: {},
|
|
1776
|
+
});
|
|
1777
|
+
const recordDedupeWriteSummary = (summary, candidate, decision) => {
|
|
1778
|
+
if (!summary || !candidate)
|
|
1779
|
+
return;
|
|
1780
|
+
summary.write.candidates += 1;
|
|
1781
|
+
summary.write[decision.persist ? 'persisted' : 'skipped'] += 1;
|
|
1782
|
+
summary.write.byKind[candidate.kind] = summary.write.byKind[candidate.kind] || { candidates: 0, persisted: 0, skipped: 0 };
|
|
1783
|
+
summary.write.byKind[candidate.kind].candidates += 1;
|
|
1784
|
+
summary.write.byKind[candidate.kind][decision.persist ? 'persisted' : 'skipped'] += 1;
|
|
1785
|
+
};
|
|
1786
|
+
const evaluateMemoryDedupeWrite = (state, candidate, at = nowIso()) => {
|
|
1787
|
+
if (!candidate || !candidate.key)
|
|
1788
|
+
return { persist: true, reason: 'no_dedupe_key' };
|
|
1789
|
+
state.seen = state.seen || {};
|
|
1790
|
+
state.latestBySlot = state.latestBySlot || {};
|
|
1791
|
+
if (state.seen[candidate.key])
|
|
1792
|
+
return { persist: false, reason: 'duplicate_memory_key' };
|
|
1793
|
+
state.seen[candidate.key] = at;
|
|
1794
|
+
state.latestBySlot[candidate.slot] = candidate.key;
|
|
1795
|
+
state.updatedAt = at;
|
|
1796
|
+
return { persist: true, reason: 'new_memory_key' };
|
|
1797
|
+
};
|
|
1798
|
+
const dedupeMemoryItemsForLoad = (items = []) => {
|
|
1799
|
+
const list = Array.isArray(items) ? items : [];
|
|
1800
|
+
const seen = new Set();
|
|
1801
|
+
const seenSlots = new Set();
|
|
1802
|
+
const out = [];
|
|
1803
|
+
for (const item of [...list].reverse()) {
|
|
1804
|
+
const candidate = buildMemoryDedupeCandidate({ messages: [{ role: 'system', content: memoryText(item) }], metadata: metadataOf(item) }, {});
|
|
1805
|
+
const slotKey = LEDGER_MEMORY_KINDS.has(candidate.kind) ? candidate.slot : candidate.key;
|
|
1806
|
+
const key = LEDGER_MEMORY_KINDS.has(candidate.kind) ? slotKey : candidate.key;
|
|
1807
|
+
if (LEDGER_MEMORY_KINDS.has(candidate.kind)) {
|
|
1808
|
+
if (seenSlots.has(slotKey))
|
|
1809
|
+
continue;
|
|
1810
|
+
seenSlots.add(slotKey);
|
|
1811
|
+
}
|
|
1812
|
+
else if (seen.has(key)) {
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
seen.add(key);
|
|
1816
|
+
out.push(item);
|
|
1817
|
+
}
|
|
1818
|
+
out.reverse();
|
|
1819
|
+
return {
|
|
1820
|
+
items: out,
|
|
1821
|
+
summary: cleanContextValue({
|
|
1822
|
+
input: list.length,
|
|
1823
|
+
kept: out.length,
|
|
1824
|
+
removed: Math.max(0, list.length - out.length),
|
|
1825
|
+
}),
|
|
1826
|
+
};
|
|
1827
|
+
};
|
|
1635
1828
|
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));
|
|
1636
1829
|
const isConversationRecallQuery = (value = '') => {
|
|
1637
1830
|
const text = normalizeIntentText(value);
|
|
@@ -2287,6 +2480,7 @@ const compactSaveAuditForAgent = (capture = {}) => cleanContextValue({
|
|
|
2287
2480
|
tool_history_after_save: capture.last_save_tool_history_after_save,
|
|
2288
2481
|
thread_state_saved: capture.last_save_thread_state_saved,
|
|
2289
2482
|
backend_memory_persistence: capture.last_save_backend_memory_persistence,
|
|
2483
|
+
dedupe: capture.last_save_dedupe_summary,
|
|
2290
2484
|
},
|
|
2291
2485
|
});
|
|
2292
2486
|
const compactEntityTimelineForAgent = (timeline = [], maxItems = 8) => pruneByLimit(timeline || [], maxItems).map((item) => cleanContextValue({
|
|
@@ -2581,6 +2775,7 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
|
|
|
2581
2775
|
recentHighlights: Array.isArray(parsed.recentHighlights) ? parsed.recentHighlights.length : undefined,
|
|
2582
2776
|
});
|
|
2583
2777
|
summary.lastSave = memoryAudit.last_save || parsed.state?.last_save || undefined;
|
|
2778
|
+
summary.dedupeSummary = parsed.dedupeSummary || parsed.diagnostics?.dedupeSummary || undefined;
|
|
2584
2779
|
summary.loadedSections = cleanContextValue({
|
|
2585
2780
|
conversation: Boolean(parsed.conversation),
|
|
2586
2781
|
summary: Boolean(parsed.summary),
|
|
@@ -2856,6 +3051,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
2856
3051
|
profile: sectionValue('profile_facts'),
|
|
2857
3052
|
tools: compactToolLedger,
|
|
2858
3053
|
memoryAudit,
|
|
3054
|
+
dedupeSummary: diagnostics?.dedupeSummary,
|
|
2859
3055
|
});
|
|
2860
3056
|
const renderCompactSection = (section) => {
|
|
2861
3057
|
if (section.value === null || section.value === undefined)
|
|
@@ -3375,6 +3571,58 @@ class TemboryMemory {
|
|
|
3375
3571
|
const output = cleanAssistantTranscriptText(rawOutput);
|
|
3376
3572
|
const toolCalls = extractToolCalls(outputValues);
|
|
3377
3573
|
const saveAt = nowIso();
|
|
3574
|
+
const dedupeState = ensureMemoryDedupeState(store, key);
|
|
3575
|
+
const dedupeSummary = createDedupeSummary();
|
|
3576
|
+
const persistLegacyMemory = async (legacyBody, diagnostics = {}) => {
|
|
3577
|
+
const candidate = buildMemoryDedupeCandidate(legacyBody, diagnostics);
|
|
3578
|
+
const decision = evaluateMemoryDedupeWrite(dedupeState, candidate, saveAt);
|
|
3579
|
+
recordDedupeWriteSummary(dedupeSummary, candidate, decision);
|
|
3580
|
+
if (!decision.persist)
|
|
3581
|
+
return { skipped: true, reason: decision.reason, dedupeKey: candidate.key };
|
|
3582
|
+
const bodyWithDedupe = {
|
|
3583
|
+
...legacyBody,
|
|
3584
|
+
metadata: cleanContextValue({
|
|
3585
|
+
...(legacyBody.metadata || {}),
|
|
3586
|
+
memory_dedupe_key: candidate.key,
|
|
3587
|
+
memory_dedupe_slot: candidate.slot,
|
|
3588
|
+
memory_dedupe_strategy: candidate.strategy,
|
|
3589
|
+
}),
|
|
3590
|
+
};
|
|
3591
|
+
return await safePersistLegacyMemory(this, bodyWithDedupe, {
|
|
3592
|
+
...diagnostics,
|
|
3593
|
+
memory_dedupe_key: candidate.key,
|
|
3594
|
+
memory_dedupe_slot: candidate.slot,
|
|
3595
|
+
});
|
|
3596
|
+
};
|
|
3597
|
+
const finalizeDedupeSummary = async () => {
|
|
3598
|
+
dedupeState.lastSummary = dedupeSummary;
|
|
3599
|
+
dedupeState.updatedAt = nowIso();
|
|
3600
|
+
store.memoryDedupe[key] = mergeMemoryDedupeState(store.memoryDedupe[key], dedupeState);
|
|
3601
|
+
store.captureState[key] = cleanContextValue({
|
|
3602
|
+
...(store.captureState[key] || {}),
|
|
3603
|
+
last_save_dedupe_summary: dedupeSummary,
|
|
3604
|
+
});
|
|
3605
|
+
const finalThreadStateSaved = await saveThreadState(this, key, threadId, project, {
|
|
3606
|
+
kind: 'tembory.thread_state.v1',
|
|
3607
|
+
threadId,
|
|
3608
|
+
project: project || undefined,
|
|
3609
|
+
updatedAt: nowIso(),
|
|
3610
|
+
recentMessages: recentForTurn,
|
|
3611
|
+
toolHistory: toolHistoryForTurn,
|
|
3612
|
+
profileFacts: store.profileFacts[key] || {},
|
|
3613
|
+
workingMemory: workingMemoryForTurn,
|
|
3614
|
+
decisionState: decisionStateForTurn,
|
|
3615
|
+
memoryCompression: compressionForTurn,
|
|
3616
|
+
operationalState: operationalStateForTurn,
|
|
3617
|
+
captureState: store.captureState[key],
|
|
3618
|
+
memoryDedupe: store.memoryDedupe[key],
|
|
3619
|
+
activeSummary: store.activeSummary[key] || '',
|
|
3620
|
+
});
|
|
3621
|
+
store.captureState[key] = cleanContextValue({
|
|
3622
|
+
...(store.captureState[key] || {}),
|
|
3623
|
+
last_save_thread_state_saved: (store.captureState[key] || {}).last_save_thread_state_saved || finalThreadStateSaved,
|
|
3624
|
+
});
|
|
3625
|
+
};
|
|
3378
3626
|
store.captureState[key] = {
|
|
3379
3627
|
last_save_status: 'started',
|
|
3380
3628
|
last_save_saved: false,
|
|
@@ -3388,6 +3636,7 @@ class TemboryMemory {
|
|
|
3388
3636
|
last_save_tool_calls_captured: toolCalls.length,
|
|
3389
3637
|
last_save_tool_names: toolCalls.map((tool) => tool.name).filter(Boolean).slice(0, 20),
|
|
3390
3638
|
last_save_capture_sources: Array.from(new Set(toolCalls.map((tool) => tool.source).filter(Boolean))),
|
|
3639
|
+
last_save_dedupe_summary: dedupeSummary,
|
|
3391
3640
|
};
|
|
3392
3641
|
const recentForTembory = [];
|
|
3393
3642
|
const profileFromTurn = extractProfileFactsFromText(input, 'user_message', nowIso());
|
|
@@ -3488,6 +3737,7 @@ class TemboryMemory {
|
|
|
3488
3737
|
: adv.useVectorMemory === false
|
|
3489
3738
|
? 'thread_state_only'
|
|
3490
3739
|
: 'enabled',
|
|
3740
|
+
last_save_dedupe_summary: dedupeSummary,
|
|
3491
3741
|
});
|
|
3492
3742
|
const threadStateSaved = await saveThreadState(this, key, threadId, project, {
|
|
3493
3743
|
kind: 'tembory.thread_state.v1',
|
|
@@ -3502,6 +3752,7 @@ class TemboryMemory {
|
|
|
3502
3752
|
memoryCompression: compressionForTurn,
|
|
3503
3753
|
operationalState: operationalStateForTurn,
|
|
3504
3754
|
captureState: nextCaptureState,
|
|
3755
|
+
memoryDedupe: dedupeState,
|
|
3505
3756
|
activeSummary: store.activeSummary[key] || '',
|
|
3506
3757
|
});
|
|
3507
3758
|
store.captureState[key] = cleanContextValue({
|
|
@@ -3513,10 +3764,14 @@ class TemboryMemory {
|
|
|
3513
3764
|
globalData.__dataChanged = true;
|
|
3514
3765
|
}
|
|
3515
3766
|
catch { }
|
|
3516
|
-
if (adv.persistBackendMemories === false)
|
|
3767
|
+
if (adv.persistBackendMemories === false) {
|
|
3768
|
+
await finalizeDedupeSummary();
|
|
3517
3769
|
return;
|
|
3518
|
-
|
|
3770
|
+
}
|
|
3771
|
+
if (adv.useVectorMemory === false) {
|
|
3772
|
+
await finalizeDedupeSummary();
|
|
3519
3773
|
return;
|
|
3774
|
+
}
|
|
3520
3775
|
const connectedEmbedding = await getConnectedEmbedding(this, itemIndex);
|
|
3521
3776
|
if (connectedEmbedding) {
|
|
3522
3777
|
const ids = { user_id: body.user_id, agent_id: body.agent_id, run_id: body.run_id };
|
|
@@ -3529,21 +3784,32 @@ class TemboryMemory {
|
|
|
3529
3784
|
memoryCompression: compressionForTurn,
|
|
3530
3785
|
operationalState: operationalStateForTurn,
|
|
3531
3786
|
});
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3787
|
+
const archiveMetadata = {
|
|
3788
|
+
kind: 'turn_archive',
|
|
3789
|
+
thread_id: threadId,
|
|
3790
|
+
project: project || undefined,
|
|
3791
|
+
source: 'n8n_connected_embedding',
|
|
3792
|
+
generated_at: nowIso(),
|
|
3793
|
+
message_count: recentForTurn.length,
|
|
3794
|
+
tool_count: toolHistoryForTurn.length,
|
|
3795
|
+
latest_tool: toolHistoryForTurn.at(-1)?.name || null,
|
|
3796
|
+
};
|
|
3797
|
+
const archiveCandidate = buildMemoryDedupeCandidate({ messages: [{ role: 'system', content: archiveText }], metadata: archiveMetadata }, { kind: 'turn_archive', user_id: body.user_id });
|
|
3798
|
+
const archiveDecision = evaluateMemoryDedupeWrite(dedupeState, archiveCandidate, saveAt);
|
|
3799
|
+
recordDedupeWriteSummary(dedupeSummary, archiveCandidate, archiveDecision);
|
|
3800
|
+
if (archiveDecision.persist) {
|
|
3801
|
+
await saveClientVectorMemories(this, [
|
|
3802
|
+
await createClientVectorMemory(connectedEmbedding, archiveText, {
|
|
3803
|
+
...archiveMetadata,
|
|
3804
|
+
memory_dedupe_key: archiveCandidate.key,
|
|
3805
|
+
memory_dedupe_slot: archiveCandidate.slot,
|
|
3806
|
+
memory_dedupe_strategy: archiveCandidate.strategy,
|
|
3807
|
+
}, ids),
|
|
3808
|
+
], ids);
|
|
3809
|
+
}
|
|
3544
3810
|
if (adv.includeRecentMessages !== false && recentForTembory.length) {
|
|
3545
3811
|
for (const recent of recentForTembory) {
|
|
3546
|
-
await
|
|
3812
|
+
await persistLegacyMemory({
|
|
3547
3813
|
messages: [{ role: 'system', content: encodeRecentMessage(recent, threadId) }],
|
|
3548
3814
|
infer: false,
|
|
3549
3815
|
user_id: body.user_id,
|
|
@@ -3561,9 +3827,10 @@ class TemboryMemory {
|
|
|
3561
3827
|
}, {
|
|
3562
3828
|
kind: 'recent_message',
|
|
3563
3829
|
user_id: body.user_id,
|
|
3830
|
+
thread_id: threadId,
|
|
3564
3831
|
});
|
|
3565
3832
|
}
|
|
3566
|
-
await
|
|
3833
|
+
await persistLegacyMemory({
|
|
3567
3834
|
messages: [{ role: 'system', content: encodeConversationLedger(recentForTurn, threadId) }],
|
|
3568
3835
|
infer: false,
|
|
3569
3836
|
user_id: body.user_id,
|
|
@@ -3579,11 +3846,12 @@ class TemboryMemory {
|
|
|
3579
3846
|
}, {
|
|
3580
3847
|
kind: 'conversation_ledger',
|
|
3581
3848
|
user_id: body.user_id,
|
|
3849
|
+
thread_id: threadId,
|
|
3582
3850
|
});
|
|
3583
3851
|
}
|
|
3584
3852
|
if (adv.includeToolHistory !== false && toolCalls.length) {
|
|
3585
3853
|
for (const tool of toolCalls) {
|
|
3586
|
-
await
|
|
3854
|
+
await persistLegacyMemory({
|
|
3587
3855
|
messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
|
|
3588
3856
|
infer: false,
|
|
3589
3857
|
user_id: body.user_id,
|
|
@@ -3607,9 +3875,10 @@ class TemboryMemory {
|
|
|
3607
3875
|
kind: 'tool_history',
|
|
3608
3876
|
user_id: body.user_id,
|
|
3609
3877
|
tool: tool.name,
|
|
3878
|
+
thread_id: threadId,
|
|
3610
3879
|
});
|
|
3611
3880
|
}
|
|
3612
|
-
await
|
|
3881
|
+
await persistLegacyMemory({
|
|
3613
3882
|
messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
|
|
3614
3883
|
infer: false,
|
|
3615
3884
|
user_id: body.user_id,
|
|
@@ -3625,18 +3894,21 @@ class TemboryMemory {
|
|
|
3625
3894
|
}, {
|
|
3626
3895
|
kind: 'tool_ledger',
|
|
3627
3896
|
user_id: body.user_id,
|
|
3897
|
+
thread_id: threadId,
|
|
3628
3898
|
});
|
|
3629
3899
|
}
|
|
3900
|
+
await finalizeDedupeSummary();
|
|
3630
3901
|
return;
|
|
3631
3902
|
}
|
|
3632
3903
|
if (messages.length)
|
|
3633
|
-
await
|
|
3904
|
+
await persistLegacyMemory(body, {
|
|
3634
3905
|
kind: 'conversation_messages',
|
|
3635
3906
|
user_id: body.user_id,
|
|
3907
|
+
thread_id: threadId,
|
|
3636
3908
|
});
|
|
3637
3909
|
if (adv.includeRecentMessages !== false && recentForTembory.length) {
|
|
3638
3910
|
for (const recent of recentForTembory) {
|
|
3639
|
-
await
|
|
3911
|
+
await persistLegacyMemory({
|
|
3640
3912
|
messages: [{ role: 'system', content: encodeRecentMessage(recent, threadId) }],
|
|
3641
3913
|
infer: false,
|
|
3642
3914
|
user_id: body.user_id,
|
|
@@ -3653,9 +3925,10 @@ class TemboryMemory {
|
|
|
3653
3925
|
}, {
|
|
3654
3926
|
kind: 'recent_message',
|
|
3655
3927
|
user_id: body.user_id,
|
|
3928
|
+
thread_id: threadId,
|
|
3656
3929
|
});
|
|
3657
3930
|
}
|
|
3658
|
-
await
|
|
3931
|
+
await persistLegacyMemory({
|
|
3659
3932
|
messages: [{ role: 'system', content: encodeConversationLedger(recentForTurn, threadId) }],
|
|
3660
3933
|
infer: false,
|
|
3661
3934
|
user_id: body.user_id,
|
|
@@ -3671,11 +3944,12 @@ class TemboryMemory {
|
|
|
3671
3944
|
}, {
|
|
3672
3945
|
kind: 'conversation_ledger',
|
|
3673
3946
|
user_id: body.user_id,
|
|
3947
|
+
thread_id: threadId,
|
|
3674
3948
|
});
|
|
3675
3949
|
}
|
|
3676
3950
|
if (adv.includeToolHistory !== false && !adv.persistToolFactsToTembory && toolCalls.length) {
|
|
3677
3951
|
for (const tool of toolCalls) {
|
|
3678
|
-
await
|
|
3952
|
+
await persistLegacyMemory({
|
|
3679
3953
|
messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
|
|
3680
3954
|
infer: false,
|
|
3681
3955
|
user_id: body.user_id,
|
|
@@ -3699,9 +3973,10 @@ class TemboryMemory {
|
|
|
3699
3973
|
kind: 'tool_history',
|
|
3700
3974
|
user_id: body.user_id,
|
|
3701
3975
|
tool: tool.name,
|
|
3976
|
+
thread_id: threadId,
|
|
3702
3977
|
});
|
|
3703
3978
|
}
|
|
3704
|
-
await
|
|
3979
|
+
await persistLegacyMemory({
|
|
3705
3980
|
messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
|
|
3706
3981
|
infer: false,
|
|
3707
3982
|
user_id: body.user_id,
|
|
@@ -3717,11 +3992,12 @@ class TemboryMemory {
|
|
|
3717
3992
|
}, {
|
|
3718
3993
|
kind: 'tool_ledger',
|
|
3719
3994
|
user_id: body.user_id,
|
|
3995
|
+
thread_id: threadId,
|
|
3720
3996
|
});
|
|
3721
3997
|
}
|
|
3722
3998
|
if (adv.persistToolFactsToTembory && toolCalls.length) {
|
|
3723
3999
|
const facts = toolCalls.map((tool) => `Tool ${tool.name} input=${tool.input}${tool.result ? ` result=${tool.result}` : ''}`).join('\n');
|
|
3724
|
-
await
|
|
4000
|
+
await persistLegacyMemory({
|
|
3725
4001
|
messages: [{ role: 'system', content: `Tool facts (read-only):\n${truncate(facts, 2000)}` }],
|
|
3726
4002
|
infer: false,
|
|
3727
4003
|
user_id: body.user_id,
|
|
@@ -3729,9 +4005,10 @@ class TemboryMemory {
|
|
|
3729
4005
|
}, {
|
|
3730
4006
|
kind: 'tool_facts',
|
|
3731
4007
|
user_id: body.user_id,
|
|
4008
|
+
thread_id: threadId,
|
|
3732
4009
|
});
|
|
3733
4010
|
for (const tool of toolCalls) {
|
|
3734
|
-
await
|
|
4011
|
+
await persistLegacyMemory({
|
|
3735
4012
|
messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
|
|
3736
4013
|
infer: false,
|
|
3737
4014
|
user_id: body.user_id,
|
|
@@ -3754,9 +4031,29 @@ class TemboryMemory {
|
|
|
3754
4031
|
kind: 'tool_history',
|
|
3755
4032
|
user_id: body.user_id,
|
|
3756
4033
|
tool: tool.name,
|
|
4034
|
+
thread_id: threadId,
|
|
3757
4035
|
});
|
|
3758
4036
|
}
|
|
4037
|
+
await persistLegacyMemory({
|
|
4038
|
+
messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
|
|
4039
|
+
infer: false,
|
|
4040
|
+
user_id: body.user_id,
|
|
4041
|
+
agent_id: body.agent_id,
|
|
4042
|
+
run_id: body.run_id,
|
|
4043
|
+
metadata: {
|
|
4044
|
+
kind: 'tool_ledger',
|
|
4045
|
+
thread_id: threadId,
|
|
4046
|
+
project: project || undefined,
|
|
4047
|
+
source: 'tembory_transcript',
|
|
4048
|
+
generated_at: nowIso(),
|
|
4049
|
+
},
|
|
4050
|
+
}, {
|
|
4051
|
+
kind: 'tool_ledger',
|
|
4052
|
+
user_id: body.user_id,
|
|
4053
|
+
thread_id: threadId,
|
|
4054
|
+
});
|
|
3759
4055
|
}
|
|
4056
|
+
await finalizeDedupeSummary();
|
|
3760
4057
|
}
|
|
3761
4058
|
async loadMemoryVariablesForItem(itemIndex, inputValues = {}) {
|
|
3762
4059
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -3808,6 +4105,11 @@ class TemboryMemory {
|
|
|
3808
4105
|
catch { }
|
|
3809
4106
|
let payload;
|
|
3810
4107
|
let vectorMemories = [];
|
|
4108
|
+
const loadDedupeSummary = {
|
|
4109
|
+
enabled: true,
|
|
4110
|
+
load: {},
|
|
4111
|
+
lastSave: store.memoryDedupe[key]?.lastSummary,
|
|
4112
|
+
};
|
|
3811
4113
|
if (vectorMemoryEnabled && (retrievalMode === 'semantic' || retrievalMode === 'semanticV2' || retrievalMode === 'hybrid')) {
|
|
3812
4114
|
const body = { query: String(query || '') };
|
|
3813
4115
|
// IDs
|
|
@@ -4068,6 +4370,12 @@ class TemboryMemory {
|
|
|
4068
4370
|
connectedAi.errors.push(`persistedContext.load: ${error.message || String(error)}`);
|
|
4069
4371
|
}
|
|
4070
4372
|
}
|
|
4373
|
+
const vectorDedupe = dedupeMemoryItemsForLoad(vectorMemories);
|
|
4374
|
+
vectorMemories = vectorDedupe.items;
|
|
4375
|
+
loadDedupeSummary.load.vectorMemories = vectorDedupe.summary;
|
|
4376
|
+
const persistedDedupe = dedupeMemoryItemsForLoad(persistedMemoryItems);
|
|
4377
|
+
persistedMemoryItems = persistedDedupe.items;
|
|
4378
|
+
loadDedupeSummary.load.persistedMemories = persistedDedupe.summary;
|
|
4071
4379
|
if (adv.includeRecentMessages !== false) {
|
|
4072
4380
|
persistedRecentMessages = persistedMemoryItems
|
|
4073
4381
|
.flatMap(recentMessagesFromMemory)
|
|
@@ -4082,7 +4390,13 @@ class TemboryMemory {
|
|
|
4082
4390
|
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
4083
4391
|
}
|
|
4084
4392
|
const storedRecentMessages = adv.includeRecentMessages === false ? [] : (store.recentMessages[key] || []).concat(persistedRecentMessages, vectorRecentMessages);
|
|
4085
|
-
const
|
|
4393
|
+
const appendedRecentMessages = appendCurrentUserMessage(storedRecentMessages, query);
|
|
4394
|
+
const allRecentMessages = dedupeRecentMessages(appendedRecentMessages);
|
|
4395
|
+
loadDedupeSummary.load.recentMessages = cleanContextValue({
|
|
4396
|
+
input: appendedRecentMessages.length,
|
|
4397
|
+
kept: allRecentMessages.length,
|
|
4398
|
+
removed: Math.max(0, appendedRecentMessages.length - allRecentMessages.length),
|
|
4399
|
+
});
|
|
4086
4400
|
const recentMessages = pruneConversationMessagesPreserveAnchors(allRecentMessages, Math.max(Number(adv.recentMessagesLastN || 8), 8));
|
|
4087
4401
|
const toolHistoryFromRecentMessages = [];
|
|
4088
4402
|
if (adv.includeToolHistory !== false) {
|
|
@@ -4091,7 +4405,13 @@ class TemboryMemory {
|
|
|
4091
4405
|
}
|
|
4092
4406
|
const toolHistoryFromVectorMarkers = adv.includeToolHistory === false ? [] : vectorMemories.flatMap(explicitToolHistoryItemsFromMemory);
|
|
4093
4407
|
const toolHistoryFromVectorMemories = adv.includeToolHistory === false || adv.includeToolHistorySemanticFallback !== true ? [] : vectorMemories.flatMap(toolHistoryItemsFromMemory);
|
|
4094
|
-
const
|
|
4408
|
+
const combinedToolHistory = (store.toolHistory[key] || []).concat(persistedToolHistory, toolHistoryFromRecentMessages, toolHistoryFromVectorMarkers, toolHistoryFromVectorMemories);
|
|
4409
|
+
const toolHistory = adv.includeToolHistory === false ? [] : applyToolHistoryWindow(combinedToolHistory, adv.toolHistoryTTLSeconds, adv.toolHistoryLastN || 15);
|
|
4410
|
+
loadDedupeSummary.load.toolHistory = cleanContextValue({
|
|
4411
|
+
input: adv.includeToolHistory === false ? 0 : combinedToolHistory.length,
|
|
4412
|
+
kept: toolHistory.length,
|
|
4413
|
+
removed: Math.max(0, (adv.includeToolHistory === false ? 0 : combinedToolHistory.length) - toolHistory.length),
|
|
4414
|
+
});
|
|
4095
4415
|
const highlights = adv.includeRecentHighlights === false ? [] : getRecentHighlights(recentMessages, toolHistory, adv.recentHighlightsMaxItems || 6);
|
|
4096
4416
|
const profileFacts = adv.includeProfileFacts === false ? {} : mergeProfileFacts(store.profileFacts[key], profileFactsFromMessages(allRecentMessages), profileFactsFromMemories(vectorMemories));
|
|
4097
4417
|
if (adv.includeProfileFacts !== false && Object.keys(profileFacts).length) {
|
|
@@ -4221,6 +4541,7 @@ class TemboryMemory {
|
|
|
4221
4541
|
},
|
|
4222
4542
|
connectedAi,
|
|
4223
4543
|
activeSummary: summaryDiagnostics,
|
|
4544
|
+
dedupeSummary: cleanContextValue(loadDedupeSummary),
|
|
4224
4545
|
};
|
|
4225
4546
|
const contextHealth = deriveContextHealth({
|
|
4226
4547
|
userId: key,
|
|
@@ -4360,6 +4681,7 @@ class TemboryMemory {
|
|
|
4360
4681
|
})),
|
|
4361
4682
|
},
|
|
4362
4683
|
diagnostics,
|
|
4684
|
+
dedupeSummary: diagnostics.dedupeSummary,
|
|
4363
4685
|
};
|
|
4364
4686
|
return {
|
|
4365
4687
|
response: {
|
|
@@ -4493,6 +4815,10 @@ exports.__private = {
|
|
|
4493
4815
|
deriveOperationalState,
|
|
4494
4816
|
deriveActionLedger,
|
|
4495
4817
|
deriveEntityTimeline,
|
|
4818
|
+
buildMemoryDedupeCandidate,
|
|
4819
|
+
dedupeMemoryItemsForLoad,
|
|
4820
|
+
mergeMemoryDedupeState,
|
|
4821
|
+
evaluateMemoryDedupeWrite,
|
|
4496
4822
|
scoreOf,
|
|
4497
4823
|
scoreMetaOf,
|
|
4498
4824
|
extractProfileFactsFromText,
|
package/package.json
CHANGED