Fix processed history hash rebuild after recovery

This commit is contained in:
Youzini-afk
2026-04-11 01:45:26 +08:00
parent 298d615126
commit b2d8fcc7a1
5 changed files with 103 additions and 3 deletions

View File

@@ -11609,6 +11609,14 @@ async function recoverHistoryIfNeeded(trigger = "history-recovery") {
resultCode: "history.recovery.fallback-full-rebuild",
}),
);
const recoveredLastProcessedFloor = Number.isFinite(
currentGraph?.historyState?.lastProcessedAssistantFloor,
)
? currentGraph.historyState.lastProcessedAssistantFloor
: -1;
if (recoveredLastProcessedFloor >= 0) {
updateProcessedHistorySnapshot(chat, recoveredLastProcessedFloor);
}
currentGraph.vectorIndexState.lastIntegrityIssue = null;
saveGraphToChat({ reason: "history-recovery-fallback-rebuild" });
refreshPanelLiveState();
@@ -12390,6 +12398,7 @@ async function onRebuild() {
replayExtractionFromHistory,
restoreRuntimeUiState,
saveGraphToChat,
updateProcessedHistorySnapshot,
setCurrentGraph: (graph) => {
currentGraph = graph;
},

View File

@@ -257,6 +257,17 @@ export function normalizeGraphRuntimeState(graph, chatId = "") {
historyState.processedMessageHashVersion = PROCESSED_MESSAGE_HASH_VERSION;
historyState.processedMessageHashesNeedRefresh = true;
}
const lastProcessedAssistantFloor = Number(
historyState.lastProcessedAssistantFloor,
);
if (
historyState.processedMessageHashesNeedRefresh !== true &&
Number.isFinite(lastProcessedAssistantFloor) &&
lastProcessedAssistantFloor >= 0 &&
Object.keys(historyState.processedMessageHashes).length === 0
) {
historyState.processedMessageHashesNeedRefresh = true;
}
if (
!vectorIndexState.hashToNodeId ||
@@ -613,10 +624,15 @@ export function clearHistoryDirty(graph, result = null) {
graph.historyState.historyDirtyFrom = null;
graph.historyState.lastMutationReason = "";
graph.historyState.lastMutationSource = "";
const lastProcessedAssistantFloor = Number(
graph.historyState.lastProcessedAssistantFloor,
);
graph.historyState.processedMessageHashVersion =
PROCESSED_MESSAGE_HASH_VERSION;
graph.historyState.processedMessageHashes = {};
graph.historyState.processedMessageHashesNeedRefresh = false;
graph.historyState.processedMessageHashesNeedRefresh =
Number.isFinite(lastProcessedAssistantFloor) &&
lastProcessedAssistantFloor >= 0;
if (result) {
graph.historyState.lastRecoveryResult = result;
}

View File

@@ -456,10 +456,17 @@ async function testManualExtractIgnoresFailedBatchWithoutPersistenceAttempt() {
async function testManualRebuildSetsTerminalRuntimeStatus() {
const chat = [{ is_user: true, mes: "u" }, { is_user: false, mes: "a" }];
let savedHashes = null;
let savedNeedRefresh = null;
const context = {
...createBaseStatusContext(),
__confirmHost: true,
currentGraph: {
historyState: {
lastProcessedAssistantFloor: -1,
processedMessageHashes: {},
processedMessageHashesNeedRefresh: false,
},
vectorIndexState: {
lastWarning: "",
},
@@ -489,6 +496,11 @@ async function testManualRebuildSetsTerminalRuntimeStatus() {
},
createEmptyGraph() {
return {
historyState: {
lastProcessedAssistantFloor: -1,
processedMessageHashes: {},
processedMessageHashesNeedRefresh: false,
},
vectorIndexState: {
lastWarning: "",
},
@@ -501,14 +513,31 @@ async function testManualRebuildSetsTerminalRuntimeStatus() {
clearInjectionState() {},
async prepareVectorStateForReplay() {},
async replayExtractionFromHistory() {
context.currentGraph.historyState.lastProcessedAssistantFloor = 1;
context.currentGraph.vectorIndexState.lastWarning = "";
return 2;
},
clearHistoryDirty() {},
clearHistoryDirty(graph) {
graph.historyState.processedMessageHashes = {};
graph.historyState.processedMessageHashesNeedRefresh = true;
},
buildRecoveryResult(status, extra = {}) {
return { status, ...extra };
},
saveGraphToChat() {},
updateProcessedHistorySnapshot(chatInput, floor) {
context.currentGraph.historyState.lastProcessedAssistantFloor = floor;
context.currentGraph.historyState.processedMessageHashes = {};
for (let index = 0; index <= floor; index += 1) {
context.currentGraph.historyState.processedMessageHashes[index] =
String(chatInput[index]?.mes || "");
}
context.currentGraph.historyState.processedMessageHashesNeedRefresh = false;
},
saveGraphToChat() {
savedHashes = { ...context.currentGraph.historyState.processedMessageHashes };
savedNeedRefresh =
context.currentGraph.historyState.processedMessageHashesNeedRefresh;
},
restoreRuntimeUiState() {},
onRebuildController,
result: null,
@@ -524,6 +553,11 @@ async function testManualRebuildSetsTerminalRuntimeStatus() {
assert.equal(context.lastExtractionStatus.text, "图谱重建完成");
assert.equal(context.runtimeStatus.text, "图谱重建完成");
assert.equal(context.runtimeStatus.level, "success");
assert.deepEqual(savedHashes, {
0: "u",
1: "a",
});
assert.equal(savedNeedRefresh, false);
}
testIndexDefinesLastProcessedAssistantFloorHelper();

View File

@@ -1,6 +1,7 @@
import assert from "node:assert/strict";
import {
appendBatchJournal,
clearHistoryDirty,
cloneGraphSnapshot,
createBatchJournalEntry,
detectHistoryMutation,
@@ -95,6 +96,17 @@ assert.equal(migratedGraph.historyState.processedMessageHashesNeedRefresh, true)
const migratedDetection = detectHistoryMutation(chat, migratedGraph.historyState);
assert.equal(migratedDetection.dirty, false);
const emptyHashGraph = normalizeGraphRuntimeState({
historyState: {
chatId: "chat-history-test",
lastProcessedAssistantFloor: 3,
processedMessageHashVersion: PROCESSED_MESSAGE_HASH_VERSION,
processedMessageHashes: {},
processedMessageHashesNeedRefresh: false,
},
});
assert.equal(emptyHashGraph.historyState.processedMessageHashesNeedRefresh, true);
const importedGraph = normalizeGraphRuntimeState({
historyState: {
chatId: "chat-history-test",
@@ -117,6 +129,19 @@ assert.deepEqual(
snapshotProcessedMessageHashes(chat, 3),
);
const clearedGraph = normalizeGraphRuntimeState({
historyState: {
chatId: "chat-history-test",
lastProcessedAssistantFloor: 3,
processedMessageHashVersion: PROCESSED_MESSAGE_HASH_VERSION,
processedMessageHashes: hashes,
processedMessageHashesNeedRefresh: false,
},
});
clearHistoryDirty(clearedGraph, { status: "replayed" });
assert.deepEqual(clearedGraph.historyState.processedMessageHashes, {});
assert.equal(clearedGraph.historyState.processedMessageHashesNeedRefresh, true);
const truncatedChat = chat.slice(0, 2);
const truncatedDetection = detectHistoryMutation(truncatedChat, {
lastProcessedAssistantFloor: 3,

View File

@@ -415,6 +415,22 @@ export async function onRebuildController(runtime) {
reason: "用户手动触发全量重建",
}),
);
const recoveredLastProcessedFloor = Number.isFinite(
runtime.getCurrentGraph()?.historyState?.lastProcessedAssistantFloor,
)
? runtime.getCurrentGraph().historyState.lastProcessedAssistantFloor
: -1;
if (recoveredLastProcessedFloor >= 0) {
if (typeof runtime.updateProcessedHistorySnapshot === "function") {
runtime.updateProcessedHistorySnapshot(chat, recoveredLastProcessedFloor);
} else if (typeof runtime.applyProcessedHistorySnapshotToGraph === "function") {
runtime.applyProcessedHistorySnapshotToGraph(
runtime.getCurrentGraph(),
chat,
recoveredLastProcessedFloor,
);
}
}
runtime.saveGraphToChat({ reason: "manual-rebuild-complete" });
runtime.setLastExtractionStatus(
"图谱重建完成",