fix: harden metadata readiness and add persistence self-heal reconcile

This commit is contained in:
Youzini-afk
2026-03-29 21:39:58 +08:00
parent 16e1427fe1
commit 4bf571ba37
3 changed files with 126 additions and 5 deletions

View File

@@ -263,6 +263,17 @@ export async function onBeforeCombinePromptsController(runtime) {
}
export function onMessageReceivedController(runtime) {
const loadState = runtime.getGraphPersistenceState?.()?.loadState || "";
if (
loadState === "loading" ||
loadState === "shadow-restored" ||
loadState === "blocked"
) {
runtime.syncGraphLoadFromLiveContext?.({
source: "message-received-reconcile",
});
}
if (runtime.getCurrentGraph()) {
if (
runtime.getGraphPersistenceState()?.pendingPersist &&

View File

@@ -1780,6 +1780,32 @@ function hasLikelySelectedChatContext(context = getContext()) {
);
}
function hasHostMetadataReadySignal(metadata = {}) {
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
return false;
}
if (normalizeChatIdCandidate(metadata.integrity)) {
return true;
}
const chatIdentityCandidates = [
metadata.chat_id,
metadata.chatId,
metadata.session_id,
metadata.sessionId,
];
if (
chatIdentityCandidates.some((candidate) =>
Boolean(normalizeChatIdCandidate(candidate)),
)
) {
return true;
}
return false;
}
function isHostChatMetadataReady(context = getContext()) {
if (
!context?.chatMetadata ||
@@ -1790,12 +1816,10 @@ function isHostChatMetadataReady(context = getContext()) {
}
const metadata = context.chatMetadata;
// SillyTavern 在 CHAT_CHANGED 之前会为已加载聊天补上 integrity。
if (normalizeChatIdCandidate(metadata.integrity)) {
return true;
}
// 仅接受宿主“强信号”,避免把中间态/占位 metadata 误判为 ready。
if (hasHostMetadataReadySignal(metadata)) return true;
return Object.keys(metadata).length > 0;
return false;
}
function resolveCurrentChatIdentity(context = getContext()) {
@@ -5139,6 +5163,7 @@ function onMessageReceived() {
isAssistantChatMessage,
isFreshRecallInputRecord,
isGraphMetadataWriteAllowed,
syncGraphLoadFromLiveContext,
maybeCaptureGraphShadowSnapshot,
maybeFlushQueuedGraphPersist,
notifyExtractionIssue,

View File

@@ -526,6 +526,52 @@ result = {
assert.equal(result.loadState, "empty-confirmed");
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-metadata-placeholder",
chatMetadata: {
placeholder: "host-loading",
},
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "metadata-placeholder-not-ready",
});
const live = harness.api.getGraphPersistenceLiveState();
assert.equal(
result.loadState,
"loading",
"无 integrity 的占位 metadata 不能视作 ready",
);
assert.equal(
result.reason,
"graph-metadata-missing",
"应继续等待正式 graph metadata",
);
assert.equal(live.writesBlocked, true);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-metadata-chatid-ready",
chatMetadata: {
chatId: "chat-metadata-chatid-ready",
},
});
const result = harness.api.loadGraphFromChat({
attemptIndex: 0,
source: "metadata-chatid-ready",
});
assert.equal(result.loadState, "empty-confirmed");
assert.equal(
harness.api.getGraphPersistenceLiveState().writesBlocked,
false,
"当 metadata 提供 chatId/sessionId 等强信号时,可进入 ready-empty",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "",
@@ -640,6 +686,45 @@ result = {
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-late-reconcile",
chatMetadata: undefined,
});
harness.api.setCurrentGraph(
normalizeGraphRuntimeState(createEmptyGraph(), "chat-late-reconcile"),
);
harness.api.setGraphPersistenceState({
loadState: "blocked",
chatId: "chat-late-reconcile",
reason: "chat-metadata-timeout",
revision: 2,
writesBlocked: true,
});
harness.api.setChatContext({
...harness.api.getChatContext(),
chatId: "chat-late-reconcile",
chatMetadata: {
integrity: "chat-late-reconcile-ready",
st_bme_graph: createMeaningfulGraph("chat-late-reconcile", "late-official"),
},
});
harness.api.onMessageReceived();
const live = harness.api.getGraphPersistenceLiveState();
assert.equal(
live.loadState,
"loaded",
"BLOCKED 后 onMessageReceived 应触发元数据重探测并自动恢复",
);
assert.equal(live.writesBlocked, false);
assert.equal(
harness.api.getCurrentGraph().nodes[0]?.fields?.title,
"事件-late-official",
);
}
{
const sharedSession = new Map();
const writer = await createGraphPersistenceHarness({