refactor(runtime): extract generation recall transaction lifecycle factory (Phase 4c)

This commit is contained in:
youzini
2026-05-31 11:54:45 +00:00
parent 128cc8ab46
commit a8332a8131
4 changed files with 806 additions and 551 deletions

View File

@@ -0,0 +1,701 @@
export function createGenerationRecallTransactions(deps = {}) {
const generationRecallTransactions = new Map();
const normalizeChatIdCandidate = (value = "") =>
deps.normalizeChatIdCandidate?.(value) ?? String(value ?? "").trim();
const normalizeRecallInputText = (value = "") =>
deps.normalizeRecallInputText?.(value) ?? String(value || "").trim();
const getCurrentChatId = (...args) => deps.getCurrentChatId?.(...args);
const getContext = (...args) => deps.getContext?.(...args);
const getGenerationRecallTransactionTtlMs = () =>
Number.isFinite(Number(deps.GENERATION_RECALL_TRANSACTION_TTL_MS))
? Number(deps.GENERATION_RECALL_TRANSACTION_TTL_MS)
: 15000;
const getGenerationRecallHookBridgeMs = () =>
Number.isFinite(Number(deps.GENERATION_RECALL_HOOK_BRIDGE_MS))
? Number(deps.GENERATION_RECALL_HOOK_BRIDGE_MS)
: 1200;
function buildPreGenerationRecallKey(type, options = {}) {
const targetUserMessageIndex = Number.isFinite(options.targetUserMessageIndex)
? options.targetUserMessageIndex
: "none";
const seedText =
options.overrideUserMessage ||
options.userMessage ||
`@target:${targetUserMessageIndex}`;
const normalizedChatId = normalizeChatIdCandidate(
options.chatId || getCurrentChatId(),
);
return [
normalizedChatId,
String(type || "normal").trim() || "normal",
deps.hashRecallInput(seedText || ""),
].join(":");
}
function cleanupGenerationRecallTransactions(now = Date.now()) {
for (const [
transactionId,
transaction,
] of generationRecallTransactions.entries()) {
if (
!transaction ||
now - (transaction.updatedAt || 0) > getGenerationRecallTransactionTtlMs()
) {
generationRecallTransactions.delete(transactionId);
}
}
}
function getGenerationRecallPeerHookName(hookName = "") {
const normalized = String(hookName || "").trim();
if (normalized === "GENERATION_AFTER_COMMANDS") {
return "GENERATE_BEFORE_COMBINE_PROMPTS";
}
if (normalized === "GENERATE_BEFORE_COMBINE_PROMPTS") {
return "GENERATION_AFTER_COMMANDS";
}
return "";
}
function isGenerationRecallTransactionWithinBridgeWindow(
transaction,
now = Date.now(),
) {
if (!transaction) return false;
return (
now - Number(transaction.updatedAt || transaction.createdAt || 0) <=
getGenerationRecallHookBridgeMs()
);
}
function normalizeGenerationRecallTransactionType(generationType = "normal") {
const normalized = String(generationType || "normal").trim() || "normal";
return normalized === "normal" ? "normal" : "history";
}
function resolveGenerationRecallDeliveryMode(
hookName,
generationType = "normal",
recallOptions = {},
) {
if (recallOptions?.forceImmediateDelivery === true) {
return "immediate";
}
const normalizedType = normalizeGenerationRecallTransactionType(
recallOptions?.generationType || generationType,
);
if (normalizedType !== "normal") {
return "immediate";
}
// GENERATION_AFTER_COMMANDS: immediate —— await 完召回后直接通过
// setExtensionPrompt 注入记忆,与 shujuku 参考实现一致。
// GENERATE_BEFORE_COMBINE_PROMPTS: deferred —— 作为兜底,通过 promptData
// rewrite 补救注入。
if (hookName === "GENERATE_BEFORE_COMBINE_PROMPTS") {
return "deferred";
}
return "immediate";
}
function shouldUseAuthoritativeGenerationRecallInput(recallOptions = {}) {
const normalizedGenerationType = normalizeGenerationRecallTransactionType(
recallOptions?.generationType || "normal",
);
if (normalizedGenerationType !== "normal") {
return false;
}
return Boolean(deps.getSettings?.()?.recallUseAuthoritativeGenerationInput);
}
function shouldPreserveAuthoritativeGenerationRecallText(
source,
overrideUserMessage,
targetUserMessageText,
recallOptions = {},
) {
if (!shouldUseAuthoritativeGenerationRecallInput(recallOptions)) {
return false;
}
const normalizedOverride = normalizeRecallInputText(overrideUserMessage);
const normalizedTarget = normalizeRecallInputText(targetUserMessageText);
if (!normalizedOverride || !normalizedTarget || normalizedOverride === normalizedTarget) {
return false;
}
const normalizedSource = String(source || "").trim();
return [
"send-intent",
"generation-started-send-intent",
"generation-started-textarea",
"host-generation-lifecycle",
"textarea-live",
"planner-handoff",
].includes(normalizedSource);
}
function freezeGenerationRecallOptionsForTransaction(
chat,
generationType = "normal",
recallOptions = {},
) {
if (!Array.isArray(chat)) return null;
const optionGenerationType =
String(
recallOptions?.generationType || generationType || "normal",
).trim() || "normal";
const normalizedGenerationType = optionGenerationType;
const overrideUserMessage = normalizeRecallInputText(
recallOptions?.overrideUserMessage || recallOptions?.userMessage || "",
);
const source =
String(
recallOptions?.overrideSource || recallOptions?.source || "",
).trim() ||
(normalizeGenerationRecallTransactionType(normalizedGenerationType) ===
"normal"
? "chat-tail-user"
: "chat-last-user");
const sourceLabel =
String(
recallOptions?.overrideSourceLabel ||
recallOptions?.sourceLabel ||
deps.getRecallUserMessageSourceLabel(source),
).trim() || deps.getRecallUserMessageSourceLabel(source);
const sourceReason =
String(
recallOptions?.overrideReason || recallOptions?.reason || "",
).trim() || "transaction-source-frozen";
const sourceCandidates = Array.isArray(recallOptions?.sourceCandidates)
? recallOptions.sourceCandidates
.map((candidate) => ({
text: normalizeRecallInputText(candidate?.text || ""),
source: String(candidate?.source || "").trim(),
sourceLabel: String(candidate?.sourceLabel || "").trim(),
reason: String(candidate?.reason || "").trim(),
includeSyntheticUserMessage: Boolean(
candidate?.includeSyntheticUserMessage,
),
}))
.filter((candidate) => candidate.text && candidate.source)
: [];
let targetUserMessageIndex = Number.isFinite(
recallOptions?.targetUserMessageIndex,
)
? Math.floor(Number(recallOptions.targetUserMessageIndex))
: deps.resolveGenerationTargetUserMessageIndex(chat, {
generationType: normalizedGenerationType,
});
if (!Number.isFinite(targetUserMessageIndex)) {
if (
normalizeGenerationRecallTransactionType(normalizedGenerationType) ===
"normal" &&
overrideUserMessage
) {
return {
generationType: normalizedGenerationType,
targetUserMessageIndex: null,
overrideUserMessage,
overrideSource: source,
overrideSourceLabel: sourceLabel,
overrideReason: sourceReason,
sourceCandidates,
lockedSource: source,
lockedSourceLabel: sourceLabel,
lockedReason: sourceReason,
authoritativeInputUsed: false,
boundUserFloorText: "",
includeSyntheticUserMessage: Boolean(
recallOptions?.includeSyntheticUserMessage,
),
};
}
return null;
}
targetUserMessageIndex = Math.floor(targetUserMessageIndex);
const targetUserMessage = chat[targetUserMessageIndex];
if (!targetUserMessage?.is_user) {
return null;
}
const targetUserMessageText = normalizeRecallInputText(targetUserMessage?.mes || "");
const preserveAuthoritativeText = shouldPreserveAuthoritativeGenerationRecallText(
source,
overrideUserMessage,
targetUserMessageText,
recallOptions,
);
const frozenUserMessage = preserveAuthoritativeText
? normalizeRecallInputText(overrideUserMessage)
: normalizeRecallInputText(
targetUserMessage?.mes ||
recallOptions?.overrideUserMessage ||
recallOptions?.userMessage ||
"",
);
if (!frozenUserMessage) {
return null;
}
return {
generationType: normalizedGenerationType,
targetUserMessageIndex,
overrideUserMessage: frozenUserMessage,
overrideSource: source,
overrideSourceLabel: sourceLabel,
overrideReason:
sourceReason ||
(frozenUserMessage === overrideUserMessage
? "transaction-source-frozen"
: "transaction-bound-to-chat-user-floor"),
sourceCandidates,
lockedSource: source,
lockedSourceLabel: sourceLabel,
lockedReason:
sourceReason ||
(frozenUserMessage === overrideUserMessage
? "transaction-source-frozen"
: "transaction-bound-to-chat-user-floor"),
authoritativeInputUsed: preserveAuthoritativeText,
boundUserFloorText: targetUserMessageText,
includeSyntheticUserMessage: preserveAuthoritativeText,
};
}
function buildGenerationRecallTransactionId(chatId, generationType, recallKey) {
return [
String(chatId || ""),
String(generationType || "normal").trim() || "normal",
String(recallKey || ""),
].join(":");
}
function beginGenerationRecallTransaction({
chatId,
generationType = "normal",
recallKey = "",
forceNew = false,
} = {}) {
const normalizedChatId = String(chatId || "");
const normalizedGenerationType =
String(generationType || "normal").trim() || "normal";
const normalizedRecallKey = String(recallKey || "");
if (!normalizedChatId || !normalizedRecallKey) return null;
cleanupGenerationRecallTransactions();
const transactionId = buildGenerationRecallTransactionId(
normalizedChatId,
normalizedGenerationType,
normalizedRecallKey,
);
const now = Date.now();
const existingTransaction =
generationRecallTransactions.get(transactionId) || null;
if (
existingTransaction &&
isGenerationRecallTransactionWithinBridgeWindow(existingTransaction, now) &&
!forceNew
) {
existingTransaction.updatedAt = now;
generationRecallTransactions.set(transactionId, existingTransaction);
return existingTransaction;
}
const transaction = {
id: transactionId,
chatId: normalizedChatId,
generationType: normalizedGenerationType,
recallKey: normalizedRecallKey,
hookStates: {},
createdAt: now,
frozenRecallOptions: null,
};
transaction.updatedAt = now;
generationRecallTransactions.set(transactionId, transaction);
return transaction;
}
function findRecentGenerationRecallTransactionForChat(
chatId = getCurrentChatId(),
now = Date.now(),
) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
if (!normalizedChatId) return null;
let latestTransaction = null;
for (const transaction of generationRecallTransactions.values()) {
if (!transaction || String(transaction.chatId || "") !== normalizedChatId)
continue;
if (!isGenerationRecallTransactionWithinBridgeWindow(transaction, now))
continue;
if (
!latestTransaction ||
Number(transaction.updatedAt || 0) >
Number(latestTransaction.updatedAt || 0)
) {
latestTransaction = transaction;
}
}
return latestTransaction;
}
function shouldReuseRecentGenerationRecallTransaction(
transaction,
hookName,
recallKey = "",
now = Date.now(),
) {
if (!transaction || !hookName) return false;
if (!isGenerationRecallTransactionWithinBridgeWindow(transaction, now)) {
return false;
}
const hookStates = transaction.hookStates || {};
const normalizedRecallKey = String(recallKey || "");
const transactionRecallKey = String(transaction.recallKey || "");
if (Object.values(hookStates).includes("running")) {
return true;
}
const peerHookName = getGenerationRecallPeerHookName(hookName);
const peerHookState = peerHookName ? hookStates[peerHookName] : "";
if (peerHookState) {
return true;
}
const ownState = hookStates[hookName];
if (ownState) {
return ownState === "running";
}
if (!Object.keys(hookStates).length) {
if (!transactionRecallKey) {
return true;
}
if (!normalizedRecallKey) {
return false;
}
if (normalizedRecallKey !== transactionRecallKey) {
return false;
}
return true;
}
return false;
}
function markGenerationRecallTransactionHookState(
transaction,
hookName,
state = "completed",
) {
if (!transaction?.id || !hookName) return transaction;
transaction.hookStates ||= {};
transaction.hookStates[hookName] = state;
transaction.updatedAt = Date.now();
generationRecallTransactions.set(transaction.id, transaction);
return transaction;
}
function getGenerationRecallTransactionResult(transaction) {
return transaction?.lastRecallResult || null;
}
function storeGenerationRecallTransactionResult(
transaction,
recallResult = null,
meta = {},
) {
if (!transaction?.id) return transaction;
transaction.lastRecallResult = recallResult ? { ...recallResult } : null;
transaction.lastRecallMeta =
meta && typeof meta === "object" ? { ...meta } : {};
transaction.lastDeliveryMode =
String(meta?.deliveryMode || recallResult?.deliveryMode || "").trim() ||
transaction.lastDeliveryMode ||
"";
transaction.finalResolution = null;
transaction.updatedAt = Date.now();
generationRecallTransactions.set(transaction.id, transaction);
return transaction;
}
function readGenerationRecallTransactionFinalResolution(transaction) {
return transaction?.finalResolution || null;
}
function storeGenerationRecallTransactionFinalResolution(
transaction,
finalResolution = null,
) {
if (!transaction?.id) return transaction;
transaction.finalResolution = finalResolution ? { ...finalResolution } : null;
transaction.updatedAt = Date.now();
generationRecallTransactions.set(transaction.id, transaction);
return transaction;
}
function clearGenerationRecallTransactionsForChat(
chatId = getCurrentChatId(),
{ clearAll = false } = {},
) {
let removed = 0;
const normalizedChatId = String(chatId || "");
if (clearAll || !normalizedChatId) {
removed = generationRecallTransactions.size;
generationRecallTransactions.clear();
return removed;
}
for (const [
transactionId,
transaction,
] of generationRecallTransactions.entries()) {
if (String(transaction?.chatId || "") !== normalizedChatId) continue;
generationRecallTransactions.delete(transactionId);
removed += 1;
}
return removed;
}
function createGenerationRecallContext({
hookName,
generationType = "normal",
recallOptions = {},
chatId = getCurrentChatId(),
} = {}) {
const context = getContext();
const chat = context?.chat;
const normalizedChatId = normalizeChatIdCandidate(
chatId || context?.chatId || getCurrentChatId(),
);
const effectiveGenerationType = normalizeGenerationRecallTransactionType(
recallOptions?.generationType || generationType,
);
const plannerRecallHandoff =
effectiveGenerationType === "normal"
? deps.peekPlannerRecallHandoff(normalizedChatId)
: null;
const effectiveRecallOptions = plannerRecallHandoff
? {
...(recallOptions || {}),
overrideUserMessage: plannerRecallHandoff.rawUserInput,
overrideSource: plannerRecallHandoff.source || "planner-handoff",
overrideSourceLabel:
plannerRecallHandoff.sourceLabel || "Planner handoff",
overrideReason: "planner-handoff-reuse",
sourceCandidates: [
{
text: plannerRecallHandoff.rawUserInput,
source: plannerRecallHandoff.source || "planner-handoff",
sourceLabel:
plannerRecallHandoff.sourceLabel || "Planner handoff",
reason: "planner-handoff-reuse",
includeSyntheticUserMessage: false,
},
],
includeSyntheticUserMessage: false,
}
: recallOptions;
const frozenRecallOptions = freezeGenerationRecallOptionsForTransaction(
chat,
generationType,
effectiveRecallOptions,
);
if (!frozenRecallOptions) {
return {
hookName,
generationType,
recallKey: "",
transaction: null,
recallOptions: null,
shouldRun: false,
guardReason: "missing-frozen-recall-options",
};
}
const transactionGenerationType = normalizeGenerationRecallTransactionType(
frozenRecallOptions.generationType || generationType,
);
const fallbackRecallKey =
effectiveRecallOptions?.recallKey ||
buildPreGenerationRecallKey(transactionGenerationType, {
...frozenRecallOptions,
chatId: normalizedChatId,
userMessage: frozenRecallOptions.overrideUserMessage,
});
if (!normalizedChatId || !String(fallbackRecallKey || "").trim()) {
return {
hookName,
generationType: transactionGenerationType,
recallKey: "",
transaction: null,
recallOptions: null,
shouldRun: false,
guardReason: !normalizedChatId ? "missing-chat-id" : "missing-recall-key",
};
}
const now = Date.now();
const recentTransaction = findRecentGenerationRecallTransactionForChat(
normalizedChatId,
now,
);
let transaction = recentTransaction;
if (
!shouldReuseRecentGenerationRecallTransaction(
transaction,
hookName,
fallbackRecallKey,
now,
)
) {
transaction = beginGenerationRecallTransaction({
chatId: normalizedChatId,
generationType: transactionGenerationType,
recallKey: fallbackRecallKey,
forceNew: true,
});
}
if (!transaction) {
return {
hookName,
generationType: transactionGenerationType,
recallKey: "",
transaction: null,
recallOptions: null,
shouldRun: false,
guardReason: "transaction-unavailable",
};
}
const normalizedTransactionChatId = normalizeChatIdCandidate(
transaction.chatId,
);
const transactionRecallKey = String(transaction.recallKey || "").trim();
const peerHookName = getGenerationRecallPeerHookName(hookName);
const hasPeerHookState = Boolean(
peerHookName && transaction.hookStates?.[peerHookName],
);
if (
normalizedTransactionChatId !== normalizedChatId ||
!transactionRecallKey ||
(!hasPeerHookState && transactionRecallKey !== String(fallbackRecallKey))
) {
return {
hookName,
generationType: transactionGenerationType,
recallKey: String(fallbackRecallKey || ""),
transaction,
recallOptions: null,
shouldRun: false,
guardReason: "transaction-mismatch",
};
}
if (
!transaction.frozenRecallOptions ||
typeof transaction.frozenRecallOptions !== "object"
) {
transaction.frozenRecallOptions = {
...frozenRecallOptions,
lockedSource:
frozenRecallOptions?.lockedSource ||
frozenRecallOptions?.overrideSource ||
frozenRecallOptions?.source ||
"",
lockedSourceLabel:
frozenRecallOptions?.lockedSourceLabel ||
frozenRecallOptions?.overrideSourceLabel ||
frozenRecallOptions?.sourceLabel ||
"",
lockedReason:
frozenRecallOptions?.lockedReason ||
frozenRecallOptions?.overrideReason ||
frozenRecallOptions?.reason ||
"",
lockedAt: now,
};
}
if (!String(transaction.generationType || "").trim()) {
transaction.generationType = transactionGenerationType;
}
transaction.updatedAt = now;
generationRecallTransactions.set(transaction.id, transaction);
const boundRecallOptions = {
...(transaction.frozenRecallOptions || frozenRecallOptions),
recallKey: transaction.recallKey,
generationType:
transaction.frozenRecallOptions?.generationType || generationType,
};
if (plannerRecallHandoff?.result) {
boundRecallOptions.cachedRecallPayload = {
handoffId: plannerRecallHandoff.id,
chatId: plannerRecallHandoff.chatId,
result: plannerRecallHandoff.result,
recentMessages: Array.isArray(plannerRecallHandoff.recentMessages)
? plannerRecallHandoff.recentMessages.map((item) => String(item || ""))
: [],
injectionText: String(plannerRecallHandoff.injectionText || ""),
source: plannerRecallHandoff.source || "planner-handoff",
sourceLabel: plannerRecallHandoff.sourceLabel || "Planner handoff",
reason: "planner-handoff-reuse",
};
}
const recallKey = transactionRecallKey;
const shouldRun = deps.shouldRunRecallForTransaction(transaction, hookName);
return {
hookName,
generationType: boundRecallOptions.generationType,
recallKey,
transaction,
recallOptions: boundRecallOptions,
shouldRun,
guardReason: shouldRun ? "" : "transaction-not-runnable",
};
}
return {
generationRecallTransactions,
buildPreGenerationRecallKey,
cleanupGenerationRecallTransactions,
getGenerationRecallPeerHookName,
isGenerationRecallTransactionWithinBridgeWindow,
normalizeGenerationRecallTransactionType,
resolveGenerationRecallDeliveryMode,
shouldUseAuthoritativeGenerationRecallInput,
shouldPreserveAuthoritativeGenerationRecallText,
freezeGenerationRecallOptionsForTransaction,
buildGenerationRecallTransactionId,
beginGenerationRecallTransaction,
findRecentGenerationRecallTransactionForChat,
shouldReuseRecentGenerationRecallTransaction,
markGenerationRecallTransactionHookState,
getGenerationRecallTransactionResult,
storeGenerationRecallTransactionResult,
readGenerationRecallTransactionFinalResolution,
storeGenerationRecallTransactionFinalResolution,
clearGenerationRecallTransactionsForChat,
createGenerationRecallContext,
};
}