mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Recover graph state after missed chat events
This commit is contained in:
104
index.js
104
index.js
@@ -94,6 +94,7 @@ const GRAPH_LOAD_STATES = Object.freeze({
|
|||||||
});
|
});
|
||||||
const GRAPH_LOAD_PENDING_CHAT_ID = "__pending_chat__";
|
const GRAPH_LOAD_PENDING_CHAT_ID = "__pending_chat__";
|
||||||
const GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${MODULE_NAME}:graph-shadow:`;
|
const GRAPH_SHADOW_SNAPSHOT_STORAGE_PREFIX = `${MODULE_NAME}:graph-shadow:`;
|
||||||
|
const GRAPH_STARTUP_RECONCILE_DELAYS_MS = [150, 600, 1800, 4000];
|
||||||
|
|
||||||
function cloneRuntimeDebugValue(value, fallback = null) {
|
function cloneRuntimeDebugValue(value, fallback = null) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -1607,6 +1608,85 @@ function scheduleGraphLoadRetry(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldSyncGraphLoadFromLiveContext(
|
||||||
|
context = getContext(),
|
||||||
|
{ force = false } = {},
|
||||||
|
) {
|
||||||
|
if (force) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatIdentity = resolveCurrentChatIdentity(context);
|
||||||
|
const liveChatId = chatIdentity.chatId;
|
||||||
|
const stateChatId = normalizeChatIdCandidate(graphPersistenceState.chatId);
|
||||||
|
const liveMetadataReady = isHostChatMetadataReady(context);
|
||||||
|
|
||||||
|
if (liveChatId && liveChatId !== stateChatId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
graphPersistenceState.loadState === GRAPH_LOAD_STATES.NO_CHAT &&
|
||||||
|
(liveChatId || chatIdentity.hasLikelySelectedChat)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
graphPersistenceState.loadState === GRAPH_LOAD_STATES.SHADOW_RESTORED &&
|
||||||
|
liveMetadataReady
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
graphPersistenceState.loadState === GRAPH_LOAD_STATES.LOADING &&
|
||||||
|
liveMetadataReady
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
graphPersistenceState.loadState === GRAPH_LOAD_STATES.BLOCKED &&
|
||||||
|
(liveChatId || liveMetadataReady)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncGraphLoadFromLiveContext(options = {}) {
|
||||||
|
const { source = "live-context-sync", force = false } = options;
|
||||||
|
const context = getContext();
|
||||||
|
if (!shouldSyncGraphLoadFromLiveContext(context, { force })) {
|
||||||
|
return {
|
||||||
|
synced: false,
|
||||||
|
reason: "no-sync-needed",
|
||||||
|
loadState: graphPersistenceState.loadState,
|
||||||
|
chatId: graphPersistenceState.chatId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = loadGraphFromChat({
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
synced: true,
|
||||||
|
...result,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleStartupGraphReconciliation() {
|
||||||
|
for (const delayMs of GRAPH_STARTUP_RECONCILE_DELAYS_MS) {
|
||||||
|
setTimeout(() => {
|
||||||
|
syncGraphLoadFromLiveContext({
|
||||||
|
source: `startup-reconcile:${delayMs}`,
|
||||||
|
});
|
||||||
|
}, delayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function clearInjectionState() {
|
function clearInjectionState() {
|
||||||
lastInjectionContent = "";
|
lastInjectionContent = "";
|
||||||
lastRecalledItems = [];
|
lastRecalledItems = [];
|
||||||
@@ -4775,12 +4855,21 @@ function onChatChanged() {
|
|||||||
lastPreGenerationRecallAt = 0;
|
lastPreGenerationRecallAt = 0;
|
||||||
abortAllRunningStages();
|
abortAllRunningStages();
|
||||||
dismissAllStageNotices();
|
dismissAllStageNotices();
|
||||||
loadGraphFromChat();
|
syncGraphLoadFromLiveContext({
|
||||||
|
source: "chat-changed",
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
clearInjectionState();
|
clearInjectionState();
|
||||||
clearRecallInputTracking();
|
clearRecallInputTracking();
|
||||||
installSendIntentHooks();
|
installSendIntentHooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onChatLoaded() {
|
||||||
|
syncGraphLoadFromLiveContext({
|
||||||
|
source: "chat-loaded",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function onMessageSent(messageId) {
|
function onMessageSent(messageId) {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const chat = context?.chat;
|
const chat = context?.chat;
|
||||||
@@ -5597,6 +5686,9 @@ async function onReembedDirect() {
|
|||||||
|
|
||||||
// 注册事件钩子
|
// 注册事件钩子
|
||||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||||
|
if (event_types.CHAT_LOADED) {
|
||||||
|
eventSource.on(event_types.CHAT_LOADED, onChatLoaded);
|
||||||
|
}
|
||||||
if (event_types.MESSAGE_SENT) {
|
if (event_types.MESSAGE_SENT) {
|
||||||
eventSource.on(event_types.MESSAGE_SENT, onMessageSent);
|
eventSource.on(event_types.MESSAGE_SENT, onMessageSent);
|
||||||
}
|
}
|
||||||
@@ -5612,7 +5704,11 @@ async function onReembedDirect() {
|
|||||||
|
|
||||||
// 加载当前聊天的图谱
|
// 加载当前聊天的图谱
|
||||||
clearPendingGraphLoadRetry();
|
clearPendingGraphLoadRetry();
|
||||||
loadGraphFromChat();
|
syncGraphLoadFromLiveContext({
|
||||||
|
source: "initial-load",
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
scheduleStartupGraphReconciliation();
|
||||||
|
|
||||||
// ==================== 操控面板初始化 ====================
|
// ==================== 操控面板初始化 ====================
|
||||||
|
|
||||||
@@ -5650,6 +5746,10 @@ async function onReembedDirect() {
|
|||||||
return settings;
|
return settings;
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
syncGraphLoad: () =>
|
||||||
|
syncGraphLoadFromLiveContext({
|
||||||
|
source: "panel-open-sync",
|
||||||
|
}),
|
||||||
extract: onManualExtract,
|
extract: onManualExtract,
|
||||||
compress: onManualCompress,
|
compress: onManualCompress,
|
||||||
sleep: onManualSleep,
|
sleep: onManualSleep,
|
||||||
|
|||||||
1
panel.js
1
panel.js
@@ -612,6 +612,7 @@ export function openPanel() {
|
|||||||
if (!overlayEl) return;
|
if (!overlayEl) return;
|
||||||
ensureOverlayMountedAtRoot();
|
ensureOverlayMountedAtRoot();
|
||||||
syncViewportCssVars();
|
syncViewportCssVars();
|
||||||
|
_actionHandlers.syncGraphLoad?.();
|
||||||
overlayEl.classList.add("active");
|
overlayEl.classList.add("active");
|
||||||
|
|
||||||
_restorePanelSize();
|
_restorePanelSize();
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ result = {
|
|||||||
maybeCaptureGraphShadowSnapshot,
|
maybeCaptureGraphShadowSnapshot,
|
||||||
loadGraphFromChat,
|
loadGraphFromChat,
|
||||||
saveGraphToChat,
|
saveGraphToChat,
|
||||||
|
syncGraphLoadFromLiveContext,
|
||||||
onMessageReceived,
|
onMessageReceived,
|
||||||
applyGraphLoadState,
|
applyGraphLoadState,
|
||||||
maybeFlushQueuedGraphPersist,
|
maybeFlushQueuedGraphPersist,
|
||||||
@@ -304,6 +305,83 @@ result = {
|
|||||||
assert.equal(harness.api.getCurrentGraph().historyState.chatId, "chat-global");
|
assert.equal(harness.api.getCurrentGraph().historyState.chatId, "chat-global");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const harness = await createGraphPersistenceHarness({
|
||||||
|
chatId: "",
|
||||||
|
globalChatId: "",
|
||||||
|
chatMetadata: {},
|
||||||
|
});
|
||||||
|
const lateGraph = createMeaningfulGraph("chat-late", "late");
|
||||||
|
harness.api.setChatContext({
|
||||||
|
chatId: "chat-late",
|
||||||
|
chatMetadata: {
|
||||||
|
integrity: "chat-late-ready",
|
||||||
|
st_bme_graph: lateGraph,
|
||||||
|
},
|
||||||
|
characterId: "char-late",
|
||||||
|
groupId: null,
|
||||||
|
chat: [{ is_user: true, mes: "late load" }],
|
||||||
|
updateChatMetadata(patch) {
|
||||||
|
const base =
|
||||||
|
this.chatMetadata &&
|
||||||
|
typeof this.chatMetadata === "object" &&
|
||||||
|
!Array.isArray(this.chatMetadata)
|
||||||
|
? this.chatMetadata
|
||||||
|
: {};
|
||||||
|
this.chatMetadata = {
|
||||||
|
...base,
|
||||||
|
...(patch || {}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
saveMetadataDebounced() {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = harness.api.syncGraphLoadFromLiveContext({
|
||||||
|
source: "late-context-sync",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.synced, true);
|
||||||
|
assert.equal(result.loadState, "loaded");
|
||||||
|
assert.equal(harness.api.getCurrentGraph().historyState.chatId, "chat-late");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const harness = await createGraphPersistenceHarness({
|
||||||
|
chatId: "",
|
||||||
|
globalChatId: "",
|
||||||
|
chatMetadata: {},
|
||||||
|
});
|
||||||
|
harness.api.setChatContext({
|
||||||
|
chatId: "chat-empty-live",
|
||||||
|
chatMetadata: {
|
||||||
|
integrity: "chat-empty-live-ready",
|
||||||
|
},
|
||||||
|
characterId: "char-empty-live",
|
||||||
|
groupId: null,
|
||||||
|
chat: [{ is_user: true, mes: "hello" }],
|
||||||
|
updateChatMetadata(patch) {
|
||||||
|
const base =
|
||||||
|
this.chatMetadata &&
|
||||||
|
typeof this.chatMetadata === "object" &&
|
||||||
|
!Array.isArray(this.chatMetadata)
|
||||||
|
? this.chatMetadata
|
||||||
|
: {};
|
||||||
|
this.chatMetadata = {
|
||||||
|
...base,
|
||||||
|
...(patch || {}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
saveMetadataDebounced() {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = harness.api.syncGraphLoadFromLiveContext({
|
||||||
|
source: "late-empty-sync",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.synced, true);
|
||||||
|
assert.equal(result.loadState, "empty-confirmed");
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const harness = await createGraphPersistenceHarness({
|
const harness = await createGraphPersistenceHarness({
|
||||||
chatId: "",
|
chatId: "",
|
||||||
|
|||||||
Reference in New Issue
Block a user