From baff705e28f2e5fa52ef06176690f86c7dce076a Mon Sep 17 00:00:00 2001 From: youzini Date: Sun, 31 May 2026 12:28:47 +0000 Subject: [PATCH] refactor(test): migrate generation-recall harness off index.js slicing (Phase 4g) --- tests/helpers/generation-recall-harness.mjs | 1455 +++++++++++++------ tests/index-slicing-ratchet.mjs | 1 - 2 files changed, 996 insertions(+), 460 deletions(-) diff --git a/tests/helpers/generation-recall-harness.mjs b/tests/helpers/generation-recall-harness.mjs index b3020a6..eb52035 100644 --- a/tests/helpers/generation-recall-harness.mjs +++ b/tests/helpers/generation-recall-harness.mjs @@ -1,7 +1,3 @@ -import fs from "node:fs/promises"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import vm from "node:vm"; import { onBeforeCombinePromptsController, onGenerationAfterCommandsController, @@ -11,53 +7,33 @@ import { } from "../../host/event-binding.js"; import { isSystemMessageForExtraction } from "../../maintenance/chat-history.js"; import { resolveAutoExtractionPlanController } from "../../maintenance/extraction-controller.js"; -import { - GRAPH_LOAD_STATES, - GRAPH_METADATA_KEY, - GRAPH_PERSISTENCE_META_KEY, - MODULE_NAME, -} from "../../graph/graph-persistence.js"; +import { GRAPH_LOAD_STATES, MODULE_NAME } from "../../graph/graph-persistence.js"; import { getSmartTriggerDecision } from "../../maintenance/smart-trigger.js"; import { buildPersistedRecallRecord, bumpPersistedRecallGenerationCount, readPersistedRecallFromUserMessage, resolveFinalRecallInjectionSource, + resolveGenerationTargetUserMessageIndex, writePersistedRecallToUserMessage, } from "../../retrieval/recall-persistence.js"; import { - createGraphPersistenceState, + buildRecallRecentMessagesController, + getRecallUserMessageSourceLabelController, +} from "../../retrieval/recall-controller.js"; +import { + clampInt, createRecallInputRecord, - createRecallRunResult, - createUiStatus, - getGenerationRecallHookStateFromResult, - getRecallHookLabel, - getStageNoticeDuration, - getStageNoticeTitle, + createUiStatus, + formatRecallContextLine, + getGenerationRecallHookStateFromResult, hashRecallInput, isFreshRecallInputRecord, - isTerminalGenerationRecallHookState, isTrivialUserInput, normalizeRecallInputText, - normalizeStageNoticeLevel, shouldRunRecallForTransaction, } from "../../ui/ui-status.js"; -import { - defaultSettings, - mergePersistedSettings, -} from "../../runtime/settings-defaults.js"; -import { - createDefaultAuthorityCapabilityState, - normalizeAuthoritySettings, - normalizeAuthorityCapabilityState, - probeAuthorityCapabilities, -} from "../../runtime/authority-capabilities.js"; -import { - createAuthorityBrowserState, - getAuthorityBrowserStateSnapshot, - normalizeAuthorityBrowserState, - recordAuthorityAcceptedRevision, -} from "../../sync/authority-browser-state.js"; +import { defaultSettings, mergePersistedSettings } from "../../runtime/settings-defaults.js"; import { consumeRerollRecallReuseMarker, createRerollRecallReuseMarker, @@ -69,435 +45,996 @@ import { createFinalRecallInjection } from "../../runtime/final-recall-injection import { createAutoExtractionDefer } from "../../runtime/auto-extraction-defer.js"; import { runPlannerRecallForEnaController } from "../../runtime/planner-recall-controller.js"; -const moduleDir = path.dirname(fileURLToPath(import.meta.url)); -const indexPath = path.resolve(moduleDir, "../../index.js"); +const RECALL_INPUT_RECORD_TTL_MS = 60000; +const TRIVIAL_GENERATION_SKIP_TTL_MS = 60000; +const GENERATION_RECALL_TRANSACTION_TTL_MS = 15000; +const PLANNER_RECALL_HANDOFF_TTL_MS = GENERATION_RECALL_TRANSACTION_TTL_MS; +const GENERATION_RECALL_HOOK_BRIDGE_MS = 1200; +const AUTO_EXTRACTION_DEFER_RETRY_DELAYS_MS = [120, 320, 800, 1600, 2800]; +const AUTO_EXTRACTION_HOST_SETTLE_MS = 120; -export function createGenerationRecallHarness(options = {}) { - const { realApplyFinal = false } = options; - return fs.readFile(indexPath, "utf8").then((source) => { - const start = source.indexOf("const RECALL_INPUT_RECORD_TTL_MS = 60000;"); - const end = source.indexOf( - 'function onMessageReceived(messageId = null, type = "") {', - ); - const endFallback = source.indexOf("async function runExtraction()"); - const resolvedEnd = end >= 0 ? end : endFallback; - if (start < 0 || resolvedEnd < 0 || resolvedEnd <= start) { - throw new Error("无法从 index.js 提取生成召回事务定义"); +function createTrivialRecallSkipSentinel(reason = "") { + return { + __trivialSkip: true, + trivialReason: String(reason || ""), + }; +} + +function findLatestUserChatMessageWithIndex(chat) { + if (!Array.isArray(chat)) return null; + for (let index = chat.length - 1; index >= 0; index--) { + const message = chat[index]; + if (isSystemMessageForExtraction(message, { index, chat })) continue; + if (message?.is_user) return { message, index }; + } + return null; +} + +function getLastNonSystemChatMessage(chat) { + if (!Array.isArray(chat)) return null; + for (let index = chat.length - 1; index >= 0; index--) { + const message = chat[index]; + if (!isSystemMessageForExtraction(message, { index, chat })) { + return message; } - const snippet = source - .slice(start, resolvedEnd) - .replace(/^export\s+/gm, ""); - const context = { - console, - Date, - Map, - setTimeout, - clearTimeout, - __sendTextareaValue: "", - document: { - getElementById(id) { - if ( - id === "send_textarea" && - typeof context.__sendTextareaValue === "string" && - context.__sendTextareaValue - ) { - return { value: context.__sendTextareaValue }; - } - return null; - }, - }, - result: null, - currentGraph: {}, - pendingRecallSendIntent: createRecallInputRecord(), - lastRecallSentUserMessage: createRecallInputRecord(), - pendingHostGenerationInputSnapshot: createRecallInputRecord(), - _panelModule: null, - defaultSettings, - mergePersistedSettings, - createDefaultAuthorityCapabilityState, - normalizeAuthoritySettings, - normalizeAuthorityCapabilityState, - probeAuthorityCapabilities, - createAuthorityBrowserState, - getAuthorityBrowserStateSnapshot, - normalizeAuthorityBrowserState, - recordAuthorityAcceptedRevision, - consumeRerollRecallReuseMarker, - createRerollRecallReuseMarker, - createRecallInputState, - createRerollRecallInput, - createGenerationRecallTransactions, - createFinalRecallInjection, - createAutoExtractionDefer, - runPlannerRecallForEnaController, - // retrieve is only reached on the non-trivial, graph-non-empty recall - // path; the planner-recall tests exercise trivial/graph-empty early - // returns, so a recording stub is sufficient and avoids pulling the host - // llm/retriever chain into the test environment. - retrieve: (...args) => { - context.retrieveCalls ||= []; - context.retrieveCalls.push(args); - return { entries: [], items: [], nodes: [] }; - }, - settings: {}, - graphPersistenceState: createGraphPersistenceState(), - extension_settings: { [MODULE_NAME]: {} }, - extension_prompt_types: { - NONE: 0, - BEFORE_PROMPT: 1, - IN_PROMPT: 2, - IN_CHAT: 3, - }, - extension_prompt_roles: { - SYSTEM: 0, - USER: 1, - ASSISTANT: 2, - }, - clampInt: (value, fallback = 0, min = 0, max = 9999) => { - const numeric = Number(value); - if (!Number.isFinite(numeric)) return fallback; - return Math.min(max, Math.max(min, Math.trunc(numeric))); - }, - getHostAdapter: () => null, - migrateLegacyTaskProfiles: (settings = {}) => ({ - taskProfilesVersion: settings?.taskProfilesVersion || 0, - taskProfiles: settings?.taskProfiles || {}, - }), - migratePerTaskRegexToGlobal: (settings = {}) => ({ - changed: false, - settings, - }), - refreshPanelLiveStateController: () => { - context.refreshPanelCalls += 1; - }, - isRecalling: false, - getCurrentChatId: () => "chat-main", - normalizeChatIdCandidate: (value = "") => String(value ?? "").trim(), - normalizeRecallInputText: (text = "") => String(text || "").trim(), - isTrivialUserInput, - getAssistantTurns: (chat = []) => - chat.flatMap((message, index) => - !message?.is_user && - !isSystemMessageForExtraction(message, { index, chat }) - ? [index] - : [], - ), - isSystemMessageForExtraction, - getLatestUserChatMessage: (chat = []) => - [...chat].reverse().find((message) => message?.is_user) || null, - getLastNonSystemChatMessage: (chat = []) => - [...chat] - .map((message, index) => ({ message, index })) - .reverse() - .find( - ({ message, index }) => - !isSystemMessageForExtraction(message, { index, chat }), - )?.message || null, - getSmartTriggerDecision, - getSendTextareaValue: () => context.__sendTextareaValue, - getRecallUserMessageSourceLabel: (source = "") => source, - getRecallUserMessageSourceLabelController: (source = "") => source, - formatInjection: (result = null) => - String(result?.injectionText || result?.memoryBlock || ""), - getSchema: () => [], - buildRecallRecentMessages: ( - chat = [], - _limit, - syntheticUserMessage = "", - ) => - syntheticUserMessage - ? [...chat, { is_user: true, mes: syntheticUserMessage }] - : [...chat], - getContext: () => ({ - chatId: "chat-main", - chat: context.chat, - }), - chat: [], - runRecallCalls: [], - runExtractionCalls: [], - extractionIssues: [], - applyFinalCalls: [], - moduleInjectionCalls: [], - recordedInjectionSnapshots: [], - refreshPanelCalls: 0, - hideScheduleCalls: [], - isExtracting: false, - isRecoveringHistory: false, - isRestoreLockActive: () => false, - isAssistantChatMessage: (message) => - Boolean(message) && !message.is_user && !message.is_system, - createRecallInputRecord, - createRecallRunResult, - hashRecallInput, - isFreshRecallInputRecord, - isTerminalGenerationRecallHookState, - shouldRunRecallForTransaction, - getGenerationRecallHookStateFromResult, - createUiStatus, - createGraphPersistenceState, - getRecallHookLabel, - getStageNoticeTitle, - getStageNoticeDuration, - normalizeStageNoticeLevel, - MODULE_NAME, - GRAPH_LOAD_STATES, - GRAPH_METADATA_KEY, - GRAPH_PERSISTENCE_META_KEY, - resolveAutoExtractionPlanController, - onBeforeCombinePromptsController, - onGenerationAfterCommandsController, - onGenerationStartedController, - readPersistedRecallFromUserMessage, - writePersistedRecallToUserMessage, - buildPersistedRecallRecord, - resolveFinalRecallInjectionSource, - bumpPersistedRecallGenerationCount, - applyModuleInjectionPrompt: (text = "") => { - const normalizedText = String(text || ""); - context.moduleInjectionCalls.push(normalizedText); - return { - applied: Boolean(normalizedText.trim()), - source: normalizedText.trim() ? "module-injection" : "rewrite-clear", - mode: normalizedText.trim() ? "module-injection" : "rewrite-clear", - }; - }, - getSettings: () => context.settings, - $: () => ({}), - triggerChatMetadataSave: () => { - context.metadataSaveCalls += 1; - return "debounced"; - }, - toastr: { - success() {}, - warning() {}, - info() {}, - error() {}, - }, - openRecallSidebar() {}, - removePersistedRecallFromUserMessage: () => false, - markPersistedRecallManualEdit: () => null, - createRecallCardElement: () => null, - updateRecallCardData: () => {}, - createRecallMessageUiController: () => ({ - refreshPersistedRecallMessageUi: () => ({ - status: "missing_recall_record", - renderedCount: 0, - persistedRecordCount: 0, - waitingMessageIndices: [], - anchorFailureIndices: [], - skippedNonUserIndices: [], - }), - schedulePersistedRecallMessageUiRefresh: () => { - context.recallUiRefreshCalls += 1; - }, - cleanupPersistedRecallMessageUi: () => {}, - resolveMessageIndexFromElement: () => null, - resolveRecallCardAnchor: () => null, - }), - refreshPanelLiveState: () => { - context.refreshPanelCalls += 1; - }, - recordInjectionSnapshot: (_kind, snapshot = {}) => { - context.recordedInjectionSnapshots.push({ ...snapshot }); - }, - recordMessageTraceSnapshot() {}, - schedulePersistedRecallMessageUiRefresh: () => { - context.recallUiRefreshCalls += 1; - }, - getMessageHideSettings: () => ({}), - getHideRuntimeAdapters: () => ({}), - scheduleHideSettingsApply: (...args) => { - context.hideScheduleCalls.push(args); - }, - estimateTokens: (text = "") => - normalizeRecallInputText(text) - .split(/\s+/) - .filter(Boolean).length || (normalizeRecallInputText(text) ? 1 : 0), - resolveGenerationTargetUserMessageIndex: ( - chat = [], - { generationType } = {}, - ) => { - const normalized = String(generationType || "normal"); - if (!Array.isArray(chat) || chat.length === 0) return null; - if (normalized === "normal") - return chat[chat.length - 1]?.is_user ? chat.length - 1 : null; - for (let index = chat.length - 1; index >= 0; index--) - if (chat[index]?.is_user) return index; + } + return null; +} + +function normalizeRecallNodeIdList(nodeIds = []) { + if (!Array.isArray(nodeIds)) return []; + return nodeIds + .map((entry) => { + if (typeof entry === "string" || typeof entry === "number") { + return String(entry).trim(); + } + if (entry && typeof entry === "object") { + return String(entry.id || entry.nodeId || "").trim(); + } + return ""; + }) + .filter(Boolean); +} + +function areRecallNodeIdListsEqual(left = [], right = []) { + const normalizedLeft = normalizeRecallNodeIdList(left); + const normalizedRight = normalizeRecallNodeIdList(right); + if (normalizedLeft.length !== normalizedRight.length) return false; + for (let index = 0; index < normalizedLeft.length; index++) { + if (normalizedLeft[index] !== normalizedRight[index]) return false; + } + return true; +} + +function buildRecallTargetCandidateHashes(candidateTexts = []) { + const hashes = new Set(); + for (const text of candidateTexts) { + const normalized = normalizeRecallInputText(text); + if (!normalized) continue; + const hash = hashRecallInput(normalized); + if (hash) hashes.add(hash); + } + return hashes; +} + +function doesChatUserMessageMatchRecallCandidates(message, candidateHashes) { + if (!message?.is_user || !(candidateHashes instanceof Set) || !candidateHashes.size) { + return false; + } + const normalizedMessage = normalizeRecallInputText(message?.mes || ""); + if (!normalizedMessage) return false; + return candidateHashes.has(hashRecallInput(normalizedMessage)); +} + +function resolveRecallPersistenceTargetUserMessageIndex( + chat, + { + generationType = "normal", + explicitTargetUserMessageIndex = null, + candidateTexts = [], + preferredRecord = null, + } = {}, +) { + if (!Array.isArray(chat) || chat.length === 0) return null; + const normalizedGenerationType = + String(generationType || "normal").trim() || "normal"; + + const explicitIndex = Number.isFinite(explicitTargetUserMessageIndex) + ? Math.floor(Number(explicitTargetUserMessageIndex)) + : null; + if (Number.isFinite(explicitIndex) && chat[explicitIndex]?.is_user) { + return explicitIndex; + } + + const candidateHashes = buildRecallTargetCandidateHashes(candidateTexts); + const latestUserIndex = resolveGenerationTargetUserMessageIndex(chat, { + generationType: "history", + }); + + const hasFreshPreferredRecord = isFreshRecallInputRecord(preferredRecord); + const preferredMessageId = + hasFreshPreferredRecord && Number.isFinite(preferredRecord?.messageId) + ? Math.floor(Number(preferredRecord.messageId)) + : null; + + if ( + Number.isFinite(preferredMessageId) && + chat[preferredMessageId]?.is_user && + (!candidateHashes.size || + doesChatUserMessageMatchRecallCandidates( + chat[preferredMessageId], + candidateHashes, + )) + ) { + return preferredMessageId; + } + + if ( + candidateHashes.size && + Number.isFinite(latestUserIndex) && + chat[latestUserIndex]?.is_user && + doesChatUserMessageMatchRecallCandidates( + chat[latestUserIndex], + candidateHashes, + ) + ) { + return latestUserIndex; + } + + if (hasFreshPreferredRecord && candidateHashes.size) { + for (let index = chat.length - 1; index >= 0; index--) { + const message = chat[index]; + if (doesChatUserMessageMatchRecallCandidates(message, candidateHashes)) { + return index; + } + } + } + + if ( + normalizedGenerationType === "normal" && + Number.isFinite(latestUserIndex) && + chat[latestUserIndex]?.is_user + ) { + return latestUserIndex; + } + + if ( + normalizedGenerationType === "normal" && + Number.isFinite(preferredMessageId) && + chat[preferredMessageId]?.is_user + ) { + return preferredMessageId; + } + + if ( + normalizedGenerationType !== "normal" && + Number.isFinite(latestUserIndex) && + chat[latestUserIndex]?.is_user + ) { + return latestUserIndex; + } + + return null; +} + +export async function createGenerationRecallHarness(options = {}) { + const { realApplyFinal = false } = options; + + let pendingRecallSendIntent = createRecallInputRecord(); + let lastRecallSentUserMessage = createRecallInputRecord(); + let pendingHostGenerationInputSnapshot = createRecallInputRecord(); + let isHostGenerationRunning = false; + let lastHostGenerationEndedAt = 0; + let skipBeforeCombineRecallUntil = 0; + let graphPersistenceState = { + loadState: GRAPH_LOAD_STATES.LOADED, + dbReady: true, + chatId: "chat-main", + restoreLock: null, + }; + let runtimeStatus = createUiStatus("待命", "准备就绪", "idle"); + let lastInjectionContent = ""; + + const harness = { + console, + Date, + Map, + setTimeout, + clearTimeout, + __sendTextareaValue: "", + document: { + getElementById(id) { + if ( + id === "send_textarea" && + typeof harness.__sendTextareaValue === "string" && + harness.__sendTextareaValue + ) { + return { value: harness.__sendTextareaValue }; + } return null; }, - metadataSaveCalls: 0, - recallUiRefreshCalls: 0, + }, + result: null, + currentGraph: {}, + extension_settings: { [MODULE_NAME]: {} }, + settings: {}, + chat: [], + runRecallCalls: [], + runExtractionCalls: [], + extractionIssues: [], + applyFinalCalls: [], + moduleInjectionCalls: [], + recordedInjectionSnapshots: [], + refreshPanelCalls: 0, + hideScheduleCalls: [], + metadataSaveCalls: 0, + recallUiRefreshCalls: 0, + retrieveCalls: [], + isExtracting: false, + isRecoveringHistory: false, + }; + + const normalizeChatIdCandidate = (value = "") => String(value ?? "").trim(); + const getCurrentChatId = () => "chat-main"; + const getContext = () => ({ + chatId: "chat-main", + chat: harness.chat, + }); + const getSettings = () => { + const merged = mergePersistedSettings({ + ...(harness.settings || {}), + ...(harness.extension_settings?.[MODULE_NAME] || {}), + }); + harness.settings = merged; + harness.extension_settings[MODULE_NAME] = merged; + return merged; + }; + const getSendTextareaValue = () => harness.__sendTextareaValue; + const triggerChatMetadataSave = () => { + harness.metadataSaveCalls += 1; + return "debounced"; + }; + const refreshPanelLiveState = () => { + harness.refreshPanelCalls += 1; + }; + const schedulePersistedRecallMessageUiRefresh = () => { + harness.recallUiRefreshCalls += 1; + }; + const recordInjectionSnapshot = (_kind, snapshot = {}) => { + harness.recordedInjectionSnapshots.push({ ...snapshot }); + }; + const recordMessageTraceSnapshot = () => {}; + const applyModuleInjectionPrompt = (text = "") => { + const normalizedText = String(text || ""); + harness.moduleInjectionCalls.push(normalizedText); + return { + applied: Boolean(normalizedText.trim()), + source: normalizedText.trim() ? "module-injection" : "rewrite-clear", + mode: normalizedText.trim() ? "module-injection" : "rewrite-clear", }; - vm.createContext(context); - vm.runInContext( - `${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, prepareRerollRecallReuse, getPendingRerollRecallReuse, clearPendingRerollRecallReuse, recordRecallSendIntent, clearPendingRecallSendIntent, recordRecallSentUserMessage, getPendingRecallSendIntent: () => pendingRecallSendIntent, getLastRecallSentUserMessage: () => lastRecallSentUserMessage, getCurrentGenerationTrivialSkip, markCurrentGenerationTrivialSkip, clearCurrentGenerationTrivialSkip, consumeCurrentGenerationTrivialSkip, deferAutoExtraction, maybeResumePendingAutoExtraction, clearPendingAutoExtraction, getPendingAutoExtraction, getIsHostGenerationRunning: () => isHostGenerationRunning, preparePlannerRecallHandoff, runPlannerRecallForEna, getGraphPersistenceState: () => graphPersistenceState, setGraphPersistenceState: (value = {}) => { graphPersistenceState = { ...graphPersistenceState, ...(value || {}) }; return graphPersistenceState; } };`, - context, - { filename: indexPath }, + }; + const clearLiveRecallInjectionPromptForRewrite = () => { + try { + return ( + applyModuleInjectionPrompt("", getSettings()) || { + applied: false, + source: "rewrite-clear", + mode: "rewrite-clear", + } + ); + } catch (error) { + return { + applied: false, + source: "rewrite-clear-error", + mode: "rewrite-clear-error", + error: error instanceof Error ? error.message : String(error || ""), + }; + } + }; + const buildRecallRecentMessages = (chat, limit, syntheticUserMessage = "") => + buildRecallRecentMessagesController(chat, limit, syntheticUserMessage, { + formatRecallContextLine, + normalizeRecallInputText, + }); + const buildRecallRetrieveOptions = (settings, context) => ({ + topK: settings.recallTopK, + maxRecallNodes: settings.recallMaxNodes, + enableLLMRecall: settings.recallEnableLLM, + enableVectorPrefilter: settings.recallEnableVectorPrefilter, + enableGraphDiffusion: settings.recallEnableGraphDiffusion, + diffusionTopK: settings.recallDiffusionTopK, + llmCandidatePool: settings.recallLlmCandidatePool, + recallPrompt: undefined, + weights: { + graphWeight: settings.graphWeight, + vectorWeight: settings.vectorWeight, + importanceWeight: settings.importanceWeight, + }, + enableVisibility: settings.enableVisibility ?? false, + visibilityFilter: context.name2 || null, + enableCrossRecall: settings.enableCrossRecall ?? false, + enableProbRecall: settings.enableProbRecall ?? false, + probRecallChance: settings.probRecallChance ?? 0.15, + enableMultiIntent: settings.recallEnableMultiIntent ?? true, + multiIntentMaxSegments: settings.recallMultiIntentMaxSegments ?? 4, + enableContextQueryBlend: settings.recallEnableContextQueryBlend ?? true, + contextAssistantWeight: settings.recallContextAssistantWeight ?? 0.2, + contextPreviousUserWeight: settings.recallContextPreviousUserWeight ?? 0.1, + enableLexicalBoost: settings.recallEnableLexicalBoost ?? true, + lexicalWeight: settings.recallLexicalWeight ?? 0.18, + teleportAlpha: settings.recallTeleportAlpha ?? 0.15, + enableTemporalLinks: settings.recallEnableTemporalLinks ?? true, + temporalLinkStrength: settings.recallTemporalLinkStrength ?? 0.2, + enableDiversitySampling: settings.recallEnableDiversitySampling ?? true, + dppCandidateMultiplier: settings.recallDppCandidateMultiplier ?? 3, + dppQualityWeight: settings.recallDppQualityWeight ?? 1.0, + enableCooccurrenceBoost: settings.recallEnableCooccurrenceBoost ?? false, + cooccurrenceScale: settings.recallCooccurrenceScale ?? 0.1, + cooccurrenceMaxNeighbors: settings.recallCooccurrenceMaxNeighbors ?? 10, + enableResidualRecall: settings.recallEnableResidualRecall ?? false, + residualBasisMaxNodes: settings.recallResidualBasisMaxNodes ?? 24, + residualNmfTopics: settings.recallNmfTopics ?? 15, + residualNmfNoveltyThreshold: settings.recallNmfNoveltyThreshold ?? 0.4, + residualThreshold: settings.recallResidualThreshold ?? 0.3, + residualTopK: settings.recallResidualTopK ?? 5, + vectorQueryConcurrency: settings.vectorQueryConcurrency ?? 4, + authorityCandidateQueryConcurrency: settings.vectorQueryConcurrency ?? 4, + enableScopedMemory: settings.enableScopedMemory ?? true, + enablePovMemory: settings.enablePovMemory ?? true, + enableRegionScopedObjective: settings.enableRegionScopedObjective ?? true, + enableCognitiveMemory: settings.enableCognitiveMemory ?? true, + enableSpatialAdjacency: settings.enableSpatialAdjacency ?? true, + enableStoryTimeline: settings.enableStoryTimeline ?? true, + injectStoryTimeLabel: settings.injectStoryTimeLabel ?? true, + storyTimeSoftDirecting: settings.storyTimeSoftDirecting ?? true, + recallCharacterPovWeight: settings.recallCharacterPovWeight ?? 1.25, + recallUserPovWeight: settings.recallUserPovWeight ?? 1.05, + recallObjectiveCurrentRegionWeight: settings.recallObjectiveCurrentRegionWeight ?? 1.15, + recallObjectiveAdjacentRegionWeight: settings.recallObjectiveAdjacentRegionWeight ?? 0.9, + recallObjectiveGlobalWeight: settings.recallObjectiveGlobalWeight ?? 0.75, + injectUserPovMemory: settings.injectUserPovMemory ?? true, + injectObjectiveGlobalMemory: settings.injectObjectiveGlobalMemory ?? true, + injectLowConfidenceObjectiveMemory: settings.injectLowConfidenceObjectiveMemory ?? false, + activeRegion: + harness.currentGraph?.historyState?.activeRegion || + harness.currentGraph?.historyState?.lastExtractedRegion || + "", + activeStorySegmentId: harness.currentGraph?.historyState?.activeStorySegmentId || "", + activeStoryTimeLabel: harness.currentGraph?.historyState?.activeStoryTimeLabel || "", + activeCharacterPovOwner: harness.currentGraph?.historyState?.activeCharacterPovOwner || "", + activeUserPovOwner: harness.currentGraph?.historyState?.activeUserPovOwner || context.name1 || "", + }); + const retrieve = (...args) => { + harness.retrieveCalls.push(args); + return { entries: [], items: [], nodes: [] }; + }; + const formatInjection = (result = null) => + String(result?.injectionText || result?.memoryBlock || ""); + const getSchema = () => []; + const getEmbeddingConfig = () => ({}); + const createAbortError = (message = "aborted") => { + const error = new Error(message); + error.name = "AbortError"; + return error; + }; + const isGraphMetadataWriteAllowed = (loadState = graphPersistenceState.loadState) => + loadState === GRAPH_LOAD_STATES.LOADED || + loadState === GRAPH_LOAD_STATES.EMPTY_CONFIRMED; + const isGraphReadable = (loadState = graphPersistenceState.loadState) => + loadState === GRAPH_LOAD_STATES.LOADED || + loadState === GRAPH_LOAD_STATES.EMPTY_CONFIRMED || + loadState === GRAPH_LOAD_STATES.SHADOW_RESTORED || + (loadState === GRAPH_LOAD_STATES.BLOCKED && graphPersistenceState.shadowSnapshotUsed); + const isGraphReadableForRecall = (loadState = graphPersistenceState.loadState) => + isGraphReadable(loadState) || Boolean(harness.currentGraph?.nodes && harness.currentGraph?.edges); + const ensureVectorReadyIfNeeded = async () => true; + const recoverHistoryIfNeeded = async () => true; + const isAssistantChatMessage = (message) => + Boolean(message) && !message.is_user && !message.is_system; + const isTavernHelperPromptViewerRefreshActive = () => { + try { + const doc = harness.document; + if (!doc?.querySelectorAll) return false; + const dialogs = Array.from(doc.querySelectorAll('[role="dialog"]')); + for (const dialog of dialogs) { + const dialogText = String(dialog?.textContent || ""); + if (!/(提示词查看器|prompt\s*viewer)/i.test(dialogText)) continue; + if (dialog?.querySelector?.(".fa-rotate-right.animate-spin")) return true; + } + } catch {} + return false; + }; + + let recallInputState; + let rerollRecallInput; + let generationRecallTransactionRuntime; + let finalRecallInjectionRuntime; + let autoExtractionDeferRuntime; + + const clearPendingRerollRecallReuse = (reason = "") => + rerollRecallInput.clearPendingRerollRecallReuse(reason); + const clearPlannerRecallHandoffsForChat = (chatId = getCurrentChatId(), opts = {}) => + rerollRecallInput.clearPlannerRecallHandoffsForChat(chatId, opts); + + recallInputState = createRecallInputState({ + createRecallInputRecord, + getCurrentChatId, + getLastRecallSentUserMessage: () => lastRecallSentUserMessage, + getPendingHostGenerationInputSnapshot: () => pendingHostGenerationInputSnapshot, + getPendingRecallSendIntent: () => pendingRecallSendIntent, + hashRecallInput, + isFreshRecallInputRecord, + normalizeChatIdCandidate, + normalizeRecallInputText, + recordMessageTraceSnapshot, + setLastRecallSentUserMessage: (record) => { + lastRecallSentUserMessage = record; + }, + setPendingHostGenerationInputSnapshot: (record) => { + pendingHostGenerationInputSnapshot = record; + }, + setPendingRecallSendIntent: (record) => { + pendingRecallSendIntent = record; + }, + clearPendingRerollRecallReuse, + clearPlannerRecallHandoffsForChat, + TRIVIAL_GENERATION_SKIP_TTL_MS, + }); + + rerollRecallInput = createRerollRecallInput({ + clearPendingHostGenerationInputSnapshot: (...args) => + recallInputState.clearPendingHostGenerationInputSnapshot(...args), + clearPendingRecallSendIntent: (...args) => + recallInputState.clearPendingRecallSendIntent(...args), + console, + consumeRerollRecallReuseMarker, + createRerollRecallReuseMarker, + createTrivialRecallSkipSentinel, + findLatestUserChatMessageWithIndex, + formatInjection, + getContext, + getCurrentChatId, + getCurrentGenerationTrivialSkip: (...args) => + recallInputState.getCurrentGenerationTrivialSkip(...args), + getLastNonSystemChatMessage, + getLastRecallSentUserMessage: () => lastRecallSentUserMessage, + getLatestUserChatMessage: (chat = []) => + [...chat].reverse().find((message) => message?.is_user) || null, + getPendingRecallSendIntent: () => pendingRecallSendIntent, + getSchema, + getSendTextareaValue, + hashRecallInput, + isFreshRecallInputRecord, + isTrivialUserInput, + markCurrentGenerationTrivialSkip: (...args) => + recallInputState.markCurrentGenerationTrivialSkip(...args), + normalizeChatIdCandidate, + normalizeRecallInputText, + readPersistedRecallFromUserMessage, + resolveGenerationTargetUserMessageIndex, + GENERATION_RECALL_TRANSACTION_TTL_MS, + PLANNER_RECALL_HANDOFF_TTL_MS, + }); + + generationRecallTransactionRuntime = createGenerationRecallTransactions({ + getContext, + getCurrentChatId, + getRecallUserMessageSourceLabel: (...args) => + getRecallUserMessageSourceLabelController(...args), + getSettings, + hashRecallInput, + normalizeChatIdCandidate, + normalizeRecallInputText, + peekPlannerRecallHandoff: (...args) => + rerollRecallInput.peekPlannerRecallHandoff(...args), + resolveGenerationTargetUserMessageIndex, + shouldRunRecallForTransaction, + GENERATION_RECALL_TRANSACTION_TTL_MS, + GENERATION_RECALL_HOOK_BRIDGE_MS, + }); + + finalRecallInjectionRuntime = createFinalRecallInjection({ + applyModuleInjectionPrompt, + areRecallNodeIdListsEqual, + buildPersistedRecallRecord, + bumpPersistedRecallGenerationCount, + clearLiveRecallInjectionPromptForRewrite, + createUiStatus, + debugPersistedRecallPersistence: () => {}, + estimateTokens: (text = "") => + normalizeRecallInputText(text).split(/\s+/).filter(Boolean).length || + (normalizeRecallInputText(text) ? 1 : 0), + getContext, + getGenerationRecallTransactionResult: (...args) => + generationRecallTransactionRuntime.getGenerationRecallTransactionResult(...args), + getLastInjectionContent: () => lastInjectionContent, + getLastRecallSentUserMessage: () => lastRecallSentUserMessage, + getRuntimeStatus: () => runtimeStatus, + getSettings, + normalizeRecallInputText, + normalizeRecallNodeIdList, + readGenerationRecallTransactionFinalResolution: (...args) => + generationRecallTransactionRuntime.readGenerationRecallTransactionFinalResolution(...args), + readPersistedRecallFromUserMessage, + recordInjectionSnapshot, + refreshPanelLiveState, + resolveFinalRecallInjectionSource, + resolveGenerationRecallDeliveryMode: (...args) => + generationRecallTransactionRuntime.resolveGenerationRecallDeliveryMode(...args), + resolveRecallPersistenceTargetUserMessageIndex, + schedulePersistedRecallMessageUiRefresh, + setLastInjectionContent: (value = "") => { + lastInjectionContent = String(value || ""); + }, + setRuntimeStatus: (status) => { + runtimeStatus = status; + }, + storeGenerationRecallTransactionFinalResolution: (...args) => + generationRecallTransactionRuntime.storeGenerationRecallTransactionFinalResolution(...args), + triggerChatMetadataSave, + writePersistedRecallToUserMessage, + }); + + autoExtractionDeferRuntime = createAutoExtractionDefer({ + clearTimeout, + cloneRuntimeDebugValue: (value) => value == null ? null : JSON.parse(JSON.stringify(value)), + console, + ensureGraphMutationReady: () => true, + getContext, + getCurrentChatId, + getGraphPersistenceState: () => graphPersistenceState, + getIsExtracting: () => harness.isExtracting, + getIsHostGenerationRunning: () => isHostGenerationRunning, + getIsRecoveringHistory: () => harness.isRecoveringHistory, + getLastHostGenerationEndedAt: () => lastHostGenerationEndedAt, + getSettings, + isAssistantChatMessage, + isRestoreLockActive: () => false, + normalizeChatIdCandidate, + normalizeRestoreLockState: (value) => value || null, + notifyExtractionIssue: (message) => { + harness.extractionIssues.push(String(message || "")); + }, + resolveAutoExtractionPlan: (options = {}) => + resolveAutoExtractionPlanController( + { + getAssistantTurns(chat = []) { + return chat.flatMap((message, index) => + !message?.is_user && !message?.is_system ? [index] : [], + ); + }, + getLastProcessedAssistantFloor: () => -1, + getSettings, + getSmartTriggerDecision: () => ({ + triggered: false, + score: 0, + reasons: [], + }), + }, + options, + ), + runExtraction: (...args) => harness.runExtraction(...args), + setTimeout, + AUTO_EXTRACTION_DEFER_RETRY_DELAYS_MS, + AUTO_EXTRACTION_HOST_SETTLE_MS, + }); + + const hookRuntime = () => ({ + applyFinalRecallInjectionForGeneration: (...args) => + harness.result.applyFinalRecallInjectionForGeneration(...args), + buildGenerationAfterCommandsRecallInput: (...args) => + rerollRecallInput.buildGenerationAfterCommandsRecallInput(...args), + buildHistoryGenerationRecallInput: (...args) => + rerollRecallInput.buildHistoryGenerationRecallInput(...args), + buildNormalGenerationRecallInput: (...args) => + rerollRecallInput.buildNormalGenerationRecallInput(...args), + clearDryRunPromptPreview, + clearPendingHostGenerationInputSnapshot: (...args) => + recallInputState.clearPendingHostGenerationInputSnapshot(...args), + clearPendingRecallSendIntent: (...args) => + recallInputState.clearPendingRecallSendIntent(...args), + clearLiveRecallInjectionPromptForRewrite, + clearCurrentGenerationTrivialSkip: (...args) => + recallInputState.clearCurrentGenerationTrivialSkip(...args), + consumeDryRunPromptPreview, + consumeHostGenerationInputSnapshot: (...args) => + recallInputState.consumeHostGenerationInputSnapshot(...args), + createGenerationRecallContext: (...args) => + generationRecallTransactionRuntime.createGenerationRecallContext(...args), + createRecallInputRecord, + ensurePersistedRecallRecordForGeneration: (...args) => + finalRecallInjectionRuntime.ensurePersistedRecallRecordForGeneration(...args), + freezeHostGenerationInputSnapshot: (...args) => + recallInputState.freezeHostGenerationInputSnapshot(...args), + getContext, + getCurrentChatId, + getGenerationRecallHookStateFromResult, + getGenerationRecallTransactionResult: (...args) => + generationRecallTransactionRuntime.getGenerationRecallTransactionResult(...args), + getPendingHostGenerationInputSnapshot: (...args) => + recallInputState.getPendingHostGenerationInputSnapshot(...args), + getPendingRecallSendIntent: () => pendingRecallSendIntent, + getSendTextareaValue, + isFreshRecallInputRecord, + isMvuExtraAnalysisGuardActive: () => false, + isTavernHelperPromptViewerRefreshActive, + isTrivialUserInput, + markDryRunPromptPreview, + markCurrentGenerationTrivialSkip: (...args) => + recallInputState.markCurrentGenerationTrivialSkip(...args), + markGenerationRecallTransactionHookState: (...args) => + generationRecallTransactionRuntime.markGenerationRecallTransactionHookState(...args), + normalizeRecallInputText, + refreshPersistedRecallMessageUi: schedulePersistedRecallMessageUiRefresh, + resolveGenerationRecallDeliveryMode: (...args) => + generationRecallTransactionRuntime.resolveGenerationRecallDeliveryMode(...args), + runRecall: (...args) => harness.runRecall(...args), + storeGenerationRecallTransactionResult: (...args) => + generationRecallTransactionRuntime.storeGenerationRecallTransactionResult(...args), + }); + + function markDryRunPromptPreview(ttlMs = GENERATION_RECALL_HOOK_BRIDGE_MS) { + const resolvedTtlMs = Math.max( + 100, + Math.floor(Number(ttlMs) || GENERATION_RECALL_HOOK_BRIDGE_MS), + ); + skipBeforeCombineRecallUntil = Date.now() + resolvedTtlMs; + return skipBeforeCombineRecallUntil; + } + function clearDryRunPromptPreview() { + const hadPendingSkip = skipBeforeCombineRecallUntil > Date.now(); + skipBeforeCombineRecallUntil = 0; + return hadPendingSkip; + } + function consumeDryRunPromptPreview(now = Date.now()) { + if (skipBeforeCombineRecallUntil <= now) { + if (skipBeforeCombineRecallUntil !== 0) { + skipBeforeCombineRecallUntil = 0; + } + return false; + } + skipBeforeCombineRecallUntil = 0; + return true; + } + + function onGenerationStarted(type, params = {}, dryRun = false) { + const generationType = String(type || "normal").trim() || "normal"; + if ( + !dryRun && + !params?.automatic_trigger && + !params?.quiet_prompt && + generationType === "normal" + ) { + isHostGenerationRunning = true; + lastHostGenerationEndedAt = 0; + } + return onGenerationStartedController(hookRuntime(), type, params, dryRun); + } + + function onGenerationEnded(_chatLength = null) { + isHostGenerationRunning = false; + lastHostGenerationEndedAt = Date.now(); + const recentTransaction = + generationRecallTransactionRuntime.findRecentGenerationRecallTransactionForChat(); + const recentRecallResult = + generationRecallTransactionRuntime.getGenerationRecallTransactionResult(recentTransaction); + finalRecallInjectionRuntime.ensurePersistedRecallRecordForGeneration({ + generationType: recentTransaction?.generationType || "normal", + recallResult: recentRecallResult, + transaction: recentTransaction, + recallOptions: recentTransaction?.frozenRecallOptions || null, + hookName: + recentRecallResult?.hookName || + recentTransaction?.lastRecallMeta?.hookName || + "", + }); + schedulePersistedRecallMessageUiRefresh(320); + void autoExtractionDeferRuntime.maybeResumePendingAutoExtraction("generation-ended"); + harness.hideScheduleCalls.push([{}, {}, 180]); + } + + async function onGenerationAfterCommands(type, params = {}, dryRun = false) { + return await onGenerationAfterCommandsController( + hookRuntime(), + type, + params, + dryRun, + ); + } + + async function onBeforeCombinePrompts(promptData = null) { + return await onBeforeCombinePromptsController(hookRuntime(), promptData); + } + + harness.runRecall = async (options = {}) => { + harness.runRecallCalls.push({ ...options }); + const overrideUserMessage = String( + options.overrideUserMessage || options.userMessage || "", + ); + return { + status: "completed", + didRecall: true, + ok: true, + injectionText: `注入:${overrideUserMessage}`, + deliveryMode: String(options.deliveryMode || "immediate"), + source: options.overrideSource, + sourceLabel: options.overrideSourceLabel, + reason: options.overrideReason, + hookName: options.hookName, + recallInput: overrideUserMessage, + userMessage: overrideUserMessage, + authoritativeInputUsed: Boolean(options.authoritativeInputUsed), + boundUserFloorText: String(options.boundUserFloorText || ""), + sourceCandidates: Array.isArray(options.sourceCandidates) + ? options.sourceCandidates.map((candidate) => ({ ...candidate })) + : [], + selectedNodeIds: ["node-test-1"], + retrievalMeta: { + vectorHits: 1, + vectorMergedHits: 0, + diffusionHits: 0, + candidatePoolAfterDpp: 1, + }, + llmMeta: { + status: "disabled", + reason: "test-disabled", + candidatePool: 0, + }, + stats: { + coreCount: 1, + recallCount: 1, + }, + }; + }; + harness.runExtraction = async (...args) => { + harness.runExtractionCalls.push(args); + return { ok: true }; + }; + + const originalApplyFinalRecallInjectionForGeneration = (payload = {}) => + finalRecallInjectionRuntime.applyFinalRecallInjectionForGeneration(payload); + + harness.result = { + hashRecallInput, + buildPreGenerationRecallKey: (...args) => + generationRecallTransactionRuntime.buildPreGenerationRecallKey(...args), + buildGenerationAfterCommandsRecallInput: (...args) => + rerollRecallInput.buildGenerationAfterCommandsRecallInput(...args), + buildNormalGenerationRecallInput: (...args) => + rerollRecallInput.buildNormalGenerationRecallInput(...args), + cleanupGenerationRecallTransactions: (...args) => + generationRecallTransactionRuntime.cleanupGenerationRecallTransactions(...args), + buildGenerationRecallTransactionId: (...args) => + generationRecallTransactionRuntime.buildGenerationRecallTransactionId(...args), + beginGenerationRecallTransaction: (...args) => + generationRecallTransactionRuntime.beginGenerationRecallTransaction(...args), + markGenerationRecallTransactionHookState: (...args) => + generationRecallTransactionRuntime.markGenerationRecallTransactionHookState(...args), + shouldRunRecallForTransaction, + createGenerationRecallContext: (...args) => + generationRecallTransactionRuntime.createGenerationRecallContext(...args), + onGenerationStarted, + onGenerationEnded, + onGenerationAfterCommands, + onBeforeCombinePrompts, + applyFinalRecallInjectionForGeneration: (payload = {}) => { + harness.applyFinalCalls.push({ ...payload }); + if (realApplyFinal) return originalApplyFinalRecallInjectionForGeneration(payload); + return { source: "fresh", targetUserMessageIndex: null }; + }, + persistRecallInjectionRecord: (...args) => + finalRecallInjectionRuntime.persistRecallInjectionRecord(...args), + ensurePersistedRecallRecordForGeneration: (...args) => + finalRecallInjectionRuntime.ensurePersistedRecallRecordForGeneration(...args), + findRecentGenerationRecallTransactionForChat: (...args) => + generationRecallTransactionRuntime.findRecentGenerationRecallTransactionForChat(...args), + getGenerationRecallTransactionResult: (...args) => + generationRecallTransactionRuntime.getGenerationRecallTransactionResult(...args), + generationRecallTransactions: + generationRecallTransactionRuntime.generationRecallTransactions, + freezeHostGenerationInputSnapshot: (...args) => + recallInputState.freezeHostGenerationInputSnapshot(...args), + consumeHostGenerationInputSnapshot: (...args) => + recallInputState.consumeHostGenerationInputSnapshot(...args), + getPendingHostGenerationInputSnapshot: (...args) => + recallInputState.getPendingHostGenerationInputSnapshot(...args), + clearPendingHostGenerationInputSnapshot: (...args) => + recallInputState.clearPendingHostGenerationInputSnapshot(...args), + prepareRerollRecallReuse: (...args) => + rerollRecallInput.prepareRerollRecallReuse(...args), + getPendingRerollRecallReuse: (...args) => + rerollRecallInput.getPendingRerollRecallReuse(...args), + clearPendingRerollRecallReuse, + recordRecallSendIntent: (...args) => + recallInputState.recordRecallSendIntent(...args), + clearPendingRecallSendIntent: (...args) => + recallInputState.clearPendingRecallSendIntent(...args), + recordRecallSentUserMessage: (...args) => + recallInputState.recordRecallSentUserMessage(...args), + getPendingRecallSendIntent: () => pendingRecallSendIntent, + getLastRecallSentUserMessage: () => lastRecallSentUserMessage, + getCurrentGenerationTrivialSkip: (...args) => + recallInputState.getCurrentGenerationTrivialSkip(...args), + markCurrentGenerationTrivialSkip: (...args) => + recallInputState.markCurrentGenerationTrivialSkip(...args), + clearCurrentGenerationTrivialSkip: (...args) => + recallInputState.clearCurrentGenerationTrivialSkip(...args), + consumeCurrentGenerationTrivialSkip: (...args) => + recallInputState.consumeCurrentGenerationTrivialSkip(...args), + deferAutoExtraction: (...args) => + autoExtractionDeferRuntime.deferAutoExtraction(...args), + maybeResumePendingAutoExtraction: (...args) => + autoExtractionDeferRuntime.maybeResumePendingAutoExtraction(...args), + clearPendingAutoExtraction: (...args) => + autoExtractionDeferRuntime.clearPendingAutoExtraction(...args), + getPendingAutoExtraction: (...args) => + autoExtractionDeferRuntime.getPendingAutoExtraction(...args), + getIsHostGenerationRunning: () => isHostGenerationRunning, + preparePlannerRecallHandoff: (...args) => + rerollRecallInput.preparePlannerRecallHandoff(...args), + runPlannerRecallForEna: (args = {}) => + runPlannerRecallForEnaController( + { + buildRecallRecentMessages, + buildRecallRetrieveOptions, + clampInt, + console, + createAbortError, + ensureVectorReadyIfNeeded, + formatInjection, + getContext, + getCurrentGraph: () => harness.currentGraph, + getEmbeddingConfig, + getSchema, + getSettings, + isGraphMetadataWriteAllowed, + isGraphReadableForRecall, + isTrivialUserInput, + normalizeRecallInputText, + recoverHistoryIfNeeded, + retrieve, + }, + args, + ), + getGraphPersistenceState: () => graphPersistenceState, + setGraphPersistenceState: (value = {}) => { + graphPersistenceState = { ...graphPersistenceState, ...(value || {}) }; + return graphPersistenceState; + }, + }; + + Object.defineProperties(harness.result, { + pendingRecallSendIntent: { + get() { + return pendingRecallSendIntent; + }, + set(value) { + pendingRecallSendIntent = value?.text + ? { ...createRecallInputRecord(), ...value } + : createRecallInputRecord(); + }, + configurable: true, + }, + lastRecallSentUserMessage: { + get() { + return lastRecallSentUserMessage; + }, + set(value) { + lastRecallSentUserMessage = value?.text + ? { ...createRecallInputRecord(), ...value } + : createRecallInputRecord(); + }, + configurable: true, + }, + }); + + Object.defineProperties(harness, { + pendingRecallSendIntent: { + get() { + return pendingRecallSendIntent; + }, + set(value) { + pendingRecallSendIntent = value?.text + ? { ...createRecallInputRecord(), ...value } + : createRecallInputRecord(); + }, + configurable: true, + }, + lastRecallSentUserMessage: { + get() { + return lastRecallSentUserMessage; + }, + set(value) { + lastRecallSentUserMessage = value?.text + ? { ...createRecallInputRecord(), ...value } + : createRecallInputRecord(); + }, + configurable: true, + }, + graphPersistenceState: { + get() { + return graphPersistenceState; + }, + set(value) { + graphPersistenceState = { ...graphPersistenceState, ...(value || {}) }; + }, + configurable: true, + }, + }); + + harness.recordRecallSentUserMessage = (...args) => + harness.result.recordRecallSentUserMessage(...args); + harness.invokeOnMessageSent = (messageId = null) => + onMessageSentController( + { + getContext, + isTrivialUserInput, + recordRecallSentUserMessage: harness.result.recordRecallSentUserMessage, + refreshPersistedRecallMessageUi: () => { + harness.recallUiRefreshCalls += 1; + }, + }, + messageId, + ); + harness.invokeOnMessageReceived = (messageId = null, type = "") => + onMessageReceivedController( + { + console, + consumeCurrentGenerationTrivialSkip: + harness.result.consumeCurrentGenerationTrivialSkip, + createRecallInputRecord, + deferAutoExtraction: harness.result.deferAutoExtraction, + getContext, + getCurrentGraph: () => harness.currentGraph, + getGraphPersistenceState: () => graphPersistenceState, + getIsHostGenerationRunning: () => isHostGenerationRunning, + getPendingHostGenerationInputSnapshot: + harness.result.getPendingHostGenerationInputSnapshot, + getPendingRecallSendIntent: () => pendingRecallSendIntent, + getLastProcessedAssistantFloor: () => -1, + getSettings, + isAssistantChatMessage, + isFreshRecallInputRecord, + isGraphMetadataWriteAllowed, + syncGraphLoadFromLiveContext: () => {}, + maybeCaptureGraphShadowSnapshot: () => {}, + maybeFlushQueuedGraphPersist: () => {}, + notifyExtractionIssue: (message) => { + harness.extractionIssues.push(String(message || "")); + }, + queueMicrotask: (task) => task(), + resolveAutoExtractionPlan: (options = {}) => + resolveAutoExtractionPlanController( + { + getAssistantTurns(chat = []) { + return chat.flatMap((message, index) => + !message?.is_user && !message?.is_system ? [index] : [], + ); + }, + getLastProcessedAssistantFloor: () => -1, + getSettings, + getSmartTriggerDecision: () => ({ + triggered: false, + score: 0, + reasons: [], + }), + }, + options, + ), + runExtraction: harness.runExtraction, + refreshPersistedRecallMessageUi: () => { + harness.recallUiRefreshCalls += 1; + }, + setPendingHostGenerationInputSnapshot: (record) => { + pendingHostGenerationInputSnapshot = record; + }, + setPendingRecallSendIntent: (record) => { + pendingRecallSendIntent = record; + }, + }, + messageId, + type, ); - Object.defineProperties(context, { - pendingRecallSendIntent: { - get() { - return context.result.getPendingRecallSendIntent(); - }, - set(value) { - if (value?.text) { - context.result.recordRecallSendIntent( - value?.text || "", - value?.source, - ); - return; - } - context.result.clearPendingRecallSendIntent(); - }, - configurable: true, - }, - lastRecallSentUserMessage: { - get() { - return context.result.getLastRecallSentUserMessage(); - }, - set(value) { - context.result.recordRecallSentUserMessage( - value?.messageId, - value?.text || "", - value?.source, - ); - }, - configurable: true, - }, - }); - const originalApplyFinalRecallInjectionForGeneration = - context.result.applyFinalRecallInjectionForGeneration; - context.applyFinalRecallInjectionForGeneration = (payload = {}) => { - context.applyFinalCalls.push({ ...payload }); - if (realApplyFinal) { - return originalApplyFinalRecallInjectionForGeneration(payload); - } - return { - source: "fresh", - targetUserMessageIndex: null, - }; - }; - context.runRecall = async (options = {}) => { - context.runRecallCalls.push({ ...options }); - const overrideUserMessage = String( - options.overrideUserMessage || options.userMessage || "", - ); - return { - status: "completed", - didRecall: true, - ok: true, - injectionText: `注入:${overrideUserMessage}`, - deliveryMode: String(options.deliveryMode || "immediate"), - source: options.overrideSource, - sourceLabel: options.overrideSourceLabel, - reason: options.overrideReason, - sourceCandidates: Array.isArray(options.sourceCandidates) - ? options.sourceCandidates.map((candidate) => ({ ...candidate })) - : [], - selectedNodeIds: ["node-test-1"], - retrievalMeta: { - vectorHits: 1, - vectorMergedHits: 0, - diffusionHits: 0, - candidatePoolAfterDpp: 1, - }, - llmMeta: { - status: "disabled", - reason: "test-disabled", - candidatePool: 0, - }, - stats: { - coreCount: 1, - recallCount: 1, - }, - }; - }; - context.runExtraction = async (...args) => { - context.runExtractionCalls.push(args); - return { - ok: true, - }; - }; - context.invokeOnMessageSent = (messageId = null) => - onMessageSentController( - { - getContext: context.getContext, - isTrivialUserInput, - recordRecallSentUserMessage: context.result.recordRecallSentUserMessage, - refreshPersistedRecallMessageUi: () => { - context.recallUiRefreshCalls += 1; - }, - }, - messageId, - ); - context.invokeOnMessageReceived = (messageId = null, type = "") => - onMessageReceivedController( - { - console, - consumeCurrentGenerationTrivialSkip: - context.result.consumeCurrentGenerationTrivialSkip, - createRecallInputRecord, - deferAutoExtraction: context.result.deferAutoExtraction, - getContext: context.getContext, - getCurrentGraph: () => context.currentGraph, - getGraphPersistenceState: () => context.result.getGraphPersistenceState(), - getIsHostGenerationRunning: () => - context.result.getIsHostGenerationRunning(), - getPendingHostGenerationInputSnapshot: - context.result.getPendingHostGenerationInputSnapshot, - getPendingRecallSendIntent: () => - context.result.getPendingRecallSendIntent(), - getLastProcessedAssistantFloor: () => -1, - getSettings: () => context.settings, - isAssistantChatMessage: (message) => - Boolean(message) && !message.is_user && !message.is_system, - isFreshRecallInputRecord, - isGraphMetadataWriteAllowed: () => true, - syncGraphLoadFromLiveContext: () => {}, - maybeCaptureGraphShadowSnapshot: () => {}, - maybeFlushQueuedGraphPersist: () => {}, - notifyExtractionIssue: (message) => { - context.extractionIssues.push(String(message || "")); - }, - queueMicrotask: (task) => task(), - resolveAutoExtractionPlan: (options = {}) => - resolveAutoExtractionPlanController( - { - getAssistantTurns(chat = []) { - return chat.flatMap((message, index) => - !message?.is_user && !message?.is_system ? [index] : [], - ); - }, - getLastProcessedAssistantFloor: () => -1, - getSettings: () => context.settings, - getSmartTriggerDecision: () => ({ - triggered: false, - score: 0, - reasons: [], - }), - }, - options, - ), - runExtraction: context.runExtraction, - refreshPersistedRecallMessageUi: () => { - context.recallUiRefreshCalls += 1; - }, - setPendingHostGenerationInputSnapshot: () => {}, - setPendingRecallSendIntent: (record) => { - if (record?.text) { - context.result.recordRecallSendIntent( - record.text || "", - record.source, - ); - return; - } - context.result.clearPendingRecallSendIntent(); - }, - }, - messageId, - type, - ); - return context; - }); + return harness; } diff --git a/tests/index-slicing-ratchet.mjs b/tests/index-slicing-ratchet.mjs index 1f9c11b..4c7112b 100644 --- a/tests/index-slicing-ratchet.mjs +++ b/tests/index-slicing-ratchet.mjs @@ -28,7 +28,6 @@ const SELF_RELATIVE = "tests/index-slicing-ratchet.mjs"; // Remove the entry entirely once a file no longer reads index.js as text. const ALLOWLIST = Object.freeze({ "tests/graph-persistence.mjs": { maxMarkerCalls: 7, stage: "Phase 5" }, - "tests/helpers/generation-recall-harness.mjs": { maxMarkerCalls: 3, stage: "Phase 4" }, "tests/index-esm-entry-smoke.mjs": { maxMarkerCalls: 4, stage: "Phase 5" }, });