n8n-nodes-tembory 1.1.25 → 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);
|
|
@@ -1859,21 +2052,73 @@ const deriveNextExpectedAction = (intent, operationalState = {}) => {
|
|
|
1859
2052
|
return 'continue according to the agent prompt using retrieved context; call tools when the agent prompt or tool policy requires them';
|
|
1860
2053
|
};
|
|
1861
2054
|
const isGenericMemoryNextAction = (value = '') => /answer using retrieved context and avoid unnecessary tool calls|continue according to the agent prompt using retrieved context/i.test(String(value || ''));
|
|
2055
|
+
const shouldCarryPreviousGoal = (intent = '', previousGoal = '') => {
|
|
2056
|
+
if (!previousGoal || isGenericMemoryNextAction(previousGoal))
|
|
2057
|
+
return false;
|
|
2058
|
+
return ['general_message', 'profile_update', 'unknown'].includes(intent);
|
|
2059
|
+
};
|
|
1862
2060
|
const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [], toolHistory = [], operationalState = {}, previous = {} }) => {
|
|
1863
2061
|
const intent = inferUserIntent(query, recentMessages);
|
|
1864
|
-
const
|
|
2062
|
+
const chronological = sortConversationChronological(recentMessages || []);
|
|
2063
|
+
const userMessages = chronological.filter((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
2064
|
+
const assistantMessages = chronological.filter((msg) => /^(assistant|ai|agent)$/i.test(String(msg.role || '')));
|
|
2065
|
+
const normalizedQuery = normalizeIntentText(query).trim();
|
|
2066
|
+
const lastUser = [...userMessages].reverse().find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
2067
|
+
const priorUser = [...userMessages].reverse().find((msg) => normalizeIntentText(msg.content).trim() !== normalizedQuery);
|
|
2068
|
+
const lastAssistant = assistantMessages[assistantMessages.length - 1] || null;
|
|
1865
2069
|
const activeEntities = [];
|
|
1866
2070
|
for (const key of ['name', 'company', 'email', 'phone']) {
|
|
1867
2071
|
if (profileFacts && profileFacts[key])
|
|
1868
2072
|
activeEntities.push({ type: key, value: profileFacts[key] });
|
|
1869
2073
|
}
|
|
1870
2074
|
const lastTool = toolHistory && toolHistory.length ? toolHistory[toolHistory.length - 1] : null;
|
|
2075
|
+
const toolState = (operationalState || {}).tool_state || {};
|
|
1871
2076
|
const nextExpectedAction = deriveNextExpectedAction(intent, operationalState);
|
|
2077
|
+
const carryPreviousGoal = shouldCarryPreviousGoal(intent, previous.current_goal);
|
|
2078
|
+
const currentGoal = carryPreviousGoal ? previous.current_goal : nextExpectedAction;
|
|
2079
|
+
const toolNames = Array.from(new Set((toolHistory || []).map((tool) => tool.name || tool.tool_name || tool.tool).filter(Boolean)));
|
|
2080
|
+
const currentTurnMayNeedTool = ['affirm', 'commit_or_continue', 'tool_action_candidate', 'selection_or_slot'].includes(intent);
|
|
1872
2081
|
return {
|
|
1873
|
-
current_goal:
|
|
2082
|
+
current_goal: currentGoal,
|
|
2083
|
+
current_goal_reason: carryPreviousGoal ? 'carried_from_previous_non_generic_goal' : 'derived_from_current_turn_intent',
|
|
1874
2084
|
current_task: nextExpectedAction,
|
|
1875
2085
|
last_user_intent: intent,
|
|
1876
2086
|
last_user_message: lastUser ? truncate(lastUser.content, 500) : truncate(query, 500),
|
|
2087
|
+
conversation_digest: cleanContextValue({
|
|
2088
|
+
messages: chronological.length,
|
|
2089
|
+
user_messages: userMessages.length,
|
|
2090
|
+
assistant_messages: assistantMessages.length,
|
|
2091
|
+
current_user: truncate(query, 220),
|
|
2092
|
+
prior_user: priorUser ? { content: truncate(priorUser.content, 180), at: priorUser.at } : undefined,
|
|
2093
|
+
last_assistant: lastAssistant ? { content: truncate(lastAssistant.content, 180), at: lastAssistant.at } : undefined,
|
|
2094
|
+
}),
|
|
2095
|
+
tool_digest: cleanContextValue({
|
|
2096
|
+
total: Array.isArray(toolHistory) ? toolHistory.length : 0,
|
|
2097
|
+
ok: (operationalState || {}).tool_counts?.ok,
|
|
2098
|
+
failed: (operationalState || {}).tool_counts?.failed,
|
|
2099
|
+
names: toolNames.slice(0, 12),
|
|
2100
|
+
last_successful: toolState.last_successful_tool ? {
|
|
2101
|
+
name: toolState.last_successful_tool.name,
|
|
2102
|
+
at: toolState.last_successful_tool.at,
|
|
2103
|
+
status: toolState.last_successful_tool.status || 'ok',
|
|
2104
|
+
} : undefined,
|
|
2105
|
+
failed_by_name: toolState.failed_by_name,
|
|
2106
|
+
}),
|
|
2107
|
+
turn_flags: cleanContextValue({
|
|
2108
|
+
recall_only: intent === 'conversation_recall',
|
|
2109
|
+
status_question: intent === 'operational_status_question',
|
|
2110
|
+
current_turn_may_need_tool: currentTurnMayNeedTool,
|
|
2111
|
+
has_prior_tools: Array.isArray(toolHistory) && toolHistory.length > 0,
|
|
2112
|
+
has_profile_facts: activeEntities.length > 0,
|
|
2113
|
+
carry_previous_goal: carryPreviousGoal,
|
|
2114
|
+
}),
|
|
2115
|
+
context_sources: cleanContextValue({
|
|
2116
|
+
conversation: chronological.length > 0,
|
|
2117
|
+
tool_history: Array.isArray(toolHistory) && toolHistory.length > 0,
|
|
2118
|
+
operational_state: Boolean((operationalState || {}).tool_state || (operationalState || {}).tool_counts),
|
|
2119
|
+
profile_facts: activeEntities.length > 0,
|
|
2120
|
+
}),
|
|
2121
|
+
agent_guidance: nextExpectedAction,
|
|
1877
2122
|
active_entities: activeEntities,
|
|
1878
2123
|
open_decisions: previous.open_decisions || [],
|
|
1879
2124
|
last_error: lastTool && lastTool.ok === false ? { tool: lastTool.name, at: lastTool.at, result: lastTool.result } : null,
|
|
@@ -2148,9 +2393,15 @@ const cleanContextValue = (value) => {
|
|
|
2148
2393
|
};
|
|
2149
2394
|
const compactWorkingMemoryForAgent = (memory = {}) => cleanContextValue({
|
|
2150
2395
|
current_goal: memory.current_goal,
|
|
2396
|
+
current_goal_reason: memory.current_goal_reason,
|
|
2151
2397
|
current_task: memory.current_task,
|
|
2152
2398
|
last_user_intent: memory.last_user_intent,
|
|
2153
2399
|
last_user_message: truncate(memory.last_user_message, 220),
|
|
2400
|
+
conversation_digest: memory.conversation_digest,
|
|
2401
|
+
tool_digest: memory.tool_digest,
|
|
2402
|
+
turn_flags: memory.turn_flags,
|
|
2403
|
+
context_sources: memory.context_sources,
|
|
2404
|
+
agent_guidance: memory.agent_guidance,
|
|
2154
2405
|
active_entities: memory.active_entities,
|
|
2155
2406
|
open_decisions: memory.open_decisions,
|
|
2156
2407
|
last_error: memory.last_error,
|
|
@@ -2229,6 +2480,7 @@ const compactSaveAuditForAgent = (capture = {}) => cleanContextValue({
|
|
|
2229
2480
|
tool_history_after_save: capture.last_save_tool_history_after_save,
|
|
2230
2481
|
thread_state_saved: capture.last_save_thread_state_saved,
|
|
2231
2482
|
backend_memory_persistence: capture.last_save_backend_memory_persistence,
|
|
2483
|
+
dedupe: capture.last_save_dedupe_summary,
|
|
2232
2484
|
},
|
|
2233
2485
|
});
|
|
2234
2486
|
const compactEntityTimelineForAgent = (timeline = [], maxItems = 8) => pruneByLimit(timeline || [], maxItems).map((item) => cleanContextValue({
|
|
@@ -2523,6 +2775,7 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
|
|
|
2523
2775
|
recentHighlights: Array.isArray(parsed.recentHighlights) ? parsed.recentHighlights.length : undefined,
|
|
2524
2776
|
});
|
|
2525
2777
|
summary.lastSave = memoryAudit.last_save || parsed.state?.last_save || undefined;
|
|
2778
|
+
summary.dedupeSummary = parsed.dedupeSummary || parsed.diagnostics?.dedupeSummary || undefined;
|
|
2526
2779
|
summary.loadedSections = cleanContextValue({
|
|
2527
2780
|
conversation: Boolean(parsed.conversation),
|
|
2528
2781
|
summary: Boolean(parsed.summary),
|
|
@@ -2543,10 +2796,16 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
|
|
|
2543
2796
|
});
|
|
2544
2797
|
summary.workingMemory = cleanContextValue({
|
|
2545
2798
|
currentGoal: parsed.workingMemory?.current_goal,
|
|
2799
|
+
currentGoalReason: parsed.workingMemory?.current_goal_reason,
|
|
2546
2800
|
currentTask: parsed.workingMemory?.current_task,
|
|
2547
2801
|
nextExpectedAction: parsed.workingMemory?.next_expected_action,
|
|
2548
2802
|
lastUserIntent: parsed.workingMemory?.last_user_intent,
|
|
2549
2803
|
lastUserMessage: parsed.workingMemory?.last_user_message ? truncate(parsed.workingMemory.last_user_message, 220) : undefined,
|
|
2804
|
+
conversation: parsed.workingMemory?.conversation_digest,
|
|
2805
|
+
tools: parsed.workingMemory?.tool_digest,
|
|
2806
|
+
turnFlags: parsed.workingMemory?.turn_flags,
|
|
2807
|
+
contextSources: parsed.workingMemory?.context_sources,
|
|
2808
|
+
agentGuidance: parsed.workingMemory?.agent_guidance,
|
|
2550
2809
|
});
|
|
2551
2810
|
summary.decisionState = cleanContextValue({
|
|
2552
2811
|
currentIntent: parsed.decisionState?.current_intent,
|
|
@@ -2792,6 +3051,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
2792
3051
|
profile: sectionValue('profile_facts'),
|
|
2793
3052
|
tools: compactToolLedger,
|
|
2794
3053
|
memoryAudit,
|
|
3054
|
+
dedupeSummary: diagnostics?.dedupeSummary,
|
|
2795
3055
|
});
|
|
2796
3056
|
const renderCompactSection = (section) => {
|
|
2797
3057
|
if (section.value === null || section.value === undefined)
|
|
@@ -3311,6 +3571,58 @@ class TemboryMemory {
|
|
|
3311
3571
|
const output = cleanAssistantTranscriptText(rawOutput);
|
|
3312
3572
|
const toolCalls = extractToolCalls(outputValues);
|
|
3313
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
|
+
};
|
|
3314
3626
|
store.captureState[key] = {
|
|
3315
3627
|
last_save_status: 'started',
|
|
3316
3628
|
last_save_saved: false,
|
|
@@ -3324,6 +3636,7 @@ class TemboryMemory {
|
|
|
3324
3636
|
last_save_tool_calls_captured: toolCalls.length,
|
|
3325
3637
|
last_save_tool_names: toolCalls.map((tool) => tool.name).filter(Boolean).slice(0, 20),
|
|
3326
3638
|
last_save_capture_sources: Array.from(new Set(toolCalls.map((tool) => tool.source).filter(Boolean))),
|
|
3639
|
+
last_save_dedupe_summary: dedupeSummary,
|
|
3327
3640
|
};
|
|
3328
3641
|
const recentForTembory = [];
|
|
3329
3642
|
const profileFromTurn = extractProfileFactsFromText(input, 'user_message', nowIso());
|
|
@@ -3424,6 +3737,7 @@ class TemboryMemory {
|
|
|
3424
3737
|
: adv.useVectorMemory === false
|
|
3425
3738
|
? 'thread_state_only'
|
|
3426
3739
|
: 'enabled',
|
|
3740
|
+
last_save_dedupe_summary: dedupeSummary,
|
|
3427
3741
|
});
|
|
3428
3742
|
const threadStateSaved = await saveThreadState(this, key, threadId, project, {
|
|
3429
3743
|
kind: 'tembory.thread_state.v1',
|
|
@@ -3438,6 +3752,7 @@ class TemboryMemory {
|
|
|
3438
3752
|
memoryCompression: compressionForTurn,
|
|
3439
3753
|
operationalState: operationalStateForTurn,
|
|
3440
3754
|
captureState: nextCaptureState,
|
|
3755
|
+
memoryDedupe: dedupeState,
|
|
3441
3756
|
activeSummary: store.activeSummary[key] || '',
|
|
3442
3757
|
});
|
|
3443
3758
|
store.captureState[key] = cleanContextValue({
|
|
@@ -3449,10 +3764,14 @@ class TemboryMemory {
|
|
|
3449
3764
|
globalData.__dataChanged = true;
|
|
3450
3765
|
}
|
|
3451
3766
|
catch { }
|
|
3452
|
-
if (adv.persistBackendMemories === false)
|
|
3767
|
+
if (adv.persistBackendMemories === false) {
|
|
3768
|
+
await finalizeDedupeSummary();
|
|
3453
3769
|
return;
|
|
3454
|
-
|
|
3770
|
+
}
|
|
3771
|
+
if (adv.useVectorMemory === false) {
|
|
3772
|
+
await finalizeDedupeSummary();
|
|
3455
3773
|
return;
|
|
3774
|
+
}
|
|
3456
3775
|
const connectedEmbedding = await getConnectedEmbedding(this, itemIndex);
|
|
3457
3776
|
if (connectedEmbedding) {
|
|
3458
3777
|
const ids = { user_id: body.user_id, agent_id: body.agent_id, run_id: body.run_id };
|
|
@@ -3465,21 +3784,32 @@ class TemboryMemory {
|
|
|
3465
3784
|
memoryCompression: compressionForTurn,
|
|
3466
3785
|
operationalState: operationalStateForTurn,
|
|
3467
3786
|
});
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
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
|
+
}
|
|
3480
3810
|
if (adv.includeRecentMessages !== false && recentForTembory.length) {
|
|
3481
3811
|
for (const recent of recentForTembory) {
|
|
3482
|
-
await
|
|
3812
|
+
await persistLegacyMemory({
|
|
3483
3813
|
messages: [{ role: 'system', content: encodeRecentMessage(recent, threadId) }],
|
|
3484
3814
|
infer: false,
|
|
3485
3815
|
user_id: body.user_id,
|
|
@@ -3497,9 +3827,10 @@ class TemboryMemory {
|
|
|
3497
3827
|
}, {
|
|
3498
3828
|
kind: 'recent_message',
|
|
3499
3829
|
user_id: body.user_id,
|
|
3830
|
+
thread_id: threadId,
|
|
3500
3831
|
});
|
|
3501
3832
|
}
|
|
3502
|
-
await
|
|
3833
|
+
await persistLegacyMemory({
|
|
3503
3834
|
messages: [{ role: 'system', content: encodeConversationLedger(recentForTurn, threadId) }],
|
|
3504
3835
|
infer: false,
|
|
3505
3836
|
user_id: body.user_id,
|
|
@@ -3515,11 +3846,12 @@ class TemboryMemory {
|
|
|
3515
3846
|
}, {
|
|
3516
3847
|
kind: 'conversation_ledger',
|
|
3517
3848
|
user_id: body.user_id,
|
|
3849
|
+
thread_id: threadId,
|
|
3518
3850
|
});
|
|
3519
3851
|
}
|
|
3520
3852
|
if (adv.includeToolHistory !== false && toolCalls.length) {
|
|
3521
3853
|
for (const tool of toolCalls) {
|
|
3522
|
-
await
|
|
3854
|
+
await persistLegacyMemory({
|
|
3523
3855
|
messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
|
|
3524
3856
|
infer: false,
|
|
3525
3857
|
user_id: body.user_id,
|
|
@@ -3543,9 +3875,10 @@ class TemboryMemory {
|
|
|
3543
3875
|
kind: 'tool_history',
|
|
3544
3876
|
user_id: body.user_id,
|
|
3545
3877
|
tool: tool.name,
|
|
3878
|
+
thread_id: threadId,
|
|
3546
3879
|
});
|
|
3547
3880
|
}
|
|
3548
|
-
await
|
|
3881
|
+
await persistLegacyMemory({
|
|
3549
3882
|
messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
|
|
3550
3883
|
infer: false,
|
|
3551
3884
|
user_id: body.user_id,
|
|
@@ -3561,18 +3894,21 @@ class TemboryMemory {
|
|
|
3561
3894
|
}, {
|
|
3562
3895
|
kind: 'tool_ledger',
|
|
3563
3896
|
user_id: body.user_id,
|
|
3897
|
+
thread_id: threadId,
|
|
3564
3898
|
});
|
|
3565
3899
|
}
|
|
3900
|
+
await finalizeDedupeSummary();
|
|
3566
3901
|
return;
|
|
3567
3902
|
}
|
|
3568
3903
|
if (messages.length)
|
|
3569
|
-
await
|
|
3904
|
+
await persistLegacyMemory(body, {
|
|
3570
3905
|
kind: 'conversation_messages',
|
|
3571
3906
|
user_id: body.user_id,
|
|
3907
|
+
thread_id: threadId,
|
|
3572
3908
|
});
|
|
3573
3909
|
if (adv.includeRecentMessages !== false && recentForTembory.length) {
|
|
3574
3910
|
for (const recent of recentForTembory) {
|
|
3575
|
-
await
|
|
3911
|
+
await persistLegacyMemory({
|
|
3576
3912
|
messages: [{ role: 'system', content: encodeRecentMessage(recent, threadId) }],
|
|
3577
3913
|
infer: false,
|
|
3578
3914
|
user_id: body.user_id,
|
|
@@ -3589,9 +3925,10 @@ class TemboryMemory {
|
|
|
3589
3925
|
}, {
|
|
3590
3926
|
kind: 'recent_message',
|
|
3591
3927
|
user_id: body.user_id,
|
|
3928
|
+
thread_id: threadId,
|
|
3592
3929
|
});
|
|
3593
3930
|
}
|
|
3594
|
-
await
|
|
3931
|
+
await persistLegacyMemory({
|
|
3595
3932
|
messages: [{ role: 'system', content: encodeConversationLedger(recentForTurn, threadId) }],
|
|
3596
3933
|
infer: false,
|
|
3597
3934
|
user_id: body.user_id,
|
|
@@ -3607,11 +3944,12 @@ class TemboryMemory {
|
|
|
3607
3944
|
}, {
|
|
3608
3945
|
kind: 'conversation_ledger',
|
|
3609
3946
|
user_id: body.user_id,
|
|
3947
|
+
thread_id: threadId,
|
|
3610
3948
|
});
|
|
3611
3949
|
}
|
|
3612
3950
|
if (adv.includeToolHistory !== false && !adv.persistToolFactsToTembory && toolCalls.length) {
|
|
3613
3951
|
for (const tool of toolCalls) {
|
|
3614
|
-
await
|
|
3952
|
+
await persistLegacyMemory({
|
|
3615
3953
|
messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
|
|
3616
3954
|
infer: false,
|
|
3617
3955
|
user_id: body.user_id,
|
|
@@ -3635,9 +3973,10 @@ class TemboryMemory {
|
|
|
3635
3973
|
kind: 'tool_history',
|
|
3636
3974
|
user_id: body.user_id,
|
|
3637
3975
|
tool: tool.name,
|
|
3976
|
+
thread_id: threadId,
|
|
3638
3977
|
});
|
|
3639
3978
|
}
|
|
3640
|
-
await
|
|
3979
|
+
await persistLegacyMemory({
|
|
3641
3980
|
messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
|
|
3642
3981
|
infer: false,
|
|
3643
3982
|
user_id: body.user_id,
|
|
@@ -3653,11 +3992,12 @@ class TemboryMemory {
|
|
|
3653
3992
|
}, {
|
|
3654
3993
|
kind: 'tool_ledger',
|
|
3655
3994
|
user_id: body.user_id,
|
|
3995
|
+
thread_id: threadId,
|
|
3656
3996
|
});
|
|
3657
3997
|
}
|
|
3658
3998
|
if (adv.persistToolFactsToTembory && toolCalls.length) {
|
|
3659
3999
|
const facts = toolCalls.map((tool) => `Tool ${tool.name} input=${tool.input}${tool.result ? ` result=${tool.result}` : ''}`).join('\n');
|
|
3660
|
-
await
|
|
4000
|
+
await persistLegacyMemory({
|
|
3661
4001
|
messages: [{ role: 'system', content: `Tool facts (read-only):\n${truncate(facts, 2000)}` }],
|
|
3662
4002
|
infer: false,
|
|
3663
4003
|
user_id: body.user_id,
|
|
@@ -3665,9 +4005,10 @@ class TemboryMemory {
|
|
|
3665
4005
|
}, {
|
|
3666
4006
|
kind: 'tool_facts',
|
|
3667
4007
|
user_id: body.user_id,
|
|
4008
|
+
thread_id: threadId,
|
|
3668
4009
|
});
|
|
3669
4010
|
for (const tool of toolCalls) {
|
|
3670
|
-
await
|
|
4011
|
+
await persistLegacyMemory({
|
|
3671
4012
|
messages: [{ role: 'system', content: encodeToolCall(tool, threadId) }],
|
|
3672
4013
|
infer: false,
|
|
3673
4014
|
user_id: body.user_id,
|
|
@@ -3690,9 +4031,29 @@ class TemboryMemory {
|
|
|
3690
4031
|
kind: 'tool_history',
|
|
3691
4032
|
user_id: body.user_id,
|
|
3692
4033
|
tool: tool.name,
|
|
4034
|
+
thread_id: threadId,
|
|
3693
4035
|
});
|
|
3694
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
|
+
});
|
|
3695
4055
|
}
|
|
4056
|
+
await finalizeDedupeSummary();
|
|
3696
4057
|
}
|
|
3697
4058
|
async loadMemoryVariablesForItem(itemIndex, inputValues = {}) {
|
|
3698
4059
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -3744,6 +4105,11 @@ class TemboryMemory {
|
|
|
3744
4105
|
catch { }
|
|
3745
4106
|
let payload;
|
|
3746
4107
|
let vectorMemories = [];
|
|
4108
|
+
const loadDedupeSummary = {
|
|
4109
|
+
enabled: true,
|
|
4110
|
+
load: {},
|
|
4111
|
+
lastSave: store.memoryDedupe[key]?.lastSummary,
|
|
4112
|
+
};
|
|
3747
4113
|
if (vectorMemoryEnabled && (retrievalMode === 'semantic' || retrievalMode === 'semanticV2' || retrievalMode === 'hybrid')) {
|
|
3748
4114
|
const body = { query: String(query || '') };
|
|
3749
4115
|
// IDs
|
|
@@ -4004,6 +4370,12 @@ class TemboryMemory {
|
|
|
4004
4370
|
connectedAi.errors.push(`persistedContext.load: ${error.message || String(error)}`);
|
|
4005
4371
|
}
|
|
4006
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;
|
|
4007
4379
|
if (adv.includeRecentMessages !== false) {
|
|
4008
4380
|
persistedRecentMessages = persistedMemoryItems
|
|
4009
4381
|
.flatMap(recentMessagesFromMemory)
|
|
@@ -4018,7 +4390,13 @@ class TemboryMemory {
|
|
|
4018
4390
|
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
4019
4391
|
}
|
|
4020
4392
|
const storedRecentMessages = adv.includeRecentMessages === false ? [] : (store.recentMessages[key] || []).concat(persistedRecentMessages, vectorRecentMessages);
|
|
4021
|
-
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
|
+
});
|
|
4022
4400
|
const recentMessages = pruneConversationMessagesPreserveAnchors(allRecentMessages, Math.max(Number(adv.recentMessagesLastN || 8), 8));
|
|
4023
4401
|
const toolHistoryFromRecentMessages = [];
|
|
4024
4402
|
if (adv.includeToolHistory !== false) {
|
|
@@ -4027,7 +4405,13 @@ class TemboryMemory {
|
|
|
4027
4405
|
}
|
|
4028
4406
|
const toolHistoryFromVectorMarkers = adv.includeToolHistory === false ? [] : vectorMemories.flatMap(explicitToolHistoryItemsFromMemory);
|
|
4029
4407
|
const toolHistoryFromVectorMemories = adv.includeToolHistory === false || adv.includeToolHistorySemanticFallback !== true ? [] : vectorMemories.flatMap(toolHistoryItemsFromMemory);
|
|
4030
|
-
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
|
+
});
|
|
4031
4415
|
const highlights = adv.includeRecentHighlights === false ? [] : getRecentHighlights(recentMessages, toolHistory, adv.recentHighlightsMaxItems || 6);
|
|
4032
4416
|
const profileFacts = adv.includeProfileFacts === false ? {} : mergeProfileFacts(store.profileFacts[key], profileFactsFromMessages(allRecentMessages), profileFactsFromMemories(vectorMemories));
|
|
4033
4417
|
if (adv.includeProfileFacts !== false && Object.keys(profileFacts).length) {
|
|
@@ -4157,6 +4541,7 @@ class TemboryMemory {
|
|
|
4157
4541
|
},
|
|
4158
4542
|
connectedAi,
|
|
4159
4543
|
activeSummary: summaryDiagnostics,
|
|
4544
|
+
dedupeSummary: cleanContextValue(loadDedupeSummary),
|
|
4160
4545
|
};
|
|
4161
4546
|
const contextHealth = deriveContextHealth({
|
|
4162
4547
|
userId: key,
|
|
@@ -4296,6 +4681,7 @@ class TemboryMemory {
|
|
|
4296
4681
|
})),
|
|
4297
4682
|
},
|
|
4298
4683
|
diagnostics,
|
|
4684
|
+
dedupeSummary: diagnostics.dedupeSummary,
|
|
4299
4685
|
};
|
|
4300
4686
|
return {
|
|
4301
4687
|
response: {
|
|
@@ -4429,6 +4815,10 @@ exports.__private = {
|
|
|
4429
4815
|
deriveOperationalState,
|
|
4430
4816
|
deriveActionLedger,
|
|
4431
4817
|
deriveEntityTimeline,
|
|
4818
|
+
buildMemoryDedupeCandidate,
|
|
4819
|
+
dedupeMemoryItemsForLoad,
|
|
4820
|
+
mergeMemoryDedupeState,
|
|
4821
|
+
evaluateMemoryDedupeWrite,
|
|
4432
4822
|
scoreOf,
|
|
4433
4823
|
scoreMetaOf,
|
|
4434
4824
|
extractProfileFactsFromText,
|
package/package.json
CHANGED