From 3a157f5fc16bba1ce7f34b8a27e59ff43ed386fa Mon Sep 17 00:00:00 2001 From: Youzini-afk <13153778771cx@gmail.com> Date: Thu, 30 Apr 2026 22:17:14 +0800 Subject: [PATCH] fix(recall): reuse user-floor cache without target index --- retrieval/recall-controller.js | 56 +++++++++++++++++++++------ tests/recall-reroll-reuse.mjs | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 11 deletions(-) diff --git a/retrieval/recall-controller.js b/retrieval/recall-controller.js index be93d12..8ae9dbe 100644 --- a/retrieval/recall-controller.js +++ b/retrieval/recall-controller.js @@ -94,30 +94,20 @@ function buildPersistedRecallReuseResult(record = {}) { function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) { const generationType = String(recallInput?.generationType || "normal").trim() || "normal"; - const targetUserMessageIndex = Number.isFinite(recallInput?.targetUserMessageIndex) + let targetUserMessageIndex = Number.isFinite(recallInput?.targetUserMessageIndex) ? Math.floor(Number(recallInput.targetUserMessageIndex)) : null; - if (!Number.isFinite(targetUserMessageIndex)) return null; - - const targetMessage = Array.isArray(chat) ? chat[targetUserMessageIndex] : null; - if (!targetMessage?.is_user) return null; const readPersistedRecallFromUserMessage = runtime.readPersistedRecallFromUserMessage; if (typeof readPersistedRecallFromUserMessage !== "function") return null; - const record = readPersistedRecallFromUserMessage(chat, targetUserMessageIndex); - if (!record?.injectionText) return null; - const normalizeText = (value = "") => typeof runtime.normalizeRecallInputText === "function" ? runtime.normalizeRecallInputText(value) : String(value ?? "") .replace(/\r\n/g, "\n") .trim(); - const currentUserFloorText = normalizeText(targetMessage?.mes || ""); const currentRecallInputText = normalizeText(recallInput?.userMessage || ""); - const recordRecallInput = normalizeText(record?.recallInput || ""); - const boundUserFloorText = normalizeText(record?.boundUserFloorText || ""); const recallSource = String(recallInput?.source || "").trim(); const activeInputSources = new Set([ "send-intent", @@ -129,6 +119,50 @@ function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) { ]); const isActiveInputSource = activeInputSources.has(recallSource); + if (!Number.isFinite(targetUserMessageIndex)) { + if (!currentRecallInputText || isActiveInputSource || !Array.isArray(chat)) { + return null; + } + for (let index = chat.length - 1; index >= 0; index--) { + const message = chat[index]; + if (!message?.is_user) continue; + const candidateRecord = readPersistedRecallFromUserMessage(chat, index); + if (!candidateRecord?.injectionText) continue; + const candidateUserFloorText = normalizeText(message?.mes || ""); + const candidateBoundUserFloorText = normalizeText( + candidateRecord?.boundUserFloorText || "", + ); + if ( + candidateBoundUserFloorText && + candidateUserFloorText !== candidateBoundUserFloorText + ) { + continue; + } + const candidateRecallInput = normalizeText(candidateRecord?.recallInput || ""); + if ( + currentRecallInputText === candidateUserFloorText || + (candidateBoundUserFloorText && + currentRecallInputText === candidateBoundUserFloorText) || + (candidateRecallInput && currentRecallInputText === candidateRecallInput) + ) { + targetUserMessageIndex = index; + break; + } + } + } + + if (!Number.isFinite(targetUserMessageIndex)) return null; + + const targetMessage = Array.isArray(chat) ? chat[targetUserMessageIndex] : null; + if (!targetMessage?.is_user) return null; + + const record = readPersistedRecallFromUserMessage(chat, targetUserMessageIndex); + if (!record?.injectionText) return null; + + const currentUserFloorText = normalizeText(targetMessage?.mes || ""); + const recordRecallInput = normalizeText(record?.recallInput || ""); + const boundUserFloorText = normalizeText(record?.boundUserFloorText || ""); + const matchesBoundUserFloor = Boolean( currentUserFloorText && boundUserFloorText && diff --git a/tests/recall-reroll-reuse.mjs b/tests/recall-reroll-reuse.mjs index 21279a3..28696ad 100644 --- a/tests/recall-reroll-reuse.mjs +++ b/tests/recall-reroll-reuse.mjs @@ -708,4 +708,73 @@ assert.equal( console.log(" ✓ runRecallController reuses user-floor record with empty recallInput"); // ═══════════════════════════════════════════════════════════════ +// 5. runRecallController: normal generation below an assistant reuses user-floor record +// ═══════════════════════════════════════════════════════════════ + +const assistantTailChat = [ + { is_user: true, mes: "今晚去海边看烟花" }, + { is_user: false, mes: "好,我会准备好相机。", is_system: false }, +]; +const assistantTailRecord = buildPersistedRecallRecord({ + injectionText: "注入:今晚去海边看烟花", + selectedNodeIds: ["node-fireworks"], + recallInput: "今晚去海边看烟花", + recallSource: "chat-latest-user", + hookName: "GENERATION_AFTER_COMMANDS", + tokenEstimate: 4, + manuallyEdited: false, + boundUserFloorText: "今晚去海边看烟花", +}); +writePersistedRecallToUserMessage(assistantTailChat, 0, assistantTailRecord); + +let assistantTailRetrieveCalled = false; +const assistantTailRuntime = { + ...rerollRuntime, + getContext: () => ({ chat: assistantTailChat, chatId: "chat-assistant-tail" }), + readPersistedRecallFromUserMessage, + retrieve: async () => { + assistantTailRetrieveCalled = true; + return { + injectionText: "fresh recall should not run", + selectedNodeIds: ["node-fresh"], + }; + }, + resolveRecallInput: (chat, limit, override) => ({ + userMessage: normalizeRecallInputText( + override?.overrideUserMessage || override?.userMessage || "今晚去海边看烟花", + ), + generationType: String(override?.generationType || "normal"), + targetUserMessageIndex: Number.isFinite(override?.targetUserMessageIndex) + ? override.targetUserMessageIndex + : null, + source: override?.overrideSource || "chat-latest-user", + sourceLabel: override?.overrideSourceLabel || "最近用户消息", + reason: "assistant-tail-normal-generation", + authoritativeInputUsed: Boolean(override?.authoritativeInputUsed), + boundUserFloorText: normalizeRecallInputText( + override?.boundUserFloorText || "今晚去海边看烟花", + ), + recentMessages: [], + hookName: override?.hookName || "", + deliveryMode: "immediate", + }), +}; + +const assistantTailResult = await runRecallController(assistantTailRuntime, { + overrideUserMessage: "今晚去海边看烟花", + generationType: "normal", + overrideSource: "chat-latest-user", + hookName: "GENERATION_AFTER_COMMANDS", + deliveryMode: "immediate", +}); + +assert.equal(assistantTailResult.status, "completed"); +assert.equal( + assistantTailRetrieveCalled, + false, + "normal generation below an assistant should find and reuse the matching user-floor persisted recall", +); +assert.equal(assistantTailResult.reason, "persisted-user-floor-reused"); + +console.log(" ✓ runRecallController reuses user-floor record below assistant tail"); console.log("recall-reroll-reuse tests passed");