Files
ST-Bionic-Memory-Ecology/runtime/auto-extraction-defer.js

347 lines
11 KiB
JavaScript

export function createAutoExtractionDefer(deps = {}) {
let pendingAutoExtractionTimer = null;
let pendingAutoExtraction = {
chatId: "",
messageId: null,
reason: "",
requestedAt: 0,
attempts: 0,
targetEndFloor: null,
strategy: "normal",
};
const normalizeChatIdCandidate = (value = "") =>
deps.normalizeChatIdCandidate?.(value) ?? String(value ?? "").trim();
const getCurrentChatId = (...args) => deps.getCurrentChatId?.(...args);
const getContext = (...args) => deps.getContext?.(...args);
const getSettings = (...args) => deps.getSettings?.(...args);
const clearTimeoutImpl = deps.clearTimeout || globalThis.clearTimeout;
const setTimeoutImpl = deps.setTimeout || globalThis.setTimeout;
const consoleImpl = deps.console || console;
const deferRetryDelays = Array.isArray(deps.AUTO_EXTRACTION_DEFER_RETRY_DELAYS_MS)
? deps.AUTO_EXTRACTION_DEFER_RETRY_DELAYS_MS
: [120, 320, 800, 1600, 2800];
const hostSettleMs = Number.isFinite(Number(deps.AUTO_EXTRACTION_HOST_SETTLE_MS))
? Number(deps.AUTO_EXTRACTION_HOST_SETTLE_MS)
: 120;
function getPendingAutoExtraction() {
return { ...pendingAutoExtraction };
}
function clearPendingAutoExtraction({ resetState = true } = {}) {
if (pendingAutoExtractionTimer) {
clearTimeoutImpl(pendingAutoExtractionTimer);
pendingAutoExtractionTimer = null;
}
if (resetState) {
pendingAutoExtraction = {
chatId: "",
messageId: null,
reason: "",
requestedAt: 0,
attempts: 0,
targetEndFloor: null,
strategy: "normal",
};
}
}
function deferAutoExtraction(
reason = "auto-extraction-deferred",
{
chatId = getCurrentChatId(),
messageId = null,
delayMs = null,
targetEndFloor = null,
strategy = "",
} = {},
) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
if (!normalizedChatId) {
clearPendingAutoExtraction();
return {
scheduled: false,
reason: "missing-chat-id",
chatId: "",
};
}
const sameChat = normalizedChatId === pendingAutoExtraction.chatId;
const previousAttempts = sameChat
? Math.max(0, Math.floor(Number(pendingAutoExtraction.attempts) || 0))
: 0;
const nextAttempts = previousAttempts + 1;
const resolvedDelayMs =
delayMs !== null &&
delayMs !== undefined &&
Number.isFinite(Number(delayMs))
? Math.max(0, Math.floor(Number(delayMs)))
: deferRetryDelays[
Math.min(
previousAttempts,
deferRetryDelays.length - 1,
)
];
pendingAutoExtraction = {
chatId: normalizedChatId,
messageId: Number.isFinite(Number(messageId))
? Math.floor(Number(messageId))
: sameChat
? pendingAutoExtraction.messageId
: null,
reason: String(reason || "auto-extraction-deferred"),
requestedAt:
sameChat && pendingAutoExtraction.requestedAt > 0
? pendingAutoExtraction.requestedAt
: Date.now(),
attempts: nextAttempts,
targetEndFloor: Number.isFinite(Number(targetEndFloor))
? sameChat &&
Number.isFinite(Number(pendingAutoExtraction.targetEndFloor))
? Math.max(
Math.floor(Number(targetEndFloor)),
Math.floor(Number(pendingAutoExtraction.targetEndFloor)),
)
: Math.floor(Number(targetEndFloor))
: sameChat
? pendingAutoExtraction.targetEndFloor
: null,
strategy: String(strategy || "")
? String(strategy || "")
: sameChat
? String(pendingAutoExtraction.strategy || "normal")
: "normal",
};
if (pendingAutoExtractionTimer) {
clearTimeoutImpl(pendingAutoExtractionTimer);
}
pendingAutoExtractionTimer = setTimeoutImpl(() => {
pendingAutoExtractionTimer = null;
void maybeResumePendingAutoExtraction(
`retry:${pendingAutoExtraction.reason || "auto-extraction-deferred"}`,
);
}, resolvedDelayMs);
consoleImpl.debug?.("[ST-BME] auto extraction deferred", {
reason: pendingAutoExtraction.reason,
chatId: normalizedChatId,
messageId: pendingAutoExtraction.messageId,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
attempts: nextAttempts,
delayMs: resolvedDelayMs,
});
return {
scheduled: true,
chatId: normalizedChatId,
messageId: pendingAutoExtraction.messageId,
reason: pendingAutoExtraction.reason,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
attempts: nextAttempts,
delayMs: resolvedDelayMs,
};
}
function maybeResumePendingAutoExtraction(source = "auto-extraction-resume") {
const pendingChatId = normalizeChatIdCandidate(pendingAutoExtraction.chatId);
if (!pendingChatId) {
return {
resumed: false,
reason: "no-pending-auto-extraction",
};
}
if (deps.isRestoreLockActive()) {
return {
resumed: false,
reason: "restore-lock-active",
restoreLock: deps.cloneRuntimeDebugValue(
deps.normalizeRestoreLockState(deps.getGraphPersistenceState?.()?.restoreLock),
null,
),
};
}
const currentChatId = normalizeChatIdCandidate(getCurrentChatId());
if (!currentChatId || currentChatId !== pendingChatId) {
clearPendingAutoExtraction();
return {
resumed: false,
reason: "chat-switched",
chatId: pendingChatId,
currentChatId,
};
}
if (deps.getIsExtracting?.()) {
return deferAutoExtraction("extracting", {
chatId: pendingChatId,
messageId: pendingAutoExtraction.messageId,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
});
}
if (deps.getIsHostGenerationRunning?.()) {
return deferAutoExtraction("generation-running", {
chatId: pendingChatId,
messageId: pendingAutoExtraction.messageId,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
});
}
const lastHostGenerationEndedAt = Number(deps.getLastHostGenerationEndedAt?.() || 0);
const hostGenerationSettleRemainingMs =
lastHostGenerationEndedAt > 0
? hostSettleMs -
(Date.now() - lastHostGenerationEndedAt)
: 0;
if (hostGenerationSettleRemainingMs > 0) {
return deferAutoExtraction("generation-settling", {
chatId: pendingChatId,
messageId: pendingAutoExtraction.messageId,
delayMs: hostGenerationSettleRemainingMs,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
});
}
if (deps.getIsRecoveringHistory?.()) {
return deferAutoExtraction("history-recovering", {
chatId: pendingChatId,
messageId: pendingAutoExtraction.messageId,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
});
}
if (!deps.ensureGraphMutationReady("自动提取", { notify: false })) {
consoleImpl.debug?.(
"[ST-BME] pending auto extraction resume blocked: graph-not-ready",
{
source,
chatId: pendingChatId,
attempts: pendingAutoExtraction.attempts || 0,
loadState: deps.getGraphPersistenceState?.()?.loadState || "",
},
);
return deferAutoExtraction("graph-not-ready", {
chatId: pendingChatId,
messageId: pendingAutoExtraction.messageId,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
});
}
const resumeContext = getContext();
const resumeChat = resumeContext?.chat;
const settings = getSettings();
let lockedEndFloor = Number.isFinite(Number(pendingAutoExtraction.targetEndFloor))
? Math.floor(Number(pendingAutoExtraction.targetEndFloor))
: null;
if (
Array.isArray(resumeChat) &&
Number.isFinite(Number(pendingAutoExtraction.messageId))
) {
const pendingMessageIndex = Math.floor(
Number(pendingAutoExtraction.messageId),
);
const pendingMessage = resumeChat[pendingMessageIndex];
if (
deps.isAssistantChatMessage(pendingMessage, {
index: pendingMessageIndex,
chat: resumeChat,
}) &&
!String(pendingMessage?.mes ?? "").trim()
) {
return deferAutoExtraction("assistant-message-empty", {
chatId: pendingChatId,
messageId: pendingMessageIndex,
delayMs: hostSettleMs,
targetEndFloor: pendingAutoExtraction.targetEndFloor,
strategy: pendingAutoExtraction.strategy,
});
}
}
if (Array.isArray(resumeChat) && resumeChat.length > 0 && lockedEndFloor != null) {
const lockedPlan = deps.resolveAutoExtractionPlan({
chat: resumeChat,
settings,
lockedEndFloor,
});
if (
!lockedPlan.canRun &&
lockedPlan.candidateAssistantTurns.length === 0
) {
const fallbackPlan = deps.resolveAutoExtractionPlan({
chat: resumeChat,
settings,
});
lockedEndFloor = fallbackPlan.canRun
? fallbackPlan.plannedBatchEndFloor
: null;
}
}
const pendingRequest = { ...pendingAutoExtraction };
clearPendingAutoExtraction();
if (lockedEndFloor == null) {
const currentPlan = deps.resolveAutoExtractionPlan({
chat: resumeChat,
settings,
});
if (!currentPlan.canRun) {
return {
resumed: false,
reason: "no-runnable-auto-extraction",
source,
...pendingRequest,
};
}
lockedEndFloor = currentPlan.plannedBatchEndFloor;
}
consoleImpl.debug?.("[ST-BME] resuming pending auto extraction", {
source,
chatId: pendingRequest.chatId,
messageId: pendingRequest.messageId,
targetEndFloor: lockedEndFloor,
attempts: pendingRequest.attempts || 0,
});
const enqueueMicrotask =
typeof globalThis.queueMicrotask === "function"
? globalThis.queueMicrotask.bind(globalThis)
: (task) => Promise.resolve().then(task);
enqueueMicrotask(() => {
void deps.runExtraction({
lockedEndFloor,
triggerSource: source,
}).catch((error) => {
consoleImpl.error("[ST-BME] 延迟自动提取失败:", error);
deps.notifyExtractionIssue(error?.message || String(error) || "自动提取失败");
});
});
return {
resumed: true,
source,
lockedEndFloor,
...pendingRequest,
};
}
return {
clearPendingAutoExtraction,
deferAutoExtraction,
maybeResumePendingAutoExtraction,
getPendingAutoExtraction,
};
}