Files
ST-Bionic-Memory-Ecology/tests/helpers/generation-recall-harness.mjs
youzini f986bcc293 fix(recall): deterministic persisted reapply on reroll (Phase 1 inject-decoupling)
Reroll (swipe/regenerate/continue = no-new-user) now deterministically
reapplies the parent user floor's stored bme_recall block in
GENERATE_BEFORE_COMBINE_PROMPTS, bypassing the transaction gate and
runRecall entirely. AFTER_COMMANDS defers no-new-user work to
before-combine. Staleness guard recomputes when the user edited the
floor; settings gate honors plugin/recall disable. Fresh normal sends
are unaffected. Compute machinery retained as fallback.
2026-06-01 05:52:45 +00:00

1036 lines
39 KiB
JavaScript

import {
onBeforeCombinePromptsController,
onGenerationAfterCommandsController,
onGenerationStartedController,
onMessageReceivedController,
onMessageSentController,
} from "../../host/event-binding.js";
import { isSystemMessageForExtraction } from "../../maintenance/chat-history.js";
import { resolveAutoExtractionPlanController } from "../../maintenance/extraction-controller.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 {
buildRecallRecentMessagesController,
getRecallUserMessageSourceLabelController,
} from "../../retrieval/recall-controller.js";
import {
clampInt,
createRecallInputRecord,
createUiStatus,
formatRecallContextLine,
getGenerationRecallHookStateFromResult,
hashRecallInput,
isFreshRecallInputRecord,
isTrivialUserInput,
normalizeRecallInputText,
shouldRunRecallForTransaction,
} from "../../ui/ui-status.js";
import { defaultSettings, mergePersistedSettings } from "../../runtime/settings-defaults.js";
import { createRecallInputState } from "../../runtime/recall-input-state.js";
import { createRerollRecallInput } from "../../runtime/reroll-recall-input.js";
import { createGenerationRecallTransactions } from "../../runtime/generation-recall-transactions.js";
import { createFinalRecallInjection } from "../../runtime/final-recall-injection.js";
import { createAutoExtractionDefer } from "../../runtime/auto-extraction-defer.js";
import { runPlannerRecallForEnaController } from "../../runtime/planner-recall-controller.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;
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;
}
}
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;
},
},
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",
};
};
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,
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),
resolveGenerationTargetUserMessageIndex,
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,
reapplyPersistedRecallBlock: (...args) =>
finalRecallInjectionRuntime.reapplyPersistedRecallBlock(...args),
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),
reapplyPersistedRecallBlock: (...args) =>
finalRecallInjectionRuntime.reapplyPersistedRecallBlock(...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),
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,
);
return harness;
}