mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
fix(recall): reuse persisted recall on reroll
This commit is contained in:
52
index.js
52
index.js
@@ -5250,6 +5250,12 @@ function persistRecallInjectionRecord({
|
||||
return null;
|
||||
}
|
||||
|
||||
const targetUserFloorText = normalizeRecallInputText(
|
||||
chat[resolvedTargetIndex]?.mes || "",
|
||||
);
|
||||
const boundUserFloorText = normalizeRecallInputText(
|
||||
recallInput?.boundUserFloorText || targetUserFloorText,
|
||||
);
|
||||
const record = buildPersistedRecallRecord(
|
||||
{
|
||||
injectionText,
|
||||
@@ -5259,6 +5265,8 @@ function persistRecallInjectionRecord({
|
||||
hookName: String(recallInput?.hookName || ""),
|
||||
tokenEstimate,
|
||||
manuallyEdited: false,
|
||||
authoritativeInputUsed: Boolean(recallInput?.authoritativeInputUsed),
|
||||
boundUserFloorText,
|
||||
},
|
||||
readPersistedRecallFromUserMessage(chat, resolvedTargetIndex),
|
||||
);
|
||||
@@ -5379,11 +5387,34 @@ function ensurePersistedRecallRecordForGeneration({
|
||||
chat,
|
||||
targetUserMessageIndex,
|
||||
);
|
||||
const nextAuthoritativeInputUsed = Boolean(
|
||||
recallResult?.authoritativeInputUsed ??
|
||||
frozenRecallOptions?.authoritativeInputUsed ??
|
||||
recallOptions?.authoritativeInputUsed,
|
||||
);
|
||||
const targetUserFloorText = normalizeRecallInputText(
|
||||
chat[targetUserMessageIndex]?.mes || "",
|
||||
);
|
||||
const nextBoundUserFloorText = normalizeRecallInputText(
|
||||
recallResult?.boundUserFloorText ||
|
||||
frozenRecallOptions?.boundUserFloorText ||
|
||||
recallOptions?.boundUserFloorText ||
|
||||
targetUserFloorText ||
|
||||
"",
|
||||
);
|
||||
const existingBoundUserFloorText = normalizeRecallInputText(
|
||||
existingRecord?.boundUserFloorText || "",
|
||||
);
|
||||
const existingMetadataUpToDate =
|
||||
Boolean(existingRecord?.authoritativeInputUsed) === nextAuthoritativeInputUsed &&
|
||||
(!nextBoundUserFloorText ||
|
||||
existingBoundUserFloorText === nextBoundUserFloorText);
|
||||
if (
|
||||
existingRecord &&
|
||||
String(existingRecord.injectionText || "").trim() === injectionText &&
|
||||
areRecallNodeIdListsEqual(existingRecord.selectedNodeIds, selectedNodeIds) &&
|
||||
String(existingRecord.recallInput || "").trim()
|
||||
String(existingRecord.recallInput || "").trim() &&
|
||||
existingMetadataUpToDate
|
||||
) {
|
||||
return {
|
||||
persisted: false,
|
||||
@@ -5421,17 +5452,8 @@ function ensurePersistedRecallRecordForGeneration({
|
||||
),
|
||||
tokenEstimate: estimateTokens(injectionText),
|
||||
manuallyEdited: false,
|
||||
authoritativeInputUsed: Boolean(
|
||||
recallResult?.authoritativeInputUsed ??
|
||||
frozenRecallOptions?.authoritativeInputUsed ??
|
||||
recallOptions?.authoritativeInputUsed,
|
||||
),
|
||||
boundUserFloorText: String(
|
||||
recallResult?.boundUserFloorText ||
|
||||
frozenRecallOptions?.boundUserFloorText ||
|
||||
recallOptions?.boundUserFloorText ||
|
||||
"",
|
||||
),
|
||||
authoritativeInputUsed: nextAuthoritativeInputUsed,
|
||||
boundUserFloorText: nextBoundUserFloorText,
|
||||
},
|
||||
existingRecord,
|
||||
);
|
||||
@@ -20042,10 +20064,14 @@ function createGenerationRecallContext({
|
||||
transaction.chatId,
|
||||
);
|
||||
const transactionRecallKey = String(transaction.recallKey || "").trim();
|
||||
const peerHookName = getGenerationRecallPeerHookName(hookName);
|
||||
const hasPeerHookState = Boolean(
|
||||
peerHookName && transaction.hookStates?.[peerHookName],
|
||||
);
|
||||
if (
|
||||
normalizedTransactionChatId !== normalizedChatId ||
|
||||
!transactionRecallKey ||
|
||||
transactionRecallKey !== String(fallbackRecallKey)
|
||||
(!hasPeerHookState && transactionRecallKey !== String(fallbackRecallKey))
|
||||
) {
|
||||
return {
|
||||
hookName,
|
||||
|
||||
@@ -132,8 +132,8 @@ function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) {
|
||||
);
|
||||
const matchesCurrentUserFloor = Boolean(
|
||||
currentUserFloorText &&
|
||||
recordRecallInput &&
|
||||
currentUserFloorText === recordRecallInput,
|
||||
boundUserFloorText &&
|
||||
currentUserFloorText === boundUserFloorText,
|
||||
);
|
||||
|
||||
if (record.authoritativeInputUsed) {
|
||||
|
||||
@@ -269,7 +269,7 @@ export function createGenerationRecallHarness(options = {}) {
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(
|
||||
`${snippet}\nresult = { hashRecallInput, buildPreGenerationRecallKey, buildGenerationAfterCommandsRecallInput, buildNormalGenerationRecallInput, cleanupGenerationRecallTransactions, buildGenerationRecallTransactionId, beginGenerationRecallTransaction, markGenerationRecallTransactionHookState, shouldRunRecallForTransaction, createGenerationRecallContext, onGenerationStarted, onGenerationEnded, onGenerationAfterCommands, onBeforeCombinePrompts, applyFinalRecallInjectionForGeneration, ensurePersistedRecallRecordForGeneration, findRecentGenerationRecallTransactionForChat, getGenerationRecallTransactionResult, generationRecallTransactions, freezeHostGenerationInputSnapshot, consumeHostGenerationInputSnapshot, getPendingHostGenerationInputSnapshot, clearPendingHostGenerationInputSnapshot, recordRecallSendIntent, clearPendingRecallSendIntent, recordRecallSentUserMessage, getPendingRecallSendIntent: () => pendingRecallSendIntent, getLastRecallSentUserMessage: () => lastRecallSentUserMessage, getCurrentGenerationTrivialSkip, markCurrentGenerationTrivialSkip, clearCurrentGenerationTrivialSkip, consumeCurrentGenerationTrivialSkip, deferAutoExtraction, maybeResumePendingAutoExtraction, clearPendingAutoExtraction, getPendingAutoExtraction: () => ({ ...pendingAutoExtraction }), getIsHostGenerationRunning: () => isHostGenerationRunning, preparePlannerRecallHandoff, runPlannerRecallForEna, getGraphPersistenceState: () => graphPersistenceState, setGraphPersistenceState: (value = {}) => { graphPersistenceState = { ...graphPersistenceState, ...(value || {}) }; return graphPersistenceState; } };`,
|
||||
`${snippet}\nresult = { hashRecallInput, buildPreGenerationRecallKey, buildGenerationAfterCommandsRecallInput, buildNormalGenerationRecallInput, cleanupGenerationRecallTransactions, buildGenerationRecallTransactionId, beginGenerationRecallTransaction, markGenerationRecallTransactionHookState, shouldRunRecallForTransaction, createGenerationRecallContext, onGenerationStarted, onGenerationEnded, onGenerationAfterCommands, onBeforeCombinePrompts, applyFinalRecallInjectionForGeneration, persistRecallInjectionRecord, ensurePersistedRecallRecordForGeneration, findRecentGenerationRecallTransactionForChat, getGenerationRecallTransactionResult, generationRecallTransactions, freezeHostGenerationInputSnapshot, consumeHostGenerationInputSnapshot, getPendingHostGenerationInputSnapshot, clearPendingHostGenerationInputSnapshot, recordRecallSendIntent, clearPendingRecallSendIntent, recordRecallSentUserMessage, getPendingRecallSendIntent: () => pendingRecallSendIntent, getLastRecallSentUserMessage: () => lastRecallSentUserMessage, getCurrentGenerationTrivialSkip, markCurrentGenerationTrivialSkip, clearCurrentGenerationTrivialSkip, consumeCurrentGenerationTrivialSkip, deferAutoExtraction, maybeResumePendingAutoExtraction, clearPendingAutoExtraction, getPendingAutoExtraction: () => ({ ...pendingAutoExtraction }), getIsHostGenerationRunning: () => isHostGenerationRunning, preparePlannerRecallHandoff, runPlannerRecallForEna, getGraphPersistenceState: () => graphPersistenceState, setGraphPersistenceState: (value = {}) => { graphPersistenceState = { ...graphPersistenceState, ...(value || {}) }; return graphPersistenceState; } };`,
|
||||
context,
|
||||
{ filename: indexPath },
|
||||
);
|
||||
|
||||
@@ -38,6 +38,40 @@ Object.assign(harness.settings, {
|
||||
recallEnabled: true,
|
||||
});
|
||||
|
||||
harness.chat = [
|
||||
{ is_user: true, mes: "楼层里的稳定用户输入" },
|
||||
{ is_user: false, mes: "好的。", is_system: false },
|
||||
];
|
||||
|
||||
const persistedWriteResult = harness.result.persistRecallInjectionRecord({
|
||||
recallInput: {
|
||||
userMessage: "发送前捕获的权威输入",
|
||||
source: "send-intent",
|
||||
hookName: "GENERATION_AFTER_COMMANDS",
|
||||
targetUserMessageIndex: 0,
|
||||
authoritativeInputUsed: true,
|
||||
boundUserFloorText: "楼层里的稳定用户输入",
|
||||
},
|
||||
result: {
|
||||
selectedNodeIds: ["node-write-1"],
|
||||
},
|
||||
injectionText: "注入:楼层里的稳定用户输入",
|
||||
tokenEstimate: 8,
|
||||
});
|
||||
assert.ok(persistedWriteResult?.record, "persistRecallInjectionRecord should write a record");
|
||||
assert.equal(
|
||||
persistedWriteResult.record.authoritativeInputUsed,
|
||||
true,
|
||||
"initial persisted record should keep authoritativeInputUsed",
|
||||
);
|
||||
assert.equal(
|
||||
persistedWriteResult.record.boundUserFloorText,
|
||||
"楼层里的稳定用户输入",
|
||||
"initial persisted record should keep boundUserFloorText",
|
||||
);
|
||||
|
||||
console.log(" ✓ persistRecallInjectionRecord stores authoritative input metadata");
|
||||
|
||||
// Set up chat: user + assistant
|
||||
harness.chat = [
|
||||
{ is_user: true, mes: "去摩耶山看夜景" },
|
||||
@@ -118,10 +152,77 @@ assert.equal(
|
||||
|
||||
console.log(" ✓ ensurePersistedRecallRecordForGeneration overwrites record with empty recallInput");
|
||||
|
||||
harness.chat = [
|
||||
{ is_user: true, mes: "稳定楼层文本" },
|
||||
{ is_user: false, mes: "好的。", is_system: false },
|
||||
];
|
||||
const staleMetadataRecord = buildPersistedRecallRecord({
|
||||
injectionText: "注入:稳定楼层文本",
|
||||
selectedNodeIds: ["node-stale-meta"],
|
||||
recallInput: "发送前捕获文本",
|
||||
recallSource: "send-intent",
|
||||
hookName: "GENERATION_AFTER_COMMANDS",
|
||||
tokenEstimate: 4,
|
||||
manuallyEdited: false,
|
||||
});
|
||||
writePersistedRecallToUserMessage(harness.chat, 0, staleMetadataRecord);
|
||||
const staleMetadataEnsureResult = harness.result.ensurePersistedRecallRecordForGeneration({
|
||||
generationType: "regenerate",
|
||||
recallResult: {
|
||||
status: "completed",
|
||||
didRecall: true,
|
||||
ok: true,
|
||||
injectionText: "注入:稳定楼层文本",
|
||||
selectedNodeIds: ["node-stale-meta"],
|
||||
recallInput: "发送前捕获文本",
|
||||
source: "send-intent",
|
||||
hookName: "GENERATION_AFTER_COMMANDS",
|
||||
authoritativeInputUsed: false,
|
||||
boundUserFloorText: "稳定楼层文本",
|
||||
},
|
||||
transaction: {
|
||||
frozenRecallOptions: {
|
||||
generationType: "regenerate",
|
||||
targetUserMessageIndex: 0,
|
||||
overrideUserMessage: "稳定楼层文本",
|
||||
overrideSource: "chat-last-user",
|
||||
authoritativeInputUsed: false,
|
||||
boundUserFloorText: "稳定楼层文本",
|
||||
},
|
||||
},
|
||||
recallOptions: {
|
||||
generationType: "regenerate",
|
||||
targetUserMessageIndex: 0,
|
||||
overrideUserMessage: "稳定楼层文本",
|
||||
authoritativeInputUsed: false,
|
||||
boundUserFloorText: "稳定楼层文本",
|
||||
},
|
||||
hookName: "GENERATION_AFTER_COMMANDS",
|
||||
});
|
||||
assert.equal(
|
||||
staleMetadataEnsureResult.persisted,
|
||||
true,
|
||||
"ensure should rewrite records whose metadata is stale even when text/nodeIds match",
|
||||
);
|
||||
const repairedMetadataRecord = readPersistedRecallFromUserMessage(harness.chat, 0);
|
||||
assert.equal(
|
||||
repairedMetadataRecord.boundUserFloorText,
|
||||
"稳定楼层文本",
|
||||
"ensure should repair missing boundUserFloorText",
|
||||
);
|
||||
|
||||
console.log(" ✓ ensurePersistedRecallRecordForGeneration repairs stale metadata");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 2. ensurePersistedRecallRecordForGeneration: populated recallInput skip
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
harness.chat = [
|
||||
{ is_user: true, mes: "去摩耶山看夜景" },
|
||||
{ is_user: false, mes: "好的,我们出发吧。", is_system: false },
|
||||
];
|
||||
writePersistedRecallToUserMessage(harness.chat, 0, afterRecord);
|
||||
|
||||
// Now the record has proper recallInput — calling ensure again should skip
|
||||
const ensureResult2 = harness.result.ensurePersistedRecallRecordForGeneration({
|
||||
generationType: "regenerate",
|
||||
@@ -138,6 +239,56 @@ assert.equal(
|
||||
|
||||
console.log(" ✓ ensurePersistedRecallRecordForGeneration skips when recallInput is populated");
|
||||
|
||||
harness.chat = [
|
||||
{ is_user: true, mes: "继续写摩耶山夜景" },
|
||||
{ is_user: false, mes: "前一次回复。", is_system: false },
|
||||
];
|
||||
harness.result.cleanupGenerationRecallTransactions(Date.now() + 60000);
|
||||
const afterCommandsRecallOptions = harness.result.buildGenerationAfterCommandsRecallInput(
|
||||
"regenerate",
|
||||
{},
|
||||
harness.chat,
|
||||
);
|
||||
const afterCommandsContext = harness.result.createGenerationRecallContext({
|
||||
hookName: "GENERATION_AFTER_COMMANDS",
|
||||
generationType: "regenerate",
|
||||
recallOptions: afterCommandsRecallOptions,
|
||||
});
|
||||
assert.ok(
|
||||
afterCommandsContext.transaction,
|
||||
"after-commands should create a history transaction",
|
||||
);
|
||||
harness.result.markGenerationRecallTransactionHookState(
|
||||
afterCommandsContext.transaction,
|
||||
"GENERATION_AFTER_COMMANDS",
|
||||
"completed",
|
||||
);
|
||||
const beforeCombineNormalFallback = harness.result.buildNormalGenerationRecallInput(
|
||||
harness.chat,
|
||||
);
|
||||
const beforeCombineContext = harness.result.createGenerationRecallContext({
|
||||
hookName: "GENERATE_BEFORE_COMBINE_PROMPTS",
|
||||
generationType: "normal",
|
||||
recallOptions: beforeCombineNormalFallback,
|
||||
});
|
||||
assert.equal(
|
||||
beforeCombineContext.transaction,
|
||||
afterCommandsContext.transaction,
|
||||
"before-combine should reuse the existing history transaction despite normal fallback input",
|
||||
);
|
||||
assert.equal(
|
||||
beforeCombineContext.generationType,
|
||||
"regenerate",
|
||||
"before-combine should keep the transaction's history generation type",
|
||||
);
|
||||
assert.equal(
|
||||
beforeCombineContext.shouldRun,
|
||||
false,
|
||||
"before-combine should not run recall again after after-commands completed",
|
||||
);
|
||||
|
||||
console.log(" ✓ before-combine reuses existing history transaction");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 3. runRecallController: regenerate reuses persisted record
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
@@ -151,8 +302,8 @@ const rerollChat = [
|
||||
const validRecord = buildPersistedRecallRecord({
|
||||
injectionText: "注入:明日去摩耶山看夜景",
|
||||
selectedNodeIds: ["node-a"],
|
||||
recallInput: "明日去摩耶山看夜景",
|
||||
recallSource: "chat-tail-user",
|
||||
recallInput: "发送意图中的扩展文本,不等于当前用户楼层",
|
||||
recallSource: "send-intent",
|
||||
hookName: "GENERATION_AFTER_COMMANDS",
|
||||
tokenEstimate: 5,
|
||||
manuallyEdited: false,
|
||||
|
||||
Reference in New Issue
Block a user