Optimize detached graph save path

This commit is contained in:
Youzini-afk
2026-04-21 17:44:32 +08:00
parent a0313a6399
commit 6ddf3a7386
2 changed files with 76 additions and 3 deletions

View File

@@ -7425,6 +7425,7 @@ async function persistGraphToHostChatState(
mode = "primary",
persistDelta = null,
chatStateTarget = null,
graphDetached = false,
} = {},
) {
if (!context || !graph || !canUseHostGraphChatStatePersistence(context)) {
@@ -7482,7 +7483,10 @@ async function persistGraphToHostChatState(
getChatMetadataIntegrity(context) ||
normalizeChatIdCandidate(resolvedIdentity?.integrity) ||
graphPersistenceState.metadataIntegrity;
const persistedGraph = cloneGraphForPersistence(graph, chatId);
const persistedGraph =
graphDetached === true
? normalizeGraphRuntimeState(graph, chatId)
: cloneGraphForPersistence(graph, chatId);
stampGraphPersistenceMeta(persistedGraph, {
revision,
reason: `chat-state:${String(reason || "graph-chat-state")}`,
@@ -10534,6 +10538,7 @@ async function persistGraphToConfiguredDurableTier(
lastProcessedAssistantFloor = null,
persistDelta = null,
chatStateTarget = null,
graphDetached = false,
} = {},
) {
const preferredLocalStore = getPreferredGraphLocalStorePresentationSync();
@@ -10559,6 +10564,7 @@ async function persistGraphToConfiguredDurableTier(
mode: "primary",
persistDelta,
chatStateTarget,
graphDetached,
});
if (chatStateResult?.saved) {
const acceptedRevision = Number(chatStateResult.revision || revision);
@@ -10624,6 +10630,7 @@ async function persistGraphToConfiguredDurableTier(
persistRole: "cache-mirror",
scheduleCloudUpload: false,
persistDelta,
graphDetached,
});
}
return buildGraphPersistResult({
@@ -10691,6 +10698,7 @@ async function persistGraphToConfiguredDurableTier(
mode: "primary",
persistDelta,
chatStateTarget,
graphDetached,
});
if (chatStateResult?.saved) {
const acceptedRevision = Number(chatStateResult.revision || revision);
@@ -10734,6 +10742,7 @@ async function persistGraphToConfiguredDurableTier(
revision: acceptedRevision,
reason: `${reason}:chat-state-fallback:promote-indexeddb`,
persistDelta,
graphDetached,
});
return buildGraphPersistResult({
saved: true,
@@ -11359,6 +11368,10 @@ async function persistExtractionBatchResult({
} = {}) {
ensureCurrentGraphRuntimeState();
const context = getContext();
const persistGraphDetached =
Boolean(graphSnapshot) &&
typeof graphSnapshot === "object" &&
graphSnapshot !== currentGraph;
const persistGraph =
graphSnapshot && typeof graphSnapshot === "object"
? graphSnapshot === currentGraph
@@ -11400,6 +11413,7 @@ async function persistExtractionBatchResult({
reason,
lastProcessedAssistantFloor,
persistDelta,
graphDetached: persistGraphDetached,
},
);
if (acceptedPersistResult?.accepted) {
@@ -13824,6 +13838,7 @@ function queueGraphPersistToIndexedDb(
persistRole = "primary",
scheduleCloudUpload = undefined,
persistDelta = null,
graphDetached = false,
} = {},
) {
const normalizedChatId = normalizeChatIdCandidate(chatId);
@@ -13872,7 +13887,9 @@ function queueGraphPersistToIndexedDb(
};
}
const graphSnapshot = graph
? cloneGraphForPersistence(graph, normalizedChatId)
? graphDetached === true
? normalizeGraphRuntimeState(graph, normalizedChatId)
: cloneGraphForPersistence(graph, normalizedChatId)
: null;
return await saveGraphToIndexedDb(normalizedChatId, graphSnapshot, {
revision: normalizedRevision,
@@ -13939,7 +13956,8 @@ function saveGraphToChat(options = {}) {
}
const shouldQueueIndexedDbPersist =
markMutation || !isGraphEffectivelyEmpty(currentGraph);
persistenceEnvironment.hostProfile !== "luker" &&
(markMutation || !isGraphEffectivelyEmpty(currentGraph));
if (shouldQueueIndexedDbPersist) {
queueGraphPersistToIndexedDb(chatId, currentGraph, {
revision,
@@ -13985,6 +14003,7 @@ function saveGraphToChat(options = {}) {
reason,
lastProcessedAssistantFloor,
chatStateTarget,
graphDetached: true,
},
);
if (!persistResult?.accepted) {
@@ -18727,6 +18746,7 @@ async function onRebuildLocalCacheFromLukerSidecar() {
reason: "panel-manual-luker-cache-rebuild",
persistRole: "cache-mirror",
scheduleCloudUpload: false,
graphDetached: true,
});
refreshPanelLiveState();
toastr.success("已开始从 Luker 主 sidecar 重建本地缓存");

View File

@@ -3782,6 +3782,59 @@ result = {
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-luker-queued-save-detached",
globalChatId: "chat-luker-queued-save-detached",
characterId: "char-luker-queued-save",
chatMetadata: {
integrity: "meta-luker-queued-save-detached",
},
});
harness.runtimeContext.Luker = {
getContext() {
return harness.runtimeContext.__chatContext;
},
};
harness.api.setCurrentGraph(
stampPersistedGraph(
createMeaningfulGraph("chat-luker-queued-save-detached", "luker-detached"),
{
revision: 6,
integrity: "meta-luker-queued-save-detached",
chatId: "chat-luker-queued-save-detached",
reason: "luker-detached-seed",
},
),
);
harness.api.setGraphPersistenceState({
loadState: "loaded",
chatId: "chat-luker-queued-save-detached",
revision: 6,
lastPersistedRevision: 6,
writesBlocked: false,
});
const result = harness.api.saveGraphToChat({
reason: "luker-detached-save",
markMutation: false,
});
assert.equal(result.queued, true);
assert.equal(result.storageTier, "luker-chat-state");
assert.equal(result.saveMode, "luker-chat-state-queued");
harness.api.getCurrentGraph().nodes[0].fields.title = "runtime-mutated-after-queued-save";
await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(
harness.api.getIndexedDbSnapshot()?.nodes?.[0]?.fields?.title,
"事件-luker-detached",
"Luker queued save 的异步本地 mirror 不应被后续 live graph 修改污染",
);
}
{
const harness = await createGraphPersistenceHarness({
chatId: "chat-luker-v2-load",